一、中斷下半部-工作隊列
1、中斷
先看一下宋寶華先生的《Linux設備驅動開發詳解》里面對中斷的描述吧。這本書個人感覺 寫的比較好,從開始學驅動到現在,還能從中得到不少知識。
設備的中斷會打斷內核中進程的正常調度和運行,系統對更高吞吐率的追求勢必要求中斷服務程序盡可能地短小精悍。但是,這個良好的願望往往與現實並不吻合。 在大多數真實的系統中,當中斷到來時,要完成的工作往往並不會是短小的,它可能要進行較大量的耗時處理。如下圖描述了Linux內核的中斷 處理機制。為了在中斷執行時間盡可能短和中斷處理需完成大量工作之間找到一個平衡點,Linux將中斷處理程序分解為兩個半部:頂半部(top half)和底半部(bottom half)。頂半部完成盡可能少的比較緊急的功能,它往往只是簡單地讀取寄存器中的中斷狀態並清除中斷標志后就進行“登記中斷”的工作。“登記中斷”意味 着將底半部處理程序掛到該設備的底半部執行隊列中去。這樣,頂半部執行的速度就會很快,可以服務更多的中斷請求。現在,中斷處理工作的重心就落在了底半部 的頭上,它來完成中斷事件的絕大多數任務。底半部幾乎做了中斷處理程序所有的事情,而且可以被新的中斷打斷,這也是底半部和頂半部的最大不同,因為頂半部 往往被設計成不可中斷。底半部則相對來說並不是非常緊急的,而且相對比較耗時,不在硬件中斷服務程序中執行。盡管頂半部、底半部的結合能夠改善系統的響應 能力,但是,僵化地認為Linux設備驅動中的中斷處理一定要分兩個半部則是不對的。如果中斷要處理的工作本身很少,則完全可以直接在頂半部全部完成。
其實上面這一段大致說明一個問題,那就是:中斷要盡可能耗時比較短,盡快恢復系統正常調試,所以把中斷觸發、中斷執行分開,也就是所說的“上半部分(中斷觸發)、底半部(中斷執行)”,其實就是我們后面說的中斷上下文。下半部分一般有tasklet、工作隊列實現,觸摸屏中中斷實現以工作隊列形式實現的,所以我們今天重點講工作隊列。
2、為什么還需要工作隊列?
工作隊列(work queue) 是另外一種將中斷的部分工作推后的一種方式,它可以實現一些tasklet不能實現的工作,比如工作隊列機制可以睡眠。這種差異的本質原因是,在工作隊列 機制中,將推后的工作交給一個稱之為工作者線程(worker thread)的內核線程去完成(單核下一般會交給默認的線程events/0)。因此,在該機制中,當內核在執行中斷的剩余工作時就處在進程上下文 (process context)中。也就是說由工作隊列所執行的中斷代碼會表現出進程的一些特性,最典型的就是可以重新調度甚至睡眠。
對於tasklet機 制(中斷處理程序也是如此),內核在執行時處於中斷上下文(interrupt context)中。而中斷上下文與進程毫無瓜葛,所以在中斷上下文中就不能睡眠。因此,選擇tasklet還是工作隊列來完成下半部分應該不難選擇。當 推后的那部分中斷程序需要睡眠時,工作隊列毫無疑問是你的最佳選擇;否則,還是用tasklet吧。
3、中斷上下文
有上面那個圖可以看出上下兩部分吧,就是上下文吧,這個比較好理解。
看下別人比較專業的解釋:
在了解中斷上下文時,先來回顧另一個熟悉概念:進程上下文(這個中文翻譯真的不是很好理解,用“環 境”比它好很多)。一般的進程運行在用戶態,如果這個進程進行了系統調用,那么此時用戶空間中的程序就進入了內核空間,並且稱內核代表該進程運行於內核空 間中。由於用戶空間和內核空間具有不同的地址映射,並且用戶空間的進程要傳遞很多變量、參數給內核,內核也要保存用戶進程的一些寄存器、變量等,以便系統 調用結束后回到用戶空間繼續執行。這樣就產生了進程上下文。
所謂的進程上下文,就是一個進程在執行的時候,CPU的 所有寄存器中的值、進程的狀態以及堆棧中的內容。當內核需要切換到另一個進程時(上下文切換),它需要保存當前進程的所有狀態,即保存當前進程的進程上下 文,以便再次執行該進程時,能夠恢復切換時的狀態繼續執行。上述所說的工作隊列所要做的工作都交給工作者線程來處理,因此它可以表現出進程的一些特性,比 如說可以睡眠等。 對於中斷而言,是硬件通過觸發信號,導致內核調用中斷處理程序,進入內核空間。這個過程中,硬件的一些變量 和參數也要傳遞給內核,內核通過這些參數進行中斷處理,中斷上下文就可以理解為硬件傳遞過來的這些參數和內核需要保存的一些環境,主要是被中斷的進程的環 境。因此處於中斷上下文的tasklet不會有睡眠這樣的特性。
4、工作隊列的使用方法
內核中通過下述結構體來表示一個具體的工作:
- struct long
- struct
- voidvoid
- void
- void
- struct
- };
而這些工作(結構體)鏈接成的鏈表就是所謂的工作隊列。工作者線程會在被喚醒時執行鏈表上的所有工作,當一個工作被執行完畢后,相應的work_struct結構體也會被刪除。當這個工作鏈表上沒有工作時,工作線程就會休眠。
(1)、通過如下宏可以創建一個要推后的完成的工作:
- DECLARE_WORK(name,voidvoidvoid*data);
(2)、也可以通過下述宏動態創建一個工作:
- INIT_WORK(structvoidvoidvoid *data);
與tasklet類似,每個工作都有具體的工作隊列處理函數,原型如下:
- voidvoid *data)
將工作隊列機制對應到具體的中斷程序中,即那些被推后的工作將會在func所指向的那個工作隊列處理函數中被執行。
(3)、實現了工作隊列處理函數后,就需要schedule_work函數對這個工作進行調度,就像這樣:
- schedule_work(&work);
這樣work會馬上就被調度,一旦工作線程被喚醒,這個工作就會被執行(因為其所在工作隊列會被執行)。