重慶分公司,新征程啟航
為企業提供網站建設、域名注冊、服務器等服務
為企業提供網站建設、域名注冊、服務器等服務
1、中斷處理程序以異步的方式執行,并且它有可能會打斷其他重要代碼的執行。
2、如果當前有一個中斷處理程序正在執行,最好的情況是,與該中斷同級的其他中斷會被屏蔽,最壞情況下,當前處理器上所有其他中斷都會被屏蔽。
3、中斷處理程序往往需要對硬件進行操作,所以他們不能阻塞。這限制了他們所作的事情。
8.1 下半部
下半部的任務就是執行與中斷處理密切相關但中斷處理程序本身不執行的工作。中斷處理程序注定要完成一部分工作:幾乎都需要通過操作硬件對中斷的到達進行確認,有時他還會從硬件拷貝數據。
8.1.2 下半部的環境
3、軟中斷和tasklet
軟中斷是一組靜態定義的下半部接口,有32個,可以在所有處理器上同時執行——及時兩個類型相同也可以。
tasklet:兩個不同類型的tasklet可以在不同的處理器上同時執行,但類型相同的tasklet不能同時執行。軟中斷必須在靜態編譯期間就進行注冊。如此相反,tasklet可以通過代碼動態注冊。
內核定時器把操作推遲到某個確定的時間段之后執行。
8.2 軟中斷
8.2.1 軟中斷的實現
軟中斷是在編譯期間靜態分配的。它不像tasklet那樣能被動態的注冊或注銷。軟中斷由softirq_action結構表示。
一個軟中斷不會搶占另外一個軟中斷。唯一可以搶占軟中斷的是中斷處理程序。其他的軟中斷甚至是相同類型的軟中斷可以在其他處理器上同時執行。
在下列地方,待處理的軟中斷會被檢查和執行:
1、從一個硬件中斷代碼處返回時
2、在ksoftirqd內核線程中
3、在那些顯示檢查和執行待處理的軟中斷代碼中,如網絡子系統中
不管用什么辦法喚起,軟中斷都要在do_softirq()中執行。
8.2.2 使用軟中斷
軟中斷保留給系統中對時間要求最嚴格以及最重要的下半部使用。目前,只有兩個子系統(網絡和SCSI)直接使用軟中斷。此外,內核定時器和tasklet都是簡歷在軟中斷上的。軟中斷處理程序執行時,允許響應中斷,但它自己不能休眠。在一個處理程序運行的時候,當前處理器上的軟中斷被禁止。但其他處理器仍可以執行別的軟中斷。如果同一個軟中斷在它被執行的同時再次觸發了,那么另一個處理器可以同時運行這個處理程序。這意味著任何共享數據都需要嚴格保護。因此,大部分軟中斷處理沖虛,都通過采取單處理器數據或其他一些技巧來避免枷鎖。
raise_softirq()函數可以講一個軟中斷設置為掛起狀態,讓他在下次調用do_softirq()的時候投入運行。
在中斷處理程序中觸發軟中斷是最常見的形式。在這種情況下,中斷處理程序執行硬件設備的相關操作,然后觸發相應的軟中斷,最后推出。內核在執行完中斷處理程序后會馬上執行do_softirq()函數。
8.3 tasklet
8.3.1
因為tasklet是通過軟中斷實現的,所以其本身也是軟中斷。tasklt有兩類軟中斷代表:HI_SOFTIRQ和TASKLET_SOFTIRQ。HI_SOFTIRQ優先執行。
8.3.2 使用tasklet
1、聲明tasklet
靜態:DECLARE_TASKLET(name,func,data);tasklet處于激活狀態
DELCARE_TASKLET_DISABLE(name,func,data);tasklet禁止狀態
動態:struct tasklet_struct my_tasklet={null,0,ATOMIC_INIT(0),my_tasklet_handler,dev)
tasklet_init(t,tasklet_handler,dev);
2、編寫tasklet處理程序
void tasklet_handler(unsigned long data)
因為是靠軟中斷實現,所以tasklet不能睡眠。不能使用阻塞式函數,由于tasklet運行時允許響應中斷,所以必須做好預防工作。
3、調度tasklet
tasklet_schedule(&my_tasklet);
在tasklet被調度后,只要有機會就會盡可能的執行。在它還沒有得到運行機會之前,如果有一個相同的tasklet又被調度了,那么它仍然只能運行一次。作為一種優化措施,一個tasklet總在調度它的處理器上運行——更好的利用處理器緩存。
tasklet_disable();
tasklet_disable_nosync();
tasklet_enable();
tasklet_kill();從掛起的隊列中去掉一個tasklet。如果tasklet正在執行,該函數等待其執行完畢后,從隊列中去掉tasklet。
4、ksoftirqd
內核不會立即處理重新觸發的軟中斷。當大量軟中斷出現后,內核會喚醒一組內核線程來處理這些負載。這些線程在最低優先級上運行(nice 19),這能避免與其他重要的任務搶奪資源。但他們最終肯定會執行,所以這個方案能夠保證在軟中斷負擔很重的時候,用戶程序不會因為得不到處理器時間而處于饑餓狀態。相應的,也能保證“過量”的軟中斷終究會得到處理。在空閑系統上,這個方案表現良好,軟中斷處理的非常迅速(因為內核線程會馬上調度)。
8.4 工作隊列
工作隊列可以把工作推后,交由一個內核線程去執行——這個下半部分總是會在進程上下文中執行。工作隊列允許重新調度和睡眠。
8.4.1 工作隊列的實現
工作隊列子系統是一個用于創建內核線程的接口,通過它創建的進程負責執行由其他內核其他部分排到隊列中的人物。它創建的這些內核線程稱作工作者線程。工作隊列子系統提供了一個缺省的工作者線程來處理這些工作。
8.4.2 使用工作隊列
1、創建推后的工作
DECLARE_WORK(name,void(*func)(void*),void *data);
INIT_WORK(struct work_struct *work,void(*func)(void *),void *data);
2、工作隊列處理函數
void work_handler(void *data)
這個函數會由一個工作者線程執行,因此,函數會運行在進程上下文中。默認情況下,允許響應中斷,并且不持有任何鎖。如果需要,函數可以睡眠。需要注意的是,盡管操作處理函數運行在進程上下文中,但它不能訪問用戶空間,因為內核線程在用戶空間沒有相關的內存映射。通常發生在系統調用時,內核會代表用戶空間的進程運行,此時它才能訪問用戶空間,也只有在此時它才會映射用戶空間的內存。
3、對工作進行調度
想要把給定的工作的處理函數提交給缺省的events工作者線程:
schedule_work(&work);
schedule_delayed_work(&work);
4、刷新操作
void flush_scheduled_work(void);
函數會一直等待,知道隊列中所有對象都被執行以后才返回。在等待所有待處理的工作執行的時候,該函數會進入休眠狀態,所以只能在進程上下文使用它。
int cancel_delayed_wrok(struct work_struct *work);
5、創建新的工作隊列
struct workqueue_struct *create_workqueue(const char *name);
int queue_work(struct workqueue_struct *wq,struct work_struct *work);
int queue_delayed_work(struct wrokqueue_struct *wq,struct work_struct *work,unsigned long delay);
flush_workqueue(struct workqueue_struct *wq);
8.5 下半部機制的選擇
8.6 在下半部之間加鎖
使用tasklet的好處在于,他自己負責執行的序列化保障:兩個相同的tasklet不允許同時執行,即使在不同的處理器上也不行。
8.7 禁止下半部
一般的單純禁止下半部是不夠的,為了保證共享數據的安全,更常見的做法是,先得到一個鎖然后再禁止下半部的處理。驅動程序中通常使用的都是這種方法。
local_bh_disable();函數通過preempt_count(內核搶占的時候使用的也是它)為每一個進程維護一個計數器。當計數器變為0時,下半部才能夠被處理。