❶ linux-怎麼理解軟中斷
中斷是系統用來響應硬體設備請求的一種機制,它會打斷進程的正常調度和執行,然後調用內核中的中斷處理程序來響應設備的請求。
你可能要問了,為什麼要有中斷呢?我可以舉個生活中的例子,讓感受一下中斷的魅力。
比如你訂了一份外賣,但是不確定外賣什麼時候送到,也沒有別的方法了解外賣的進度,但是,配送員送外賣是不等人的,到了你這兒沒人取的話,就直接走人了,所以你只能苦苦等著,時不時去門口看看外賣送到沒,而不能幹其他事情。
不過呢,如果在訂外賣的時候,你就跟配送員約定好,讓他送到後給你打個電話,那你就不用苦苦等待了,就可以去忙別的事情,直到電話一響,接電話、取外賣就可以了。
這里的「打電話」,其實就是一個中斷。沒接到電話的時候,你可以做其他的事情;只有接到了電話(也就是發生中斷),你才要進行另一個動作:取外賣。
這個例子你就可以發現, 中斷其實是一種非同步的事件處理機制,可以提高系統的並發處理能力。
由於中斷處理程序會打斷其他進程的運行,所以, 為了減少對正常進程運行調度的影響,中斷處理程序就需要盡可能快地運行。 如果中斷本身要做的事情不多,那麼處理起來也不會有太大問題;但如果中斷要處理的事情很多,中斷服務程序就有可能要運行很長時間。
特別是,中斷處理程序在響應中斷時,還會臨時關閉中斷。這就會導致上一次中斷處理完成之前,其他中斷都不能響應,也就是說中斷有可能會丟失。
那麼還是以取外賣為例。假如你訂了 2 份外賣,一份主食和一份飲料,並且是由 2 個不同的配送員來配送。這次你不用時時等待著,兩份外賣都約定了電話取外賣的方式。但是,問題又來了。
當第一份外賣送到時,配送員給你打了個長長的電話,商量發票的處理方式。與此同時,第二個配送員也到了,也想給你打電話。
但是很明顯,因為電話占線(也就是關閉了中斷響應),第二個配送員的電話是打不通的。所以,第二個配送員很可能試幾次後就走掉了(也就是丟失了一次中斷)。
如果你弄清楚了「取外賣」的模式,那對系統的中斷機制就很容易理解了。事實上,為了解決中斷處理程序執行過長和中斷丟失的問題,Linux 將中斷處理過程分成了兩個階段,也就是 上半部和下半部:
比如說前面取外賣的例子,上半部就是你接聽電話,告訴配送員你已經知道了,其他事兒見面再說,然後電話就可以掛斷了;下半部才是取外賣的動作,以及見面後商量發票處理的動作。
這樣,第一個配送員不會佔用你太多時間,當第二個配送員過來時,照樣能正常打通你的電話。
除了取外賣,我再舉個最常見的網卡接收數據包的例子,讓你更好地理解。
網卡接收到數據包後,會通過 硬體中斷 的方式,通知內核有新的數據到了。這時,內核就應該調用中斷處理程序來響應它。你可以自己先想一下,這種情況下的上半部和下半部分別負責什麼工作呢?
對上半部來說,既然是快速處理,其實就是要把網卡的數據讀到內存中,然後更新一下硬體寄存器的狀態(表示數據已經讀好了),最後再發送一個 軟中斷 信號,通知下半部做進一步的處理。
而下半部被軟中斷信號喚醒後,需要從內存中找到網路數據,再按照網路協議棧,對數據進行逐層解析和處理,直到把它送給應用程序。
所以,這兩個階段你也可以這樣理解:
實際上,上半部會打斷 CPU 正在執行的任務,然後立即執行中斷處理程序。而下半部以內核線程的方式執行,並且每個 CPU 都對應一個軟中斷內核線程,名字為 「ksoftirqd/CPU 編號」,比如說, 0 號 CPU 對應的軟中斷內核線程的名字就是 ksoftirqd/0。
不過要注意的是,軟中斷不只包括了剛剛所講的硬體設備中斷處理程序的下半部,一些內核自定義的事件也屬於軟中斷,比如內核調度和 RCU 鎖(Read-Copy Update 的縮寫,RCU 是 Linux 內核中最常用的鎖之一)等。
不知道你還記不記得,前面提到過的 proc 文件系統。它是一種內核空間和用戶空間進行通信的機制,可以用來查看內核的數據結構,或者用來動態修改內核的配置。其中:
運行下面的命令,查看 /proc/softirqs 文件的內容,你就可以看到各種類型軟中斷在不同 CPU 上的累積運行次數:
在查看 /proc/softirqs 文件內容時,你要特別注意以下這兩點。
第一,要注意軟中斷的類型,也就是這個界面中第一列的內容。從第一列你可以看到,軟中斷包括了 10 個類別,分別對應不同的工作類型。比如 NET_RX 表示網路接收中斷,而 NET_TX 表示網路發送中斷。
第二,要注意同一種軟中斷在不同 CPU 上的分布情況,也就是同一行的內容。正常情況下,同一種中斷在不同 CPU 上的累積次數應該差不多。比如這個界面中,NET_RX 在 CPU0 和 CPU1 上的中斷次數基本是同一個數量級,相差不大。
不過你可能發現,TASKLET 在不同 CPU 上的分布並不均勻。TASKLET 是最常用的軟中斷實現機制,每個 TASKLET 只運行一次就會結束 ,並且只在調用它的函數所在的 CPU 上運行。
因此,使用 TASKLET 特別簡便,當然也會存在一些問題,比如說由於只在一個 CPU 上運行導致的調度不均衡,再比如因為不能在多個 CPU 上並行運行帶來了性能限制。
另外,剛剛提到過,軟中斷實際上是以內核線程的方式運行的,每個 CPU 都對應一個軟中斷內核線程,這個軟中斷內核線程就叫做 ksoftirqd/CPU 編號。那要怎麼查看這些線程的運行狀況呢?
其實用 ps 命令就可以做到,比如執行下面的指令:
注意,這些線程的名字外面都有中括弧,這說明 ps 無法獲取它們的命令行參數(cmline)。一般來說,ps 的輸出中,名字括在中括弧里的,一般都是內核線程。
Linux 中的中斷處理程序分為上半部和下半部:
上半部對應硬體中斷,用來快速處理中斷。
下半部對應軟中斷,用來非同步處理上半部未完成的工作。
Linux 中的軟中斷包括網路收發、定時、調度、RCU 鎖等各種類型,可以通過查看 /proc/softirqs 來觀察軟中斷的運行情況。
❷ linux中斷的下半部機制有哪些
一、中斷處理為什麼要下半部?Linux在中斷處理中間中斷處理分了上半部和下半部,目的就是提高系統的響應能力和並發能力。通俗一點來講:當一個中斷產生,調用該中斷對應的處理程序(上半部)然後告訴系統,對應的後半部可以執行了。然後中斷處理程序就返回,下半部會在合適的時機有系統調用。這樣一來就大大的減少了中斷處理所需要的時間。
二、那些工作應該放在上半部,那些應該放在下半部?
沒有嚴格的規則,只有一些提示:
1、對時間非常敏感,放在上半部。
2、與硬體相關的,放在上半部。
3、不能被其他中斷打斷的工作,放在上半部。
以上三點之外的,考慮放在下半部。
三、下半部機制在Linux中是怎麼實現的?
下半部在Linux中有以下實現機制:
1、BH(在2.5中刪除)
2、任務隊列(task queue,在2.5刪除)
3、軟中斷(softirq,2.3開始。本文重點)
4、tasklet(2.3開始)
5、工作隊列(work queue,2.5開始)
四、軟中斷是怎麼實現的(以下代碼出自2.6.32)?
軟中斷不會搶占另外一個軟中斷,唯一可以搶占軟中斷的是中斷處理程序。
軟中斷可以在不同CPU上並發執行(哪怕是同一個軟中斷)
1、軟中斷是編譯期間靜態分配的,定義如下:
struct softirq_action { void (*action)(struct softirq_action *); };
/*
* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
* frequency threaded job scheling. For almost all the purposes
* tasklets are more than enough. F.e. all serial device BHs et
* al. should be converted to tasklets, not to softirqs.
*/
enum {
HI_SOFTIRQ=0,
TIMER_SOFTIRQ,
NET_TX_SOFTIRQ,
NET_RX_SOFTIRQ,
BLOCK_SOFTIRQ,
BLOCK_IOPOLL_SOFTIRQ,
TASKLET_SOFTIRQ,
SCHED_SOFTIRQ,
HRTIMER_SOFTIRQ,
RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */
NR_SOFTIRQS
❸ Linux如何及時響應外部中斷
FPGA每隔100us給運行linux的ARM一個中斷,要求在20us內響應中斷,並讀走2000*16bit的數據。
目前主要的問題是,當系統同時發生多個中斷時,會嚴重影響linux對FPGA中斷的響應時間。如何解決?
1、首先想到了ARM的FIQ,它可以打斷IRQ中斷服務程序,保證對外部FIQ的及時響應。但是發現linux只實現了IRQ,沒有顯示FIQ。
linux是從devicetree讀取中斷號,加入中斷向量表的。
interrupts = <0x0 0x32 0x0>;中的第一個欄位0表示非共享中斷,非零表示共享中斷,SDK產生的dts統一為0,此時第二欄位的值比XPS中的小32;如果第一欄位非零,則第二欄位比XPS小16.
最後欄位表示中斷的觸發方式。
IRQ_TYPE_EDGE_RISING =0x00000001,
IRQ_TYPE_EDGE_FALLING =0x00000002,
IRQ_TYPE_LEVEL_HIGH =0x00000004,
IRQ_TYPE_LEVEL_LOW =0x00000008,
很明顯,devicetree根本沒有提供通知linux有FIQ的渠道。
2、再來看linux的IRQ
linux的中斷分為上半部和下半部,上半部運行在IRQ模式,會屏蔽所有中斷,下半部運行在SVC模式,會重新打開中斷。
也就是說,當一個中斷的上半部正在運行時(不能再次響應中斷),FPGA的中斷是不能被linux響應的;
反過來,當FPGA中斷的上半部正在運行時(不能再次響應中斷),其他的中斷也不能被linux響應;
unsigned long flags;
...
local_irq_save(flags);
....
local_irq_restore(flags);
3.
ARM有七種模式,我們這里只討論SVC、IRQ和FIQ模式。
我們可以假設ARM核心有兩根中斷引腳(實際上是看不見的),一根叫 irq pin, 一根叫fiq pin.
在ARM的cpsr中,有一個I位和一個F位,分別用來禁止IRQ和FIQ的。
先不說中斷控制器,只說ARM核心。正常情況下,ARM核都只是機械地隨著pc的指示去做事情,當CPSR中的I和F位為1的時候,IRQ和FIQ全部處於禁止狀態。無論你在irq
pin和fiq pin上面發什麼樣的中斷信號,ARM是不會理你的,你根本不能打斷他,因為他耳聾了,眼也瞎了。
在I位和F位為0的時候,當irq
pin上有中斷信號過來的時候,就會打斷arm的當前工作,並且切換到IRQ模式下,並且跳到相應的異常向量表(vector)位置去執行代碼。這個過程是自動的,但是返回到被中斷打斷的地方就得您親自動手了。當你跳到異常向量表,處於IRQ的模式的時候,這個時候如果irq
pin上面又來中斷信號了,這個時候ARM不會理你的,irq
pin就跟秘書一樣,ARM核心就像老闆,老闆本來在做事,結果來了一個客戶,秘書打斷它,讓客戶進去了。而這個時候再來一個客戶,要麼秘書不斷去敲門問,要麼客戶走人。老闆第一個客戶沒有會見完,是不會理你的。
但是有一種情況例外,當ARM處在IRQ模式,這個時候fiq pin來了一個中斷信號,fiq
pin是什麼?是快速中斷呀,比如是公安局的來查刑事案件,那才不管你老闆是不是在會見客戶,直接打斷,進入到fiq模式下,並且跳到相應的fiq的異常向量表處去執行代碼。那如果當ARM處理FIQ模式,fiq
pin又來中斷信號,又就是又一批公安來了,那沒戲,都是執法人員,你打不斷我。那如果這個時候irq
pin來了呢?來了也不理呀,正在辦案,還敢來妨礙公務。
所以得出一個結論: IRQ模式只能被FIQ模式打斷,FIQ模式下誰也打不斷。
在打不斷的情況下,irq pin 或 fiq pin隨便你怎麼發中斷信號,都是白發。
所以除了fiq能打斷irq以外,根本沒有所謂中斷嵌套的情況。
Linux不用FIQ,只用到了IRQ。但是我們有時候一個中斷需要處理很長時間,那我們就需要佔用IRQ模式那麼長的時間嗎?沒有,linux在IRQ模式下只是簡單的記錄是什麼中斷,馬上就切換回了SVC模式,換句話說,Linux的中斷處理都是在SVC模式下處理的。
只不過SVC模式下的ISR上半部關閉了當前中斷線,下半部才重新打開
❹ 詳解Linux系統內存知識及調優方案
內存是計算機中重要的部件之一,它是與CPU進行溝通的橋梁。計算機中所有程序的運行都是在內存中進行的,因此內存的性能對計算機的影響非常大。內存作用是用於暫時存放CPU中的運算數據,以及與硬碟等外部存儲器交換的數據。只要計算機在運行中,CPU就會把需要運算的數據調到內存中進行運算,當運算完成後CPU再將結果傳送出來,內存的運行也決定了計算機的穩定運行。對於整個操作系統來說,內存可能是最麻煩的的設備。而其性能的好壞直接影響著整個操作系統。
我們知道CPU是不能與硬碟打交道的,只有數據被載入到內存中才可以被CPU調用。cpu在訪問內存的時候需要先像內存監控程序請求,由監控程序控制和分配內存的讀寫請求,這個監控程序叫做MMU(內存管理單元)。下面以32位系統來說明內存的訪問過程:
32位的系統上每一個進程在訪問內存的時候,每一個進程都當做自己有4個G的內存空間可用,這叫虛擬內存(地址),虛擬內存轉化成物理內存是通過MMU來完成的。為了能夠從線性地址轉換成物理地址,需要page table(頁表)的內存空間,page table要載入到MMU上。為了完成線性地址到物理地址的映射,如果按照1個位元組1個位元組映射的話,需要一張非常大的表,這種轉換關系會非常的復雜。因此把內存空間又劃分成了另外一種存儲單元格式,通常為4K。在不同的硬體平台上,它們的大小一般是不一樣的,像x86 32位的有4k的頁;而64位的有4k頁,2M頁,4M頁,8M頁等等,默認都是4k的。每一個進程一般而言都有自己的頁路徑和頁表映射機制,不管那一個頁表都是由內核載入的。每一個進程只能看到自己的線性地址空間,想要增加新的內存的時候,只能在自己的線性地址空間中申請,並且申請後一定是通過操作系統的內核映射到物理地址空間中去找那麼一段空間,並且告訴線性地址空間准備好了,可以訪問,並且在page table中增加一條映射關系,於是就可以訪問物理內存了,這種叫做內存分配。但是新的申請一定是通過操作的內核到物理內存中去找那麼一段空間,並且告訴線性地址空間好了,可以建設映射關系,最終page table建立映射關系。
這反映了上述描述過程的大體情況。可以看到每一個用戶程序都會有自己的頁表,並且映射到對應的主存儲器上去。
根據上述文字和圖表的描述可以發現2個問題:
1.每個進程如果需要訪問內存的時候都需要去查找page table的話,勢必會造成伺服器的性能底下
2.如果主存儲器的內存滿了以後,應用程序還需要調用內存的時候怎麼辦
對於第一個問題,我們就需要藉助TLB(Translation Lookaside Buffer)翻譯後備緩沖器。TLB是一個內存管理單元,它可以用於改進虛擬地址到物理地址轉換速度的緩存。這樣每次在查找page table的時候就可以先去TLB中查找相應的頁表數據,如果有就直接返回,沒有再去查找page table,並把查找到的結果緩存中TLB中。TLB雖然解決了緩存的功能,但是在那麼page table中查找映射關系仍然很慢,所以又有了page table的分級目錄。page table可以分為1級目錄,2級目錄和偏移量
但是一個進程在運行的時候要頻繁的打開文件,關閉文件。這就意味著要頻繁的申請內存和釋放內存。有些能夠在內存中緩存數據的那些進程,他們對內存的分配和回收更多,那麼每一次分配都會在頁表中建立一個對應項。所以,就算內存的速度很快,大量頻繁的同一時間分配和釋放內存,依然會降低伺服器的整體性能。當然內存空間不夠用的時候,我們稱為oom(out of memory,內存耗盡)。當內存耗盡的時候,,整個操作系統掛了。這種情況下我們可以考慮交換分區,交換分區畢竟是由硬碟虛擬出來的內存,所以其性能與真正的內存相比,差了很多,所以要盡力避免使用交換分區。有物理內存空間的時候盡量保證全部使用物理內存。cpu無論如何是不能給交換內存打交道的,它也只能給物理內存打交道,能定址的空間也只能是物理內存。所以當真正物理內存空間不夠用的時候,會通過LRU演算法把其中最近最少使用的內存放到交換內存中去,這樣物理內存中的那段空間就可以供新的程序使用了。但是這樣會引發另外的一個問題,即原來的進程通過page table尋找的時候,那一段空間的數據已經不屬於它了。所以此刻cpu發送通知或者異常告訴這個程序,這個地址空間已不屬於它,這個時候可能會出現2種情況:
1.物理內存有可用的空間可用:這個時候cpu會根據以前的轉換策略會把交換分區中的那段內存重新送到物理內存中去,但是轉換過來的空間地址不一定會是以前的那一段空間地址,因為以前的那一段空間地址可能已經被別人使用了。
2.物理內存沒有可用的空間可用:這個時候依然會使用LRU算發把當前物理地址空間上最近最少使用的空間地址轉換到交換內存中去,並把當前進程需要的這斷在交換空間中的內存送到物理內存空間中去,並且重新建立映射關系。
上述通知或者異常出現的情況,通常叫做缺頁異常。缺頁異常也分為大異常和小異常兩種。大異常就是訪問的數據內存中沒有,不的不去硬碟上載入,無論是從交換內存中還是直接從磁碟的某個文件系統上,反正需要從硬碟上去載入,這種異常載入需要很長時間。小異常就是進程之間通過共享內存,第二個進程訪問的時候,查看本地的內存映射表沒有,但是其它進程已經擁有了這個內存頁,所以可以直接映射,這種異常載入需要的時間一般很短。
在操作系統開機的時候,每一個io設備都會像cpu申請一些列的隨機埠,這種埠叫做io埠。在IBM PC體系結構中,I/O地址空間一共提供了65,536個8位的I/O埠。正是這些io埠的存在,cpu可以與io設備進行讀寫交互的過程。在執行讀寫操作時,CPU使用地址匯流排選擇所請求的I/O埠,使用數據匯流排在CPU寄存器和埠之間傳送數據。I/O埠還可以被映射到物理地址空間:因此,處理器和I/O設備之間的通信就可以直接使用對內存進行操作的匯編語言指令(例如,mov、and、or等等)。現代的硬體設備更傾向於映射I/O,因為這樣處理的速度較快,並可以和DMA結合起來使用。這樣io在和內存傳數據的時候就不需要通過cpu,cpu把匯流排的控制權交給DMA,每次io傳數據的時候就調用DMA一次,就把cpu給解放了出來。當數據傳輸完了以後,DMA通知給cpu中斷一次。DMA在運行的時候對整個匯流排有控制許可權,當cpu發現有其它進程需要使用匯流排的時候,二者就會產生爭用。這個時候,在匯流排控制權的使用上,CPU和DMA具有相等的許可權。只要CPU委託給了DMA,就不能隨意的收回這個委託,就要等待DMA的用完。
如果沒有其它進程可以運行,或者其它進程運行的時間非常短,這個時候CPU發現我們的IO仍然沒有完成,那就意味著,CPU只能等待IO了。CPU在時間分配裡面有個iowait的值,就是CPU在等待IO花費的時間。有些是在同步調用過程中,CPU必須要等待IO的完成;否者CPU可以釋放IO的傳輸在背後自動完成,CPU自己去處理其它的事情。等硬碟數據傳輸完成以後,硬碟只需要像CPU發起一個通知即可。CPU外圍有一種設備,這個設備叫做可編程中斷控制器。每一個硬體設備為了給CPU通信,在剛開機的時候,在BIOS實現檢測的時候,這個設備就要到可編程中斷控制器上去注冊一個所謂的中斷號。那麼這個號碼就歸這個硬體使用了。當前主機上可能有多個硬體,每一個硬體都有自己的號碼,CPU在收到中斷號以後,就能夠通過中斷相量表查找到那個硬體設備進行中斷。並且就由對應的IO埠過來處理了。
CPU正在運行其它進程,當一個中斷請求發過來的時候,CPU會立即終止當前正在處理的進程,而去處理中斷。當前CPU掛起當前正在處理的進程,轉而去執行中斷的過程,也叫做中斷切換。只不過,這種切換在量級別上比進程切換要低一些,而且任何中斷的優先順序通常比任何進程也要高,因為我們指的是硬體中斷。中斷還分為上半部和下半部,一般而言,上半部就是CPU在處理的時候,把它接進來,放到內存中,如果這個事情不是特別緊急(CPU或者內核會自己判斷),因此在這種情況下,CPU回到現場繼續執行剛才掛起的進程,當這個進程處理完了,再回過頭來執行中斷的下半部分。
在32位系統中,我們的內存(線性地址)地址空間中,一般而言,低地址空間有一個G是給內核使用的,上面3個G是給進程使用的。但是應該明白,其實在內核內存當中,再往下,不是直接這樣劃分的。32位系統和64位系統可能不一樣(物理地址),在32位系統中,最低端有那麼10多M的空間是給DMA使用的。DNA的匯流排寬度是很小的,可能只有幾位,所以定址能力很有限,訪問的內存空間也就很有限。如果DMA需要復制數據,而且自己能夠定址物理內存,還可以把數據直接壯哉進內存中去,那麼就必須保證DMA能夠定址那段內存才行。定址的前提就是把最低地址斷M,DA的定址范圍內的那一段給了DMA。所以站在這個角度來說,我們的內存管理是分區域的。
在32位系統上,16M的內存空間給了ZONE_DMA(DMA使用的物理地址空間);從16M到896M給了ZONE_NORMAL(正常物理地址空間),對於Linux操作系統來說,是內核可以直接訪問的地址空間;從896M到1G這斷空間叫做"Reserved"(預留的物理地址空間);從1G到4G的這段物理地址空間中,我們的內核是不能直接訪問的,要想訪問必須把其中的一段內容映射到Reserved來,在Reserved中保留出那一段內存的地址編碼,我們內核才能上去訪問,所以內核不直接訪問大於1G的物理地址空間。所以在32位系統上,它訪問內存當中的數據,中間是需要一個額外步驟的。
在64位系統上,ZONE_DAM給了低端的1G地址空間,這個時候DMA的定址能力被大大加強了;ZONE_DAM32可以使用4G的空間;而大於1G以上給劃分了ZONE_NORMAL,這段空間都可以被內核直接訪問。所以在64位上,內核訪問大於1G的內存地址,就不需要額外的步驟了,效率和性能上也大大增加,這也就是為什麼要使用64位系統的原因。
在現在的PC架構上,AMD,INTER都支持一種機制,叫做PEA(物理地址擴展)。所謂PAE。指的是在32位系統的地址匯流排上,又擴展了4位,使得32位系統上的地址空間可以達到64G。當然在32為系統上,不管你的物理內存有多大,單個進程所使用的空間是無法擴展的。因為在32位的系統上,線性地址空間只有4個G,而單個進程能夠識別的訪問也只有3個G。
linux的虛擬內存子系統包含了以下幾個功能模塊:
slab allocator,zoned buddy allocator,MMU,kswapd,bdflush
slab allocator叫做slab分配器
buddy allocator又叫做buddy system,叫做夥伴系統,也是一種內存分配器
buddy system是工作在MMU之上的,而slab allocator又是工作在buddy system之上的。
設置為小於等於1G,在資料庫伺服器應該勁量避免使用交換內存
3.在應用伺服器上,可以設置為RAM*0.5,當然這個是理論值
如果不的不使用交換內存,應該把交換內存放到最靠外的磁軌分區上,因為最外邊的磁碟的訪問速度最快。所以如果有多塊硬碟,可以把每塊硬碟的最外層的磁軌拿一小部分出來作為交換分區。交換分區可以定義優先順序,因此把這些硬碟的交換內存的優先順序設置為一樣,可以實現負載均衡的效果。定義交換分區優先順序的方法為編輯/etc/fstab:
/dev/sda1 swap swap pri=5 0 0
/dev/sdb1 swap swap pri=5 0 0
/dev/sdc1 swap swap pri=5 0 0
/dev/sdd1 swap swap pri=5 0 0
四.內存耗盡時候的相關調優參數
當Linux內存耗盡的時候,它會殺死那些佔用內存最多的進程,以下三種情況會殺死進程:
1.所有的進程都是活動進程,這個時候想交換出去都沒有空閑的進程
2.沒有可用的page頁在ZONE_NORMAL中
3.有其它新進程啟動,申請內存空間的時候,要找一個空閑內存給做映射,但是這個時候找不到了
一旦內存耗盡的時候,操作系統就會啟用oom-kill機制。
在/proc/PID/目錄下有一個文件叫做oom_score,就是用來指定oom的評分的,就是壞蛋指數。
如果要手動啟用oom-kill機制的話,只需要執行echo f>/proc/sysrq-trigger即可,它會自動殺掉我們指定的壞蛋指數評分最高的那個進程
可以通過echo n > /proc/PID/oom_adj來調整一個進程的壞蛋評分指數。最終的評分指數就是2的oom_adj的值的N次方。假如我們的一個進程的oom_adj的值是5,那麼它的壞蛋評分指數就是2的5次方。
如果想禁止oom-kill功能的使用可以使用vm.panic_on_oom=1即可。
五.與容量有關的內存調優參數:
overcommit_memory,可用參數有3個,規定是否能夠過量使用內存:
0:默認設置,內核執行啟發式的過量使用處理
1:內核執行無內存的過量使用處理。使用這個值會增大內存超載的可能性
2:內存的使用量等於swap的大小+RAM*overcommit_ratio的值。如果希望減小內存的過度使用,這個值是最安全的
overcommit_ratio:將overcommit_memory指定為2時候,提供的物理RAM比例,默認為50
六.與通信相關的調優參數
常見在同一個主機中進行進程間通信的方式:
1.通過消息message;2.通過signal信號量進行通信;3.通過共享內存進行通信,跨主機常見的通信方式是rpc
以消息的方式實現進程通信的調優方案:
msgmax:以位元組為單位規定消息隊列中任意消息的最大允許大小。這個值一定不能超過該隊列的大小(msgmnb),默認值為65536
msgmnb:以位元組為單位規定單一消息隊列的最大值(最大長度)。默認為65536位元組
msgmni:規定消息隊列識別符的最大數量(及隊列的最大數量)。64位架構機器的默認值為1985;32位架構機器的默認值為1736
以共享內存方式實現進程通信的調優方案:
shmall:以位元組為單位規定一次在該系統中可以使用的共享內存總量(單次申請的上限)
shmmax:以位元組為單位規定每一個共享內存片段的最大大小
shmmni:規定系統范圍內最大共享內存片段。在64和32位的系統上默認值都是4096
七.與容量相關的文件系統可調優參數:
file-max:列出內核分配的文件句柄的最大值
dirty_ratio:規定百分比值,當臟數據達到系統內存總數的這個百分比值後開始執行pdflush,默認為20
dirty_background_ratio:規定百分比值,當某一個進程自己所佔用的臟頁比例達到系統內存總數的這個百分比值後開始在後台執行pdflush,默認為10
dirty_expire_centisecs:pdlush每隔百分之一秒的時間開啟起來刷新臟頁,默認值為3000,所以每隔30秒起來開始刷新臟頁
dirty_writeback_centisecs:每隔百分之一秒開始刷新單個臟頁。默認值為500,所以一個臟頁的存在時間達到了5秒,就開始刷新臟
八.linux內存常用的觀察指標命令:
Memory activity
vmstat [interval] [count]
sar -r [interval] [count]
Rate of change in memory
sar -R [interval] [count]
frmpg/s:每秒釋放或者分配的內存頁,如果為正數,則為釋放的內存頁;如果為負數,則為分配的內存頁
bufpg/s:每秒buffer中獲得或者釋放的內存頁。如果為正數則為獲得的內存頁,為負數。則為釋放的內存頁
campg/s:每秒cache中獲得或者釋放的內存頁。如果為正數則為獲得的內存頁,為負數。則為釋放的內存頁
Swap activity
sar -W [interval] [count]
ALL IO
sar -B [interval] [count]
pgpgin/s:每秒從磁碟寫入到內核的塊數量
pgpgout/s:每秒從內核寫入到磁碟的塊數量
fault/s:每秒鍾出現的缺頁異常的個數
majflt/s:每秒鍾出現的大頁異常的個數
pgfree/s:每秒回收回來的頁面個數
❺ linux內核中斷處理為什麼分成上半部分和下半部分
一、中斷處理為什麼要下半部? Linux在中斷處理中間中斷處理分了上半部和下半部,目的就是提高系統的響應能力和並發能力。通俗一點來講:當一個中斷產生,調用該中斷對應的處理程序(上半部)然後告訴系統,對應的後半部可以執行了。然後中斷處理...
❻ Linux中斷 異常 系統調用 中斷上半部 中斷下半部 這些有什麼區別和聯系
中斷分軟中斷跟硬中斷,硬中斷是由硬體從外部觸發,軟中斷由軟體觸發,就像linux系統調用int 80一樣。至於中斷的上下部其實就是因為中斷的處理時間跟它的優先順序不一定成正比,所以一般先處理中斷最重要的部分(上半部),待到不怎麼忙的時候,再來處理比較悠閑的部分(下半部)。就像輸入的時候,拿到鍵盤輸入的是什麼才是最重要的(上半部),顯示字元才是次要的(下半部)。
❼ linux內核同步問題
Linux內核設計與實現 十、內核同步方法
手把手教Linux驅動5-自旋鎖、信號量、互斥體概述
== 基礎概念: ==
並發 :多個執行單元同時進行或多個執行單元微觀串列執行,宏觀並行執行
競態 :並發的執行單元對共享資源(硬體資源和軟體上的全局變數)的訪問而導致的竟態狀態。
臨界資源 :多個進程訪問的資源
臨界區 :多個進程訪問的代碼段
== 並發場合: ==
1、單CPU之間進程間的並發 :時間片輪轉,調度進程。 A進程訪問列印機,時間片用完,OS調度B進程訪問列印機。
2、單cpu上進程和中斷之間並發 :CPU必須停止當前進程的執行中斷;
3、多cpu之間
4、單CPU上中斷之間的並發
== 使用偏向: ==
==信號量用於進程之間的同步,進程在信號量保護的臨界區代碼裡面是可以睡眠的(需要進行進程調度),這是與自旋鎖最大的區別。==
信號量又稱為信號燈,它是用來協調不同進程間的數據對象的,而最主要的應用是共享內存方式的進程間通信。本質上,信號量是一個計數器,它用來記錄對某個資源(如共享內存)的存取狀況。它負責協調各個進程,以保證他們能夠正確、合理的使用公共資源。它和spin lock最大的不同之處就是:無法獲取信號量的進程可以睡眠,因此會導致系統調度。
1、==用於進程與進程之間的同步==
2、==允許多個進程進入臨界區代碼執行,臨界區代碼允許睡眠;==
3、信號量本質是==基於調度器的==,在UP和SMP下沒有區別;進程獲取不到信號量將陷入休眠,並讓出CPU;
4、不支持進程和中斷之間的同步
5、==進程調度也是會消耗系統資源的,如果一個int型共享變數就需要使用信號量,將極大的浪費系統資源==
6、信號量可以用於多個線程,用於資源的計數(有多種狀態)
==信號量加鎖以及解鎖過程:==
sema_init(&sp->dead_sem, 0); / 初始化 /
down(&sema);
臨界區代碼
up(&sema);
==信號量定義:==
==信號量初始化:==
==dowm函數實現:==
==up函數實現:==
信號量一般可以用來標記可用資源的個數。
舉2個生活中的例子:
==dowm函數實現原理解析:==
(1)down
判斷sem->count是否 > 0,大於0則說明系統資源夠用,分配一個給該進程,否則進入__down(sem);
(2)__down
調用__down_common(sem, TASK_UNINTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);其中TASK_UNINTERRUPTIBLE=2代表進入睡眠,且不可以打斷;MAX_SCHEDULE_TIMEOUT休眠最長LONG_MAX時間;
(3)list_add_tail(&waiter.list, &sem->wait_list);
把當前進程加入到sem->wait_list中;
(3)先解鎖後加鎖;
進入__down_common前已經加鎖了,先把解鎖,調用schele_timeout(timeout),當waiter.up=1後跳出for循環;退出函數之前再加鎖;
Linux內核ARM構架中原子變數的底層實現研究
rk3288 原子操作和原子位操作
原子變數適用於只共享一個int型變數;
1、原子操作是指不被打斷的操作,即它是最小的執行單位。
2、最簡單的原子操作就是一條條的匯編指令(不包括一些偽指令,偽指令會被匯編器解釋成多條匯編指令)
==常見函數:==
==以atomic_inc為例介紹實現過程==
在Linux內核文件archarmincludeasmatomic.h中。 執行atomic_read、atomic_set這些操作都只需要一條匯編指令,所以它們本身就是不可打斷的。 需要特別研究的是atomic_inc、atomic_dec這類讀出、修改、寫回的函數。
所以atomic_add的原型是下面這個宏:
atomic_add等效於:
result(%0) tmp(%1) (v->counter)(%2) (&v->counter)(%3) i(%4)
注意:根據內聯匯編的語法,result、tmp、&v->counter對應的數據都放在了寄存器中操作。如果出現上下文切換,切換機制會做寄存器上下文保護。
(1)ldrex %0, [%3]
意思是將&v->counter指向的數據放入result中,並且(分別在Local monitor和Global monitor中)設置獨占標志。
(2)add %0, %0, %4
result = result + i
(3)strex %1, %0, [%3]
意思是將result保存到&v->counter指向的內存中, 此時 Exclusive monitors會發揮作用,將保存是否成功的標志放入tmp中。
(4) teq %1, #0
測試strex是否成功(tmp == 0 ??)
(5)bne 1b
如果發現strex失敗,從(1)再次執行。
Spinlock 是內核中提供的一種比較常見的鎖機制,==自旋鎖是「原地等待」的方式解決資源沖突的==,即,一個線程獲取了一個自旋鎖後,另外一個線程期望獲取該自旋鎖,獲取不到,只能夠原地「打轉」(忙等待)。由於自旋鎖的這個忙等待的特性,註定了它使用場景上的限制 —— 自旋鎖不應該被長時間的持有(消耗 CPU 資源),一般應用在==中斷上下文==。
1、spinlock是一種死等機制
2、信號量可以允許多個執行單元進入,spinlock不行,一次只能允許一個執行單元獲取鎖,並且進入臨界區,其他執行單元都是在門口不斷的死等
3、由於不休眠,因此spinlock可以應用在中斷上下文中;
4、由於spinlock死等的特性,因此臨界區執行代碼盡可能的短;
==spinlock加鎖以及解鎖過程:==
spin_lock(&devices_lock);
臨界區代碼
spin_unlock(&devices_lock);
==spinlock初始化==
==進程和進程之間同步==
==本地軟中斷之間同步==
==本地硬中斷之間同步==
==本地硬中斷之間同步並且保存本地中斷狀態==
==嘗試獲取鎖==
== arch_spinlock_t結構體定義如下: ==
== arch_spin_lock的實現如下: ==
lockval(%0) newval(%1) tmp(%2) &lock->slock(%3) 1 << TICKET_SHIFT(%4)
(1)ldrex %0, [%3]
把lock->slock的值賦值給lockval;並且(分別在Local monitor和Global monitor中)設置獨占標志。
(2)add %1, %0, %4
newval =lockval +(1<<16); 相當於next+1;
(3)strex %2, %1, [%3]
newval =lockval +(1<<16); 相當於next+1;
意思是將newval保存到 &lock->slock指向的內存中, 此時 Exclusive monitors會發揮作用,將保存是否成功的標志放入tmp中。
(4) teq %2, #0
測試strex是否成功
(5)bne 1b
如果發現strex失敗,從(1)再次執行。
通過上面的分析,可知關鍵在於strex的操作是否成功的判斷上。而這個就歸功於ARM的Exclusive monitors和ldrex/strex指令的機制。
(6)while (lockval.tickets.next != lockval.tickets.owner)
如何lockval.tickets的next和owner是否相等。相同則跳出while循環,否則在循環內等待判斷;
* (7)wfe()和smp_mb() 最終調用#define barrier() asm volatile ("": : :"memory") *
阻止編譯器重排,保證編譯程序時在優化屏障之前的指令不會在優化屏障之後執行。
== arch_spin_unlock的實現如下: ==
退出鎖時:tickets.owner++
== 出現死鎖的情況: ==
1、擁有自旋鎖的進程A在內核態阻塞了,內核調度B進程,碰巧B進程也要獲得自旋鎖,此時B只能自旋轉。 而此時搶占已經關閉,(單核)不會調度A進程了,B永遠自旋,產生死鎖。
2、進程A擁有自旋鎖,中斷到來,CPU執行中斷函數,中斷處理函數,中斷處理函數需要獲得自旋鎖,訪問共享資源,此時無法獲得鎖,只能自旋,產生死鎖。
== 如何避免死鎖: ==
1、如果中斷處理函數中也要獲得自旋鎖,那麼驅動程序需要在擁有自旋鎖時禁止中斷;
2、自旋鎖必須在可能的最短時間內擁有
3、避免某個獲得鎖的函數調用其他同樣試圖獲取這個鎖的函數,否則代碼就會死鎖;不論是信號量還是自旋鎖,都不允許鎖擁有者第二次獲得這個鎖,如果試圖這么做,系統將掛起;
4、鎖的順序規則(a) 按同樣的順序獲得鎖;b) 如果必須獲得一個局部鎖和一個屬於內核更中心位置的鎖,則應該首先獲取自己的局部鎖 ;c) 如果我們擁有信號量和自旋鎖的組合,則必須首先獲得信號量;在擁有自旋鎖時調用down(可導致休眠)是個嚴重的錯誤的;)
== rw(read/write)spinlock: ==
加鎖邏輯:
1、假設臨界區內沒有任何的thread,這個時候任何的讀線程和寫線程都可以鍵入
2、假設臨界區內有一個讀線程,這時候信賴的read線程可以任意進入,但是寫線程不能進入;
3、假設臨界區有一個寫線程,這時候任何的讀、寫線程都不可以進入;
4、假設臨界區內有一個或者多個讀線程,寫線程不可以進入臨界區,但是寫線程也無法阻止後續的讀線程繼續進去,要等到臨界區所有的讀線程都結束了,才可以進入,可見:==rw(read/write)spinlock更加有利於讀線程;==
== seqlock(順序鎖): ==
加鎖邏輯:
1、假設臨界區內沒有任何的thread,這個時候任何的讀線程和寫線程都可以鍵入
2、假設臨界區內沒有寫線程的情況下,read線程可以任意進入;
3、假設臨界區有一個寫線程,這時候任何的讀、寫線程都不可以進入;
4、假設臨界區內只有read線程的情況下,寫線程可以理解執行,不會等待,可見:==seqlock(順序鎖)更加有利於寫線程;==
讀寫速度 : CPU > 一級緩存 > 二級緩存 > 內存 ,因此某一個CPU0的lock修改了,其他的CPU的lock就會失效;那麼其他CPU就會依次去L1 L2和主存中讀取lock值,一旦其他CPU去讀取了主存,就存在系統性能降低的風險;
mutex用於互斥操作。
互斥體只能用於一個線程,資源只有兩種狀態(佔用或者空閑)
1、mutex的語義相對於信號量要簡單輕便一些,在鎖爭用激烈的測試場景下,mutex比信號量執行速度更快,可擴展
性更好,
2、另外mutex數據結構的定義比信號量小;、
3、同一時刻只有一個線程可以持有mutex
4、不允許遞歸地加鎖和解鎖
5、當進程持有mutex時,進程不可以退出。
• mutex必須使用官方API來初始化。
• mutex可以睡眠,所以不允許在中斷處理程序或者中斷下半部中使用,例如tasklet、定時器等
==常見操作:==
struct mutex mutex_1;
mutex_init(&mutex_1);
mutex_lock(&mutex_1)
臨界區代碼;
mutex_unlock(&mutex_1)
==常見函數:==
=
❽ 《Linux設備驅動程序》(十六)-中斷處理
設備與處理器之間的工作通常來說是非同步,設備數據要傳遞給處理器通常來說有以下幾種方法:輪詢、等待和中斷。
讓CPU進行輪詢等待總是不能讓人滿意,所以通常都採用中斷的形式,讓設備來通知CPU讀取數據。
2.6內核的函數參數與現在的參數有所區別,這里都主要介紹概念,具體實現方法需要結合具體的內核版本。
request_irq函數申請中斷,返回0表示申請成功,其他返回值表示申請失敗,其具體參數解釋如下:
flags 掩碼可以使用以下幾個:
快速和慢速處理常式 :現代內核中基本沒有這兩個概念了,使用SA_INTERRUPT位後,當中斷被執行時,當前處理器的其他中斷都將被禁止。通常不要使用SA_INTERRUPT標志位,除非自己明確知道會發生什麼。
共享中斷 :使用共享中斷時,一方面要使用SA_SHIRQ位,另一個是request_irq中的dev_id必須是唯一的,不能為NULL。這個限制的原因是:內核為每個中斷維護了一個共享處理常式的列表,常式中的dev_id各不相同,就像設備簽名。如果dev_id相同,在卸載的時候引起混淆(卸載了另一個中斷),當中斷到達時會產生內核OOP消息。
共享中斷需要滿足以下一個條件才能申請成功:
當不需要使用該中斷時,需要使用free_irq釋放中斷。
通常我們會在模塊載入的時候申請安裝中斷處理常式,但書中建議:在設備第一次打開的時候安裝,在設備最後一次關閉的時候卸載。
如果要查看中斷觸發的次數,可以查看 /proc/interrupts 和 /proc/stat。
書中講述了如何自動檢測中斷號,在嵌入式開發中通常都是查看原理圖和datasheet來直接確定。
自動檢測的原理如下:驅動程序通知設備產生中斷,然後查看哪些中斷信號線被觸發了。Linux提供了以下方法來進行探測:
探測工作耗時較長,建議在模塊載入的時候做。
中斷處理函數和普通函數其實差不多,唯一的區別是其運行的中斷上下文中,在這個上下文中有以下注意事項:
中斷處理函數典型用法如下:
中斷處理函數的參數和返回值含義如下:
返回值主要有兩個:IRQ_NONE和IRQ_HANDLED。
對於中斷我們是可以進行開啟和關閉的,Linux中提供了以下函數操作單個中斷的開關:
該方法可以在所有處理器上禁止或啟用中斷。
需要注意的是:
如果要關閉當前處理器上所有的中斷,則可以調用以下方法:
local_irq_save 會將中斷狀態保持到flags中,然後禁用處理器上的中斷;如果明確知道中斷沒有在其他地方被禁用,則可以使用local_irq_disable,否則請使用local_irq_save。
locat_irq_restore 會根據上面獲取到flags來恢復中斷;local_irq_enable 會無條件打開所有中斷。
在中斷中需要做一些工作,如果工作內容太多,必然導致中斷處理所需的時間過長;而中斷處理又要求能夠盡快完成,這樣才不會影響正常的系統調度,這兩個之間就產生了矛盾。
現在很多操作系統將中斷分為兩個部分來處理上面的矛盾:頂半部和底半部。
頂半部就是我們用request_irq來注冊的中斷處理函數,這個函數要求能夠盡快結束,同時在其中調度底半部,讓底半部在之後來進行後續的耗時工作。
頂半部就不再說明了,就是上面的中斷處理函數,只是要求能夠盡快處理完成並返回,不要處理耗時工作。
底半部通常使用tasklet或者工作隊列來實現。
tasklet的特點和注意事項:
工作隊列的特點和注意事項: