1. 程式人生 > >再思linux核心在中斷路徑內不能睡眠/排程的原因(2010)

再思linux核心在中斷路徑內不能睡眠/排程的原因(2010)

Linux核心中斷路徑中不能睡眠,為什麼?

但是,他們的討論最後沒有得出一個明確的結論。其中,cskyrain在8樓 的思考觸及到了一個要點,但是沒有深入展開:

  1. 1樓 發表於 2009-11-24 20:36  | 只看該作者  
  2. 一直認為中斷處理函式不能休眠的是天經地義的,可從沒認真思考過問什麼不能休眠,阻塞。最近看了一下ulk中對這個的解釋,感覺還是有點不太明白,  
  3. “The price to pay for allowing nested kernel control paths is that an interrupt handler must never block, that 
    is, no process switch can take place until an interrupt handler is running. In fact, all the data needed to resume a nested kernel control path is stored in the Kernel Mode stack, which is tightly bound to the current process.”  
  4. 上面把中斷處理程式不能休眠歸結為中斷處理程式可以巢狀,而恢復巢狀的中斷處理程式的相關資料放在核心態堆疊中,這個棧和當前程序相關聯,這裡有一點不明白,既然有棧儲存資料,而且程序切換出去後,這個棧也不會被銷燬,等程序在切回來時,不同樣可以是巢狀的中斷處理程式返回嗎?(這裡先不考了中斷處理時間的太長,影響對中斷處理的服務問題,只說明中斷是否可休眠)。同時我google一下,看到有人對中斷不能休眠的以一種解釋:  
  5. ”  
  6. 中斷處理程式用到的所有資料有儲存在當前程序的核心堆疊中(一般情況下),如果此時發生了  
  7. 程序切換,中斷處理程式將被阻塞, 當在一次發生程序切換時,不一定馬上就換回來。比如當  
  8. 發生一個鍵盤中斷時,鍵盤處理程式正在進行,此時又恰好發生了程序搶佔,切換到了另一個程序  
  9. 中,假如那個程序不會引起核心的穩定,那麼中斷處理程式將一直阻塞,內禾根本就沒法響應那個中斷,  
  10. 直到在一次發生程序切換。假設最後又切換回執行中斷處理程式的那個程序中時,如果它的核心堆疊  
  11. 受到了無意的破壞怎麼辦呢?那中斷處理程式可能無法在繼續執行下去了,此次中斷服務失敗。而且  
  12. 現在的處理程式都允許中斷巢狀,一箇中斷被阻塞,其它的都將被阻塞。所以面對錯綜複雜的核心邏輯,  
  13. 最好的辦法就是在中斷處理程式中禁止發生程序切換,這樣既提高了中斷處理程式的響應速度,也增加了  
  14. 核心的穩定性與安全性。  
  15. “  
  16. 我感覺他的理由有兩個:一個事效率,可能影響處理速度,另一就是:如果它的核心堆疊  
  17. 受到了無意的破壞怎麼辦?  
  18. 對於第一種理由先不討論,  
  19. 可第二種,理由我感覺很牽強,如果棧那麼容易破壞,哪我也可以說描述程序的資料結構什麼的也可能被破壞,哪豈不是程序排程都是不安全的了,  
  20. 這樣按他的說法,就只有第一個效率的原因了。  
  21. 回到ulk的解釋,不知道是不是我理解的有問題,The price to pay for allowing nested kernel control paths is that an interrupt handler must never block  
  22. 感覺他把中斷不能休眠的原因都歸結為可支援中斷處理程式的巢狀上了。那是不是可以這樣理解,如果不支援中斷巢狀,中斷處理程式就可以休眠了,呵呵呵,貌似這樣也不對吧,  
  23. 問題:中斷處理程式不能休眠的原因究竟是什麼呢?  

7樓:

  1. kouu 發表於 2009-11-25 11:52  
  2. 回覆 #6 cskyrain 的帖子  
  3. 中斷不能block,應該特指非同步中斷吧。  
  4. 看了LZ這兩天的帖子,我覺得還是類似4樓的說法比較靠譜:非同步中斷是獨立的上下文,與當前程序無關,所以不能因為中斷上下文的block而將無辜的程序給block了。可能就是基於這一初衷吧~ 那些已經將中斷處理程式執行緒化了的實時linux應該是允許中斷block的。  
  5. 而同步的中斷(比如系統呼叫、缺頁異常)是代表當前程序的,本來就是可以block的。  

8樓:

  1. 回覆 #7 kouu 的帖子  
  2. 呵呵,又翻了翻書,思考了一下,由於時間問題,沒心情再從頭讀ulk,只是跳著查了一下,難免會漏下很多東西,不過還是說說我的理解:  
  3. 這裡中斷只代表非同步中斷,異常代表同步中斷,這樣系統呼叫是異常處理,不是中斷處理。  
  4. 這裡異常處理是可以休眠block的,因為異常處理所需的資料是儲存在異常棧中,而每個程序都有一個異常棧,所以異常處理和程序是相關聯的,這樣異常處理可以block,被排程出去。  
  5. 而對於中斷,分為兩種情況,一種是中斷使用單獨的中斷棧而不使用程序的核心棧的情況,這樣,由於所有中斷共享一箇中斷棧,這個中斷棧不和特定程序關聯,所以,這種中斷時不能block的,block後他是不能再被排程。  
  6. 第二種情況是,中斷不使用單獨的中斷棧,而是使用當前程序的核心棧,這種情況我認為是和異常處理時一樣的,這種中斷時可以block的,之所以不准許中斷 block不是技術上切換不回來,而是邏輯上為了提高處理的效率強制其不能block。  
  7. 以上是我現階段的理解,感覺,前倆個的解釋應該沒什麼問題,但最後對不是用中斷棧的中斷的解釋可能有錯誤的認識,不知kouu對這種解釋有何看法?  

