ARM linux hard soft irq

今天在ChinaUnix論壇內核源碼版上與linuxfellow網友討論到hardirq和softirq的問題,雖 然在《深入Linux設備驅動程序內核機制》(以下簡稱“ILDD”)第5章“中斷處理”對此已有詳細的解讀,但是我覺得還是有必要再花點時間深入探討一 下這兩者的區別。因爲此前關於ARM上的中斷處理我已經在另一篇帖子解密ARM based Linux內核中斷處理框架 中討論過,所以下面的討論只限於x86 32位系統。

首先給出一個ILDD書中的一個插圖,關於中斷處理的整體框架:
 



(圖1)

接下來的一個插圖顯示了內核用於標識中斷上下文(in_interrupt())的變量preempt_count的佈局:


(圖2)

按照x86處理器在外部中斷髮生時的硬件邏輯,在do_IRQ被調用時,處理器已經屏蔽了對外部中斷的響應。在圖中我們看 到中斷的處理大體上被分成兩部分HARDIRQ和SOFTIRQ,對應到代碼層面,do_IRQ()中調用irq_enter函數可以看做hardirq 部分的開始,而irq_exit函數的調用則標誌着softirq部分的開始:

<arch/x86/kernel/irq.c>
 

  1. unsigned int __irq_entry do_IRQ(struct pt_regs *regs)
  2. {
  3.         ...
  4.         irq_enter();

  5.         //handle external interrupt (ISR)
  6.         ...
  7.         irq_exit();

  8.         return 1;
  9. }

irq_enter()函數的核心調用是__irq_enter(),後者的主要作用是在圖2的 preempt_count變量的HARDIRQ部分+1,即標識一個hardirq的上下文,所以可以認爲do_IRQ()調用irq_enter函數 意味着中斷處理進入hardirq階段。此時處理器響應外部中斷的能力依然是被disable掉的(EFLAG.IF=0),因爲ISR基本上屬於設備驅 動程序涉足的領域,內核無法保證在設備驅動程序的ISR中會否將EFLAG.IF置1(此處涉及可能的中斷嵌套以及中斷棧溢出問題,可以參考ARM中斷處 理的那個帖子),所以我們會在內核代碼中看到內核開發者爲了儘可能避免非受控的ISR部分給系統帶來的損害所做的努力。按照現在內核的設計理念,在 hardirq部分最好不要打開中斷,所以處在hardirq上下文中的設備驅動程序的ISR應該儘可能快地返回(所謂只完成最關鍵的任務),而將耗時的 中斷處理善後工作留到softirq部分,因爲在softirq部分,內核會使EFLAG.IF=1,所以也意味着softirq部分可以隨時被外部中斷 所打斷。

下面我們重點討論一下softirq部分的實現原理,do_IRQ()中調用irq_exit函數標誌softirq部分 的開始。 irq_exit()函數會首先在圖2的preempt_count變量的HARDIRQ部分-1,目的是清除hardirq的上下文標記。然後它有個關鍵的調用invoke_softirq,用於實際的softirq處理工作:

<kernel/softirq.c>


  1. void irq_exit(void)
  2. {
  3.         ...
  4.         sub_preempt_count(IRQ_EXIT_OFFSET);
  5.         if (!in_interrupt() && local_softirq_pending())
  6.                 invoke_softirq();

  7.         rcu_irq_exit();
  8.         ...
  9. }
ILDD中對in_interrupt()函數的敘述很明確:"...其主要用意是根據當前preempt_count變 量,來判斷當前代碼是否在一箇中斷上下文中執行。根據in_interrupt的定義來看,Linux內核認爲HARDIRQ、SOFTIRQ以及NMI 都屬於interrupt範疇...",所以softirq部分是否被執行,取決於:1.當前是否在中斷上下文,2. 是否有pending的softirq需要處理。
第一個條件,主要用來防止softirq部分的重入,因爲一旦有pending的softirq需要處理,那麼invoke_softirq()的調用 (實際的工作發生在__do_softirq函數中)首先會將圖2中SOFTIRQ部分+1,這樣若在當前正在處理softirq過程中發生了外部中 斷,hardirq部分標識了一個pending softirq,那麼在irq_exit函數中將直接返回,而不會調用到invoke_softirq。

softirq部分的核心代碼段如下:

<kernel/softirq.c>
  1. asmlinkage void __do_softirq(void)
  2. {
  3.         ...
  4.         //獲得__softirq_pending變量上保存的pending softirq數據
  5.         pending = local_softirq_pending();
  6.         //將圖2中SOFTIRQ部分+1,標識softirq上下文,此時in_interrupt()返回true.
  7.         __local_bh_disable((unsigned long)__builtin_return_address(0), SOFTIRQ_OFFSET); 
  8.         ...
  9. restart:
  10.         /* Reset the pending bitmask before enabling irqs */
  11.         set_softirq_pending(0);
  12.         //注意此處,內核調用local_irq_enable打開處理器響應外部中斷能力,EFLAG.IF=1,所以接下來的softirq處理時外部中斷可以進入處理器
  13.         local_irq_enable();

  14.         h = softirq_vec;
  15.         do {
  16.                 if (pending & 1) {
  17.                         ...
  18.                         //調用每個類型的softirq所對應的處理函數,見下圖3
  19.                         h->action(h); 
  20.                         ...
  21.                 }
  22.                 h++;
  23.                 pending >>= 1;
  24.         } while (pending);
  25.         //softirq處理結束前,重新關閉中斷。中斷的再次打開發生在中斷的返回,也就是在圖1中處理器在執行iret指令時的硬件邏輯,POP EFLAG, EFLAG.IF=1
  26.         local_irq_disable();
  27.         
  28.         //再次檢測是否有新的pending softirq,因爲softirq執行時可能有新的外部中斷進來,如果有,此處一併處理。
  29.         pending = local_softirq_pending();
  30.         if (pending && --max_restart)
  31.                 goto restart;
  32.         ...
  33.         //將圖2中SOFTIRQ部分-1,標識softirq上下文的結束
  34.         __local_bh_enable(SOFTIRQ_OFFSET); 
  35. }
其中每個關鍵的節點都做了註釋,ILDD書中對此也有詳細的說明,包括其中的ksoftirqd的用法。

對每個類型的softirq的處理髮生在上面的do...while...循環中,原理其實非常簡單,爲節省文字,用下面一個草圖來做說明(圖3):



所以do...while...循環實際上是從bit 0遍歷__softirq_pending變量,目前該變量的0~9分別對應10個不同類型的softirq,每個softirq對應不同的處理函數,比如HI_SOFTIRQ對應的action爲tasklet_hi_action,它與TASKLET_SOFTIRQ的action的原理完全一樣,也就是我們平常所說的tasklet。__softirq_pending是個per-CPU型的變量,因爲SMP系統中每個處理器都可以獨立處理各自到來 的外部中斷,也都對應有各自的hardirq棧和softirq棧,詳見Linux內核中的中斷棧與內核棧的補充說明一貼。另外,在前面的帖子中我們一直說hardirq部分標識一個softirq,何謂標識一個softirq?其實就是調用tasklet_schedule()來把TASKLET_SOFTIRQ位置1.

在ILDD一書中實際上將tasklet分成兩部分,分別放在第5章”中斷處理“和第6章”延遲操作“,第5章側重討論softirq的機制,而第6章則重點討論tasklet。
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章