一、中斷處理機制
中斷是一個隨機事件,因此如果關中斷的時間過長,CPU就不能及時的響應其他的中斷請求,從而造成中斷的丟失。因此,Linux內核的目標就是儘可能快的處理完中斷請求,儘可能的把更多的處理向後推遲。因此,內核把中斷分爲了兩部分:上半部和下半部,上半部(就是中斷服務程序),下半部(就是一些內核函數)留着稍後處理。首先,一個快速的”上半部”來處理硬件發出的請求,它必須在一個新的中斷產生之前終止。其次,“下半部”運行時是允許中斷請求的,而上半部運行時是關中斷的。這是二者的主要區別。
1.中斷“下半部”
內核到底什麼時候執行“下半部”,以何種方式組織“下半部”?
下半部實現機制中,在老的內核中,這個機制叫做bottom-half(BH),但是linux的這種BH機制有兩個缺點:
(1) 在任一時刻,系統中只能有一個CPU可以執行BH代碼,以防止兩個或多個CPU同時來執行BH函數而相互干擾,因此BH代碼的執行是嚴格“串行化的”
(2) BH函數不允許嵌套
而2.6內核以後,“下半部”機制處理機制有了很大的發展和改進:
(1) 軟中斷請求機制
(2) 小任務機制
(3) 工作隊列機制
2.軟中斷
整個softirq機制的設計與實現中自始自終貫徹了一個思想:“誰觸發,誰執行”,即觸發軟中斷的那個CPU負責執行它所觸發的中斷,而且每個CPU都有它自己的軟中斷觸發與控制機制。
2.小任務機制
tasklet機制是一種較爲特殊的軟中斷。
特點:
(1)與一般的軟中斷不同,某一段tasklet代碼在某個時刻只能在一個CPU上運行,而不像一般的軟中斷服務函數(即softirq_action結構體中的action函數指針)那樣------在同一時刻可以被多個CPU併發地執行。
(2)與BH機制不同,不同的tasklet代碼在同一時刻可以在多個CPU上併發地執行,而不像BH機制那樣必須嚴格地串行化執行。
linux中用tasklet_struct來描述一個tasklet,每個結構體代表一個獨立的小任務
注:tasklet_struct結構體中atomic_t count成員表示對這個tasklet的引用計數值,只有當count爲0時,tasklet代碼段才能執行,如果count非零,則這個tasklet是被禁止的。任何想要執行一個tasklet代碼段的人都首先必須檢查其count成員是否爲0.
linux在interrupt.h頭文件中定義了兩個宏來定義tasklet_struct 結構變量的輔助宏:
DECLARE_TASKLET(name,func,data)和DECLARE_TASKLET_DISABLED(name,func,data),顯然,DECLARE_TASKLET宏在初始化時是被使能的,因爲其count爲0,而DECLARE_TASKLET_DISABLED是被禁止的,因爲其count爲1.
小任務機制總結:
(1) 聲明和使用小任務大多數情況下,爲了控制一個常用的硬件設備,小任務機制是實現下半部的最佳選擇。小任務可以動態創建,使用方便,執行起來也比較快,也可以靜態創建。選擇哪種方式取決於到底是想對小任務進行直接引用還是間接引用,如果準備靜態地創建一個小任務(直接引用),使用下面兩個宏中的一個:
DECLARE_TASKLET(name,func,data)
DECLARE_TASKLET_DISABLED(name,func,data)
(2) 編寫自己的小任務處理程序,小任務處理程序必須符合如下的函數類型:
void tasklet_handler(unsigned long data)
由於小任務不能睡眠,因此不能在小任務中使用信號量或者其他產生阻塞的函數。但是小任務運行時可以響應中斷
(3) 調度自己的小任務通過調用tasklet_schedule()函數
3.工作隊列
工作隊列是另外一種將工作推後執行的形式,它和tasklet有所不同。工作隊列可以把工作推後,交由一個內核線程去執行,也就是說這個下半部可以在進程的上下文中執行。這樣,通過工作隊列執行的代碼能佔盡進程上下文的所有優勢。最重要的是工作隊列允許被重新調度甚至是睡眠。
那麼,什麼情況下使用工作隊列,什麼情況下使用tasklet呢?
如果推後執行的任務需要睡眠,那麼就選擇工作隊列;
如果推後執行的任務不需要睡眠,那麼就選擇tasklet。
另外,如果需要用一個可以重新調度的實體來執行你的下半部處理,也可以使用工作隊列。它是唯一能在進程上下文運行的下半部實現的機制,也只有它纔可以睡眠。這意味着在需要獲得大量的內存時、在需要獲取信號量時,在需要執行阻塞式的IO操作時,它都會非常有用。如果不需要用一個內核線程來推後執行工作,那麼就考慮使用tasklet。
我們把推後執行的任務叫做工作,描述工作的結構體是work_struct,這些工作以隊列結構組織成工作隊列,描述工作隊列的結構體爲workqueue_struct,而工作線程就是負責執行工作隊列中的工作,系統默認的工作線程是events,自己也可以創建自己的工作者線程。
二、工作隊列詳解
Linux中workqueue機制
爲什麼需要workqueue?Linux中的workqueue機制就是爲了簡化內核線程的創建。通過調用workqueue的接口就能創建內核線程。並且可以根據當前系統CPU的個數創建線程的數量,使得線程處理的事務能夠並行化。
Linux內核創建工作隊列時:
__alloc_workqueue_key函數會調用kthread_create來進行線程的創建
這個線程以wq爲數據執行rescuer_thread這個函數。
觸摸屏驅動工作隊列:
沒有使用系統默認的工作隊列keventd_wq(這個工作隊列是在Linux系統初始化的時候就創建的),因爲默認的工作隊列存在性能問題。
在初始化workqueue過程中,內核需要初始化內核線程來處理工作隊列,註冊的內核線程工作比較簡單,就是不斷的掃描對應的cpu_workqueue_stuct中的任務隊列,從中獲取一個有效任務,然後執行該任務。
內核線程的創建調用create_singlethread_workqueue(name)來完成,該函數會創建一個工作隊列,並且在內部創建了一個內核線程。
工作隊列的創建方式有兩種,如下圖所示:
第二種方式中,會創建自己的工作隊列,優點是性能高,會創建內核線程來處理工作函數。