總體上,大家得到的初步結論是:核心在中斷路徑內不能睡眠,不是技術上做不到,而是沒有理由這麼做,或者說在中斷路徑上睡眠不合理。一方面,外部事件導致當前程序時間片被剝奪,不合理;一方面,中斷服務程式應該儘快處理完中斷,保證IO吞吐率。

-------------------------------分割線----------------------------

通過閱讀ULK中文版第三版164頁的內容,讓我對這個問題有了一個較為清晰的認識:

核心在編譯的時候設定了THREAD_SIZE的值為8K的話, 那麼每個程序的核心棧的大小就為8K, 此時如果發生中斷時, 那麼程序的暫存器等值就會儲存到它的8K的核心棧中. 但是如果設定了THREAD_SIZE的大小為4K的話, 核心就會使用3種類型的核心棧, 異常棧, 硬體中斷請求棧以及軟中斷請求棧( When using 4K stacks, interrupts get their own stack instead of using the currently active kernel stack.)

   * 異常棧:每個程序一個。

   * 硬中斷請求棧:每個CPU一個,每個佔用一個單獨的頁框。do_IRQ()函式內部通過呼叫execute_on_irq_stack負責切換到該棧中來。

   * 軟中斷請求棧:每個CPU一個,每個佔用一個單獨的頁框。

關於切換到中斷棧的方法,請看下面的程式碼:

  1. /* 
  2.  * do_IRQ handles all normal device IRQ's (the special 
  3.  * SMP cross-CPU interrupts have their own specific 
  4.  * handlers). 
  5.  */
  6. unsigned int do_IRQ(struct pt_regs *regs)  
  7. {  
  8.     struct pt_regs *old_regs;  
  9.     /* high bit used in ret_from_ code */
  10.     int overflow;  
  11.     unsigned vector = ~regs->orig_ax;  
  12.     struct irq_desc *desc;  
  13.     unsigned irq;  
  14.     old_regs = set_irq_regs(regs);  
  15.     irq_enter();  
  16.     irq = __get_cpu_var(vector_irq)[vector];  
  17.     overflow = check_stack_overflow();  
  18.     desc = irq_to_desc(irq);  
  19.     if (unlikely(!desc)) {  
  20.         printk(KERN_EMERG "%s: cannot handle IRQ %d vector %#x cpu %d/n",  
  21.                     __func__, irq, vector, smp_processor_id());  
  22.         BUG();  
  23.     }  
  24.     if (!execute_on_irq_stack(overflow, desc, irq)) {  
  25.         if (unlikely(overflow))  
  26.             print_stack_overflow();  
  27.         desc->handle_irq(irq, desc);  
  28.     }  
  29.     irq_exit();  
  30.     set_irq_regs(old_regs);  
  31.     return 1;  
  32. }  
  1. staticinlineint
  2. execute_on_irq_stack(int overflow, struct irq_desc *desc, int irq)  
  3. {  
  4.     union irq_ctx *curctx, *irqctx;  
  5.     u32 *isp, arg1, arg2;  
  6.     curctx = (union irq_ctx *) current_thread_info();  
  7.     irqctx = hardirq_ctx[smp_processor_id()];  
  8.     /* 
  9.      * this is where we switch to the IRQ stack. However, if we are 
  10.      * already using the IRQ stack (because we interrupted a hardirq 
  11.      * handler) we can't do that and just have to keep using the 
  12.      * current stack (which is the irq stack already after all) 
  13.      */
  14.     if (unlikely(curctx == irqctx))  
  15.         return 0;  
  16.     /* build the stack frame on the IRQ stack */
  17.     isp = (u32 *) ((char*)irqctx + sizeof(*irqctx));  
  18.     irqctx->tinfo.task = curctx->tinfo.task;  
  19.     irqctx->tinfo.previous_esp = current_stack_pointer;  
  20.     /* 
  21.      * Copy the softirq bits in preempt_count so that the 
  22.      * softirq checks work in the hardirq context. 
  23.      */
  24.     irqctx->tinfo.preempt_count =  
  25.         (irqctx->tinfo.preempt_count & ~SOFTIRQ_MASK) |  
  26.         (curctx->tinfo.preempt_count & SOFTIRQ_MASK);  
  27.     if (unlikely(overflow))  
  28.         call_on_stack(print_stack_overflow, isp);  
  29.     asm volatile("xchgl %%ebx,%%esp /n"
  30.              "call  *%%edi      /n"
  31.              "movl  %%ebx,%%esp /n"
  32.              : "=a" (arg1), "=d" (arg2), "=b" (isp)  
  33.              :  "0" (irq),   "1" (desc),  "2" (isp),  
  34.             "D" (desc->handle_irq)  
  35.              : "memory""cc""ecx");  
  36.     return 1;  
  37. }  

最後,思考一下標題中的問題:linux核心在中斷路徑內不能睡眠/排程的原因

Linux是以程序為排程單位的,排程器只看到程序核心棧,而看不到中斷棧。在獨立中斷棧的模式下,如果linux核心在中斷路徑內發生了排程(從技術上講,睡眠和排程是一個意思),那麼linux將無法找到“回家的路”,未執行完的中斷處理程式碼將再也無法獲得執行機會。