第八章 下半部和推後執行的工作

1. 中斷處理程序的侷限

  • 以異步方式執行,並且有可能打斷其他重要代碼。所以爲了避免被打斷的代碼的停止時間過長,中斷處理程序應該執行得越快越好

  • 如果當前有一個中斷處理程序在運行,在最好的情況下(如果IRQF_DISABLED沒有被設置),與該中斷同級的其他中斷會被屏蔽,最壞的情況下(設置IRQF_DISABLED),當前CPU上所有的其他中斷都會被屏蔽。縮短中斷被屏蔽的時間對系統的響應能力性能都至關重要,所以中斷處理程序執行得越快越好

  • 由於中斷處理程序往往需要對硬件進行操作,所以對它們通常有很強的時限要求

  • 中斷上下文不能阻塞,這限制了它們所做的事情

2. 劃分頂半部(中斷處理函數)和下半部任務的一些借鑑:

  • 如果一個任務對時間非常敏感,將其放在頂半部執行

  • 如果一個任務跟硬件相關,將其放在頂半部執行

  • 如果一個任務要保證不被其他中斷(特別是相同的中斷)打斷,將其放在中斷處理程序中執行

  • 其他所有任務,考慮放在下半部執行

3. 下半部執行的時機

  • 下半部並不需要指明一個確切時間,只要把這些任務推遲一點,讓他們在系統不太繁忙並且中斷恢復後執行。

  • 通常下半部在中斷處理程序一返回就會馬上執行。

  • 下半部執行的關鍵在於當它們運行的時候,允許響應所有的中斷

4. 三種下半部機制

    所有用於實現將工作推後執行的內核機制都被稱爲“下半部機制”。

  • 軟中斷(kernel/softirq.c)

    • 編譯期間靜態分配,有32個。

    • 一個軟中斷不會搶佔另一個軟中斷。唯一可以搶佔軟中斷的是中斷處理程序。其他的軟中斷(甚至是相同類型的軟中斷)可以在其他CPU上同時執行

    • 軟中斷處理程序執行的時候,允許響應中斷,但它自己不能休眠。

    • 在一個軟中斷處理程序運行的時候,當前CPU上的軟中斷被禁止,但其他CPU仍可以執行別的軟中斷

    • 如果同一個軟中斷在它被執行的同時再次被觸發了,那麼另一個CPU可以同時運行其處理程序。其中就涉及到共享數據的保護,爲了獲得更出色的性能,大部分軟中斷處理程序都通過單處理器數據或者其他一些技巧來避免顯示地加鎖

    • 執行時機、

      • 一個軟中斷在被標記後纔會執行,成爲觸發軟中斷。在中斷處理程序中觸發軟中斷是最常見的形式。

      • 從一個硬件中斷代碼處返回時

      • 在ksoftirqd內核線程中,每一個CPU都有這樣的線程,名字是ksoftirqd/n,n是CPU編號

      • 在那些顯示檢查和執行待處理的軟中斷的代碼中,如網絡子系統

  • tasklet

    • 基於軟中斷實現,可以動態創建和註銷,兩個不同的tasklet可以在不同的CPU上同時執行,但是同類型的tasklet不能同時執行

    • 因爲是基於軟中斷實現的,所以tasklet不能睡眠

    • 由於tasklet允許響應中斷,所以如果你的tasklet跟中斷處理函數之間共享了某些數據的話,可以屏蔽中斷然後獲得一個鎖。(屏蔽本地中斷,可以防止被當前CPU的中斷打斷,獲得鎖是爲了避免其他CPU在執行中斷處理函數的併發訪問)

    • 如果你的tasklet和其他的tasklet或者軟中斷共享了數據,必須使用適當的自旋鎖保護。(此時不用關閉中斷,因爲軟中斷不會被另一個軟中斷搶佔)

    • 在tasklet還沒有得到運行機會之前,如果有一個相同的tasklet又被調度了,那麼仍然只會運行一次。而如果已經在另一個CPU上開始運行了,那麼這個新的tasklet會被重新調度並再次運行。一般tasklet總在調度它的CPU上執行

  • 工作隊列 work queue

    • 可以把推後的工作交由一個內核線程去執行——這個下半部在進程上下文

    • 允許重新調度和睡眠

    • 缺省的工作者線程叫做 events/n,其中n是CPU的編號,每個CPU都對應一個線程

    • 工作隊列對應的處理函數雖然在進程上下文,但是不能訪問用戶空間,因爲內核線程在用戶空間沒有相關的內存映射。通常在發生系統調用時,內核纔會代表用戶空間的進程運行,此時它才能訪問用戶空間,也只有在此時他纔會映射用戶空間的內存

