@併發
設備的中斷會打斷內核進程中的正常調度和運行,系統對更高吞吐率的追求勢必要求中斷服務程序儘可能短小精悍。可是,這個良好的願望每每與現實並不吻合。在大多數真實的系統中,當中斷到來時,要完成的工做每每並不會是短小的,它可能要進行較大量的耗時處理。
下圖描述了Linux內核的中斷處理機制。爲了在中斷執行時間儘可能短和中斷處理需完成的工做盡可能大之間找到一個平衡點,Linux將中斷處理程序分解爲兩個半部:頂半部和底半部。
函數
頂半部用於完成儘可能少的比較緊急的功能,它每每只是簡單地讀取寄存器中的中斷狀態,並在清除中斷標誌後就進行「登記中斷」的工做。「登記中斷」意味着將底半部處理程序掛到該設備的底半部執行隊列中去。這樣,頂半部執行的速度就會很快,從而能夠服務更多的中斷請求。高併發
如今,中斷處理工做的重心就落在了底半部的頭上,需用它來完成中斷事件的絕大多數任務。底半部幾乎作了中斷處理程序全部的事情,並且能夠被新的中斷打斷,這也是底半部和頂半部的最大不一樣,由於頂半部每每被設計成不可中斷。底半部相對來講並非很是緊急的,並且相對比較耗時,不在硬件中斷服務程序中執行。操作系統
儘管頂半部、底半部的結合可以善系統的響應能力,可是,僵化地認爲Linux設備驅動中的中斷處理必定要分兩個半部則是不對的。若是中斷要處理的工做自己不多,則徹底能夠直接在頂半部所有完成。.net
其餘操做系統中對中斷的處理也採用了相似於 Linux的方法,真正的硬件中斷服務程序都斥儘可能短。所以,許多操做系統都提供了中斷上下文和非中斷上下文相結合的機制,將中斷的耗時工做保留到非中斷上下文去執行。線程
軟中斷( Softirq)也是一種傳統的底半部處理機制,它的執行時機一般是頂半部返回的時候, tasklet是基於軟中斷實現的,所以也運行於軟中斷上下文。設計
在Linux內核中,用 softing_action結構體表徵一個軟中斷,這個結構體包含軟中斷處理函數指針和傳遞給該函數的參數。使用 open_softirq()函數能夠註冊軟中斷對應的處理函數,而 raise_softirq()函數能夠觸發一個軟中斷。指針
軟中斷和 tasklet運行於軟中斷上下文,仍然屬於原子上下文的一種,而工做隊列則運行於進程上下文。所以,在軟中斷和 tasklet處理函數中不容許睡眠,而在工做隊列處理函數中容許睡眠。rest
local_bh_disable()和 llocal_bh_enable()是內核中用於禁止和使能軟中斷及 tasklet底半部機制的函數code
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及其處理函數,並將二者關聯則可,例如
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和底半部函數並將它們關聯 */ 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內核線程中執行。總的來講,中斷優先級高於軟中斷,軟中斷又高於任何一個線程。軟中斷適度線程化,能夠緩解高負載狀況下系統的響應。
有任何問題,都可經過公告中的二維碼聯繫我