@[toc]
中斷上半部、下半部的概念裝置的中斷會打斷核心程序中的正常排程和執行,系統對更高吞吐率的追求勢必要求中斷服務程式儘量短小精悍。但是,這個良好的願望往往與現實並不吻合。在大多數真實的系統中,當中斷到來時,要完成的工作往往並不會是短小的,它可能要進行較大量的耗時處理。 下圖描述了Linux核心的中斷處理機制。為了在中斷執行時間儘量短和中斷處理需完成的工作儘量大之間找到一個平衡點,Linux將中斷處理程式分解為兩個半部:頂半部和底半部。
頂半部用於完成儘量少的比較緊急的功能,它往往只是簡單地讀取暫存器中的中斷狀態,並在清除中斷標誌後就進行“登記中斷”的工作。“登記中斷”意味著將底半部處理程式掛到該裝置的底半部執行佇列中去。這樣,頂半部執行的速度就會很快,從而可以服務更多的中斷請求。
現在,中斷處理工作的重心就落在了底半部的頭上,需用它來完成中斷事件的絕大多數任務。底半部幾乎做了中斷處理程式所有的事情,而且可以被新的中斷打斷,這也是底半部和頂半部的最大不同,因為頂半部往往被設計成不可中斷。底半部相對來說並不是非常緊急的,而且相對比較耗時,不在硬體中斷服務程式中執行。
儘管頂半部、底半部的結合能夠善系統的響應能力,但是,僵化地認為Linux裝置驅動中的中斷處理一定要分兩個半部則是不對的。如果中斷要處理的工作本身很少,則完全可以直接在頂半部全部完成。
其他作業系統中對中斷的處理也採用了類似於 Linux的方法,真正的硬體中斷服務程式都斥儘量短。因此,許多作業系統都提供了中斷上下文和非中斷上下文相結合的機制,將中斷的耗時工作保留到非中斷上下文去執行。
實現中斷下半部的三種方法軟中斷軟中斷( Softirq)也是一種傳統的底半部處理機制,它的執行時機通常是頂半部返回的時候, tasklet是基於軟中斷實現的,因此也運行於軟中斷上下文。
在Linux核心中,用 softing_action結構體表徵一個軟中斷,這個結構體包含軟中斷處理函式指標和傳遞給該函式的引數。使用 open_softirq()函式可以註冊軟中斷對應的處理函式,而 raise_softirq()函式可以觸發一個軟中斷。
軟中斷和 tasklet運行於軟中斷上下文,仍然屬於原子上下文的一種,而工作佇列則運行於程序上下文。因此,在軟中斷和 tasklet處理函式中不允許睡眠,而在工作佇列處理函式中允許睡眠。
local_bh_disable()和 llocal_bh_enable()是核心中用於禁止和使能軟中斷及 tasklet底半部機制的函式
軟中斷模版asmlinkage void do_softirq(void){ __u32 pending; unsigned long flags; /* 判斷是否在中斷處理中,如果正在中斷處理,就直接返回 */ if (in_interrupt()) return; /* 儲存當前暫存器的值 */ local_irq_save(flags); /* 取得當前已註冊軟中斷的點陣圖 */ pending = local_softirq_pending(); /* 迴圈處理所有已註冊的軟中斷 */ if (pending) __do_softirq(); /* 恢復暫存器的值到中斷處理前 */ local_irq_restore(flags);}
tasklet
tasklet的使用較簡單,它的執行上下文是軟中斷,執行時機通常是頂半部返回的時候。我們只需要定義 tasklet及其處理函式,並將兩者關聯則可,例如
void my_tasklet_func(unsigned long); /*定義一個處理函式*/DECLARE_TASKLET(my_tasklet, my_tasklet_func, data);/*定義一個tasklet結構my_tasklet,與my_tasklet_func(data)函式相關聯*/
程式碼DECLARE_TASKLET(my_tasklet,my_tasklet_func,data)實現了定義名稱為my_tasklet的tasklet,並將其與my_tasklet_func()這個函式繫結,而傳入這個函式的引數為data。在需要排程tasklet的時候引用一個tasklet_schedule()函式就能使系統在適當的時候進行排程執行:
tasklet_schedule(&my_tasklet);
使用tasklet作為底半部處理中斷的裝置驅動程式模板下所示(僅包含與中斷相關的部分)。
tasklet函式模版/* 定義tasklet和底半部函式並將它們關聯 */void xxx_do_tasklet(unsigned long);DECLARE_TASKLET(xxx_tasklet, xxx_do_tasklet, 0);/* 中斷處理底半部 */void xxx_do_tasklet(unsigned long).../* 中斷處理頂半部 */ irqreturn_t xxx_interrupt(int irq, void *dev_id){ ... tasklet_schedule(&xxx_tasklet); ...}/* 裝置驅動模組載入函式 */ int __init xxx_init(void){ ... /* 申請中斷 */ result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL); ... return IRQ_HANDLED;}/* 裝置驅動模組解除安裝函式 */ void __exit xxx_exit(void){ ... /* 釋放中斷 */ free_irq(xxx_irq, xxx_interrupt); ...}
上述程式在模組載入函式中申請中斷(第24~25行),並在模組解除安裝函式free_irq(xxx_irq, xxx_interrupt);中釋放它。對應於xxx_irq的中斷處理程式被設定為xxx_interrupt()函式,在這個函式中,tasklet_schedule(&xxx_tasklet)排程被定義的tasklet函式xxx_do_tasklet()在適當的時候執行。
工作佇列工作佇列的使用方法和tasklet非常相似,但是工作佇列的執行上下文是核心執行緒,因此可以排程和睡眠。下面的程式碼用於定義一個工作佇列和一個底半部執行函式
struct work_struct my_wq; /* 定義一個工作佇列 */void my_wq_func(struct work_struct *work); /* 定義一個處理函式 */
透過INIT_WORK()可以初始化這個工作佇列並將工作佇列與處理函式繫結:
INIT_WORK(&my_wq, my_wq_func);/* 初始化工作佇列並將其與處理函式繫結 */
與tasklet_schedule()對應的用於排程工作佇列執行的函式為schedule_work(),如:
schedule_work(&my_wq); /* 排程工作佇列執行 */
工作佇列函式模版/* 定義工作佇列和關聯函式 */struct work_struct xxx_wq;void xxx_do_work(struct work_struct *work);/* 中斷處理底半部 */void xxx_do_work(struct work_struct *work).../*中斷處理頂半部*/ irqreturn_t xxx_interrupt(int irq, void *dev_id){ ... schedule_work(&xxx_wq); ... return IRQ_HANDLED;}/* 裝置驅動模組載入函式 */ int xxx_init(void){ ... /* 申請中斷 */ result = request_irq(xxx_irq, xxx_interrupt, 0, "xxx", NULL); ... /* 初始化工作佇列 */ INIT_WORK(&xxx_wq, xxx_do_work); ...}/* 裝置驅動模組解除安裝函式 */ void xxx_exit(void){ ... /* 釋放中斷 */ free_irq(xxx_irq, xxx_interrupt); ...}
工作佇列早期的實現是在每個CPU核上建立一個worker核心執行緒,所有在這個核上排程的工作都在該worker執行緒中執行,其併發性顯然差強人意。在Linux 2.6.36以後,轉而實現“Concurrency-managedworkqueues”,簡稱cmwq,cmwq會自動維護工作佇列的執行緒池以提高併發性,同時保持了API的向後相容。
軟中斷和硬中斷的區別硬中斷:
1. 硬中斷是由硬體產生的,比如,像磁碟,網絡卡,鍵盤,時鐘等。每個裝置或裝置集都有它自己的IRQ(中斷請求)。基於IRQ,CPU可以將相應的請求分發到對應的硬體驅動上(注:硬體驅動通常是核心中的一個子程式,而不是一個獨立的程序)。
2. 處理中斷的驅動是需要執行在CPU上的,因此,當中斷產生的時候,CPU會中斷當前正在執行的任務,來處理中斷。在有多核心的系統上,一箇中斷通常只能中斷一顆CPU(也有一種特殊的情況,就是在大型主機上是有硬體通道的,它可以在沒有主CPU的支援下,可以同時處理多箇中斷。)。
3. 硬中斷可以直接中斷CPU。它會引起核心中相關的程式碼被觸發。對於那些需要花費一些時間去處理的程序,中斷程式碼本身也可以被其他的硬中斷中斷。
4. 對於時鐘中斷,核心排程程式碼會將當前正在執行的程序掛起,從而讓其他的程序來執行。它的存在是為了讓排程程式碼(或稱為排程器)可以排程多工。
軟中斷:
1. 軟中斷的處理非常像硬中斷。然而,它們僅僅是由當前正在執行的程序所產生的。
2. 通常,軟中斷是一些對I/O的請求。這些請求會呼叫核心中可以排程I/O發生的程式。對於某些裝置,I/O請求需要被立即處理,而磁碟I/O請求通常可以排隊並且可以稍後處理。根據I/O模型的不同,程序或許會被掛起直到I/O完成,此時核心排程器就會選擇另一個程序去執行。I/O可以在程序之間產生並且排程過程通常和磁碟I/O的方式是相同。
3. 軟中斷僅與核心相聯絡。而核心主要負責對需要執行的任何其他的程序進行排程。一些核心允許裝置驅動的一些部分存在於使用者空間,並且當需要的時候核心也會排程這個程序去執行。
4. 軟中斷並不會直接中斷CPU。也只有當前正在執行的程式碼(或程序)才會產生軟中斷。這種中斷是一種需要核心為正在執行的程序去做一些事情(通常為I/O)的請求。有一個特殊的軟中斷是Yield呼叫,它的作用是請求核心排程器去檢視是否有一些其他的程序可以執行。
硬中斷、軟中斷和訊號的區別硬中斷是外部裝置對CPU的中斷,軟中斷是中斷底半部的一種處理機制,而訊號則是由核心(或其他程序)對某個程序的中斷。在涉及系統呼叫的場合,人們也常說透過軟中斷(例如ARM為swi)陷入核心,此時軟中斷的概念是指由軟體指令引發的中斷,和我們這個地方說的softirq是兩個完全不同的概念,一個是software,一個是soft。 需要特別說明的是,軟中斷以及基於軟中斷的tasklet如果在某段時間內大量出現的話,核心會把後續軟中斷放入ksoftirqd核心執行緒中執行。總的來說,中斷優先順序高於軟中斷,軟中斷又高於任何一個執行緒。軟中斷適度執行緒化,可以緩解高負載情況下系統的響應。