5. 內核定時器也可以一個將工作推後執行的機制

  • 內核定時器也是建立在軟中斷的基礎上的

6. 下半部上下文

下半部 頂半部 執行順序保障
軟中斷 中斷 沒有
tasklet 中斷 同類型不能同時執行
工作隊列 進程 沒有(和進程上下文一樣)

上面想說一下爲什麼軟中斷是在中斷上下文,我們知道軟中斷是在中斷處理函數返回後執行的,此時也沒用對應有task_struct。

7. 下半部加鎖

  • 如果進程上下文跟下半部共享數據,在訪問這些數據之前,需要禁止下半部的處理並得到鎖的使用權。以軟中斷爲例,進程上下文會可能會被當前CPU上的軟中斷打斷,導致併發,而禁止下半部只能做到禁止當前CPU的軟中斷。而使用自旋鎖的目的是,防止跟其他CPU上的軟中斷併發,而軟中斷中不能睡眠,故只能用自旋鎖

  • 如果中斷上下文和一個下半部共享數據,在訪問數據之前,你需要禁止中斷並得到鎖的使用權。以軟中斷爲例,因爲中斷會打斷下半部,導致併發,而禁止中斷只能影響到本地CPU,而其他CPU上的中斷並沒有禁止,故需要使用自旋鎖。

  • 任何在工作隊列中被共享的數據也需要使用鎖機制。其中有關鎖的要點和一般內核代碼中沒有區別,因爲工作隊列本來就是在進程上下文中執行的。

8. 禁止下半部(tasklet和軟中斷)

常見的做法:先得到一個鎖然後再禁止下半部

函數 描述
void local_bh_disable() 禁止本地CPU的軟中斷和tasklet的處理
void local_bh_enable() 激活本地CPU的軟中斷和tasklet的處理

如果調用了第一次調用local_bh_disable關閉了本地CPU的下半部,如果local_bh_disable又被調用三次,那麼當第四次調用local_bh_enable時,本地CPU的下半部處理才重新被激活

其實這兩個函數是通過修改preempt_count實現的,這個變量每個進程都有一個,存放在thread_info中,當這個變量爲0時,下半部才能夠被處理。

asm_do_IRQ

    ----> __handle_domain_irq

            ----> generic_handle_irq

            ----> irq_exit

  1. /*
  2. * Exit an interrupt context. Process softirqs if needed and possible:
  3. */
  4. void irq_exit(void)
  5. {
  6. #ifndef __ARCH_IRQ_EXIT_IRQS_DISABLED
  7. local_irq_disable();
  8. #else
  9. WARN_ON_ONCE(!irqs_disabled());
  10. #endif
  11. account_irq_exit_time(current);
  12. preempt_count_sub(HARDIRQ_OFFSET);
  13. if(!in_interrupt()&& local_softirq_pending())
  14. invoke_softirq();
  15. tick_irq_exit();
  16. rcu_irq_exit();
  17. trace_hardirq_exit();/* must be last! */
  18. }

 





posted @ 2016-09-04 16:02  摩斯电码  阅读(231)  评论(0编辑  收藏  举报