軟中斷/tasklet/工作隊列

軟中斷、tasklet和工作隊列並不是Linux內核中一直存在的機制,而是由更早版本的內核中的“下半部”(bottom half)演變而來。下半部的機制實際上包括五種,但2.6版本的內核中,下半部和任務隊列的函數都消失了,只剩下了前三者。本文重點在於介紹這三者之間的關係。(函數細節將不會在本文中出現,可以參考文獻,點這裏

 

(1)上半部和下半部的區別
上半部指的是中斷處理程序,下半部則指的是一些雖然與中斷有相關性但是可以延後執行的任務。舉個例子:在網絡傳輸中,網卡接收到數據包這個事件不一定需要馬上被處理,適合用下半部去實現;但是用戶敲擊鍵盤這樣的事件就必須馬上被響應,應該用中斷實現。
兩者的主要區別在於:中斷不能被相同類型的中斷打斷,而下半部依然可以被中斷打斷;中斷對於時間非常敏感,而下半部基本上都是一些可以延遲的工作。由於二者的這種區別,所以對於一個工作是放在上半部還是放在下半部去執行,可以參考下面四條:
a)如果一個任務對時間非常敏感,將其放在中斷處理程序中執行。
b)如果一個任務和硬件相關,將其放在中斷處理程序中執行。
c)如果一個任務要保證不被其他中斷(特別是相同的中斷)打斷,將其放在中斷處理程序中執行。
d)其他所有任務,考慮放在下半部去執行。

 

(2)爲什麼要使用軟中斷?
軟中斷作爲下半部機制的代表,是隨着SMP(share memory processor)的出現應運而生的,它也是tasklet實現的基礎(tasklet實際上只是在軟中斷的基礎上添加了一定的機制)。軟中斷一般是“可延遲函數”的總稱,有時候也包括了tasklet(請讀者在遇到的時候根據上下文推斷是否包含tasklet)。它的出現就是因爲要滿足上面所提出的上半部和下半部的區別,使得對時間不敏感的任務延後執行,而且可以在多個CPU上並行執行,使得總的系統效率可以更高。它的特性包括:
a)產生後並不是馬上可以執行,必須要等待內核的調度才能執行。軟中斷不能被自己打斷,只能被硬件中斷打斷(上半部)。
b)可以併發運行在多個CPU上(即使同一類型的也可以)。所以軟中斷必須設計爲可重入的函數(允許多個CPU同時操作),因此也需要使用自旋鎖來保護其數據結構。

 

(3)爲什麼要使用tasklet?(tasklet和軟中斷的區別)
由於軟中斷必須使用可重入函數,這就導致設計上的複雜度變高,作爲設備驅動程序的開發者來說,增加了負擔。而如果某種應用並不需要在多個CPU上並行執行,那麼軟中斷其實是沒有必要的。因此誕生了彌補以上兩個要求的tasklet。它具有以下特性:
a)一種特定類型的tasklet只能運行在一個CPU上,不能並行,只能串行執行。
b)多個不同類型的tasklet可以並行在多個CPU上。
c)軟中斷是靜態分配的,在內核編譯好之後,就不能改變。但tasklet就靈活許多,可以在運行時改變(比如添加模塊時)。
tasklet是在兩種軟中斷類型的基礎上實現的,因此如果不需要軟中斷的並行特性,tasklet就是最好的選擇。

 

(4)爲什麼要使用工作隊列work queue?(work queue和軟中斷的區別)
上面我們介紹的可延遲函數運行在中斷上下文中(軟中斷的一個檢查點就是do_IRQ退出的時候),於是導致了一些問題:軟中斷不能睡眠、不能阻塞。由於中斷上下文出於內核態,沒有進程切換,所以如果軟中斷一旦睡眠或者阻塞,將無法退出這種狀態,導致內核會整個僵死。但可阻塞函數不能用在中斷上下文中實現,必須要運行在進程上下文中,例如訪問磁盤數據塊的函數。因此,可阻塞函數不能用軟中斷來實現。但是它們往往又具有可延遲的特性。
因此在2.6版的內核中出現了在內核態運行的工作隊列(替代了2.4內核中的任務隊列)。它也具有一些可延遲函數的特點(需要被激活和延後執行),但是能夠能夠在不同的進程間切換,以完成不同的工作。

 

對於softirq,linux kernel中是在中斷處理程序執行的,具體的路徑爲:

  1. do_IRQ() --> irq_exit() --> invoke_softirq() --> do_softirq() --> __do_softirq()


在__do_softirq()中有這麼一段代碼:

  1.         do {
  2.                 if (pending & 1) {
  3.                         h->action(h);
  4.                         rcu_bh_qsctr_inc(cpu);
  5.                 }
  6.                 h++;
  7.                 pending >>= 1;
  8.         } while (pending);
複製代碼


你看,這裏就是對softirq進行處理了,因爲pengding是一個__u32的類型,所以每一位都對應了一種softirq,正好是32種(linux kernel中實際上只使用了前6種 ).
h->action(h),就是運行softirq的處理函數。

對於tasklet,前面已經說了,是一種特殊的softirq,具體就是第0和第5種softirq,所以說tasklet是基於softirq來實現的。
tasklet既然對應第0和第5種softirq,那麼就應該有對應的處理函數,以便h->action()會運行tasklet的處理函數。
我們看代碼:

  1. softirq.c
  2. void __init softirq_init(void)  
  3. {
  4.         open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
  5.         open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
  6. }
複製代碼


這裏註冊了兩種tasklet所在的softirq的處理函數,分別對應高優先級的tasklet和低優先級的tasklet。

我們看低優先級的吧(高優先級的也一樣)。

  1. static void tasklet_action(struct softirq_action *a)
  2. {
  3.         struct tasklet_struct *list;
  4.         local_irq_disable();
  5.         list = __get_cpu_var(tasklet_vec).list;
  6.         __get_cpu_var(tasklet_vec).list = NULL;
  7.         local_irq_enable();
  8.         while (list) {
  9.                 struct tasklet_struct *t = list;
  10.                 list = list->next;
  11.                 if (tasklet_trylock(t)) {
  12.                         if (!atomic_read(&t->count)) {
  13.                                 if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
  14.                                         BUG();
  15.                                 t->func(t->data);
  16.                                 tasklet_unlock(t);
  17.                                 continue;
  18.                         }
  19.                         tasklet_unlock(t);
  20.                 }
  21.                 local_irq_disable();
  22.                 t->next = __get_cpu_var(tasklet_vec).list;
  23.                 __get_cpu_var(tasklet_vec).list = t;
  24.                 __raise_softirq_irqoff(TASKLET_SOFTIRQ);  
  25.                 local_irq_enable();
  26.         }
  27. }
複製代碼


你看,在運行softirq的處理時(__do_softirq),對於

  1.         do {
  2.                 if (pending & 1) {
  3.                         h->action(h);
  4.                         rcu_bh_qsctr_inc(cpu);
  5.                 }
  6.                 h++;
  7.                 pending >>= 1;
  8.         } while (pending);
複製代碼


如果tasklet有任務需要處理,會運行到h->action(),這個函數指針就會指向tasklet_action(),然後在tasklet_action()裏再去執行tasklet對應的各個任務,這些任務都是掛在一個全局鏈表裏面的,具體的代碼這裏就不分析了。

另外, softirq在smp中是可能被同時運行的,所以softirq的處理函數必須被編寫成可重入的函數。
但tasklet是不會在多個cpu之中同時運行的,所以tasklet的處理函數可以編寫成不可重入的函數,這樣就減輕了編程人員的負擔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章