Linux內核中的中斷棧與內核棧的補充說明【轉】
阿新 • • 發佈:2017-06-24
desc 試圖 調度 php == over 註冊 tex 存在
本貼在這個基礎上對內核棧與中斷棧的話題做些補充,討論基於x86 32位系統,因為64位系統下Linux內核關於棧的支持原理上是相同的,不過也有些特性屬於64位特有的,比如IST(Interrupt Stack Table),如果可能將來會在processor版塊發個帖子專門討論。
1. x86下內核棧與中斷棧是否共享的問題
我們知道Linux系統下每個用戶進程都有個task_struct對象來表示,同時在處理器層面還對應一個TSS(Task State Segment),當中斷發生時,用戶進程或者處於用戶態(ring 3)或者處於內核態(ring 0),如果是在用戶態,那麽會發生棧的切換問題,也就是會切換到內核態的棧,如果是在內核態,那麽就沒有棧切換的問題。但是x86處理器在ring 0上只有一個ESP,這意味著中斷發生後,只能使用一個棧,這個棧就是內核棧(kernel stack)。處理器的硬件邏輯會將被中斷進程的下條指令(CS,EIP)以及EFLAG壓入棧,當然如果發生用戶態棧向內核態棧的切換,處理器還會把用戶態的(SS, ESP)也壓入棧,此時使用的就是內核棧。這個行為屬於處理器的硬件邏輯範疇,不是系統軟件的行為。
至於x86下內核棧與中斷棧是否共享的問題,其實是個內核設計的問題,換言之,中斷棧可與內核棧共享,也可重新分配一個獨立的中斷棧。2.4的內核版本似乎采用中斷棧與內核棧共享的設計,因為這種設計的好處是代碼相對簡單,如前所述,直接使用ESP0就可以了,但是負面因素是中斷棧如果發生嵌套,可能破壞內核棧的一些數據,因為畢竟共享,所以棧空間有時候難免會捉襟見肘。所以在2.5內核版本開發中,來自IBM的一位大俠曾提交過一個補丁(詳見http://lwn.net/Articles/21846/),試圖在中斷發生時,從內核棧switch到一個獨立的中斷棧中,後來也不知道被內核社區采納了沒有,總之我現在在3.2的內核源碼中沒有看到那位仁兄的補丁代碼了,當然也可能是那個補丁已經長成現在的代碼樣子了。
現在的Linux內核中采用的是內核棧與中斷棧分離的設計,下面我們從源碼層面來看一看這種分離是如何完成的。
內核棧與中斷棧分離的核心代碼發生在do_IRQ() --> handle_irq() --> execute_on_irq_stack()
最後一個函數字面上的意思大約是在中斷棧中執行中斷處理例程,也就是說中斷的處理函數會在獨立於被中斷進程的上下文中執行。execute_on_irq_stack的函數實現為:
<arch/x86/kernel/irq_32.c>
2. 中斷棧的分配
獨立的中斷棧所在內存空間的分配發生在arch/x86/kernel/irq_32.c的irq_ctx_init函數中(如果是多處理器系統,那麽每個處理器都會有一個獨立的中斷棧),函數使用__alloc_pages在低端內存區分配2個物理頁面(2的THREAD_ORDER次方),也就是8KB大小的空間。有趣的是,這個函數還會為softirq分配一個同樣大小的獨立堆棧,如此說來,softirq將不會在hardirq的中斷棧上執行,而是在自己的上下文中執行。
總結一下,系統中每個進程都會擁有屬於自己的內核棧,而系統中每個CPU都將為中斷處理準備了兩個獨立的中斷棧,分別是hardirq棧和softirq棧。草圖如下:
最後,關於設備驅動程序的中斷處理例程中調用可能引起阻塞函數的問題,可以簡單歸結為在中斷處理上下文中能否進行調度的問題。現實中,絕對不應該這樣做,因為這會引起很多問題。但是從理論實現的角度,如果調度器願意,它找到被中斷進程的上下文並不存在技術上的障礙,這意味著在中斷處理函數中如果發生進程切換,被中斷進程被再次調度是可能的,如果調度器願意這麽做的話。
(原文首發:http://www.embexperts.com/forum.php/forum.php?mod=viewthread&tid=499&extra=page%3D1,略有改動)
轉自:http://blog.chinaunix.net/uid-12461657-id-3487463.html
原文地址:Linux內核中的中斷棧與內核棧的補充說明 作者:MagicBoy2010
中斷棧與內核棧的話題更多地屬於內核的範疇,所以在《深入Linux設備驅動程序內核機制》第5章“中斷處理”當中,基本上沒怎麽涉及到上述內容,只是在5.4節有些許的文字討論中斷棧在中斷嵌套情形下可能的溢出問題。
本貼在這個基礎上對內核棧與中斷棧的話題做些補充,討論基於x86 32位系統,因為64位系統下Linux內核關於棧的支持原理上是相同的,不過也有些特性屬於64位特有的,比如IST(Interrupt Stack Table),如果可能將來會在processor版塊發個帖子專門討論。
1. x86下內核棧與中斷棧是否共享的問題
我們知道Linux系統下每個用戶進程都有個task_struct對象來表示,同時在處理器層面還對應一個TSS(Task State Segment),當中斷發生時,用戶進程或者處於用戶態(ring 3)或者處於內核態(ring 0),如果是在用戶態,那麽會發生棧的切換問題,也就是會切換到內核態的棧,如果是在內核態,那麽就沒有棧切換的問題。但是x86處理器在ring 0上只有一個ESP,這意味著中斷發生後,只能使用一個棧,這個棧就是內核棧(kernel stack)。處理器的硬件邏輯會將被中斷進程的下條指令(CS,EIP)以及EFLAG壓入棧,當然如果發生用戶態棧向內核態棧的切換,處理器還會把用戶態的(SS, ESP)也壓入棧,此時使用的就是內核棧。這個行為屬於處理器的硬件邏輯範疇,不是系統軟件的行為。
至於x86下內核棧與中斷棧是否共享的問題,其實是個內核設計的問題,換言之,中斷棧可與內核棧共享,也可重新分配一個獨立的中斷棧。2.4的內核版本似乎采用中斷棧與內核棧共享的設計,因為這種設計的好處是代碼相對簡單,如前所述,直接使用ESP0就可以了,但是負面因素是中斷棧如果發生嵌套,可能破壞內核棧的一些數據,因為畢竟共享,所以棧空間有時候難免會捉襟見肘。所以在2.5內核版本開發中,來自IBM的一位大俠曾提交過一個補丁(詳見http://lwn.net/Articles/21846/),試圖在中斷發生時,從內核棧switch到一個獨立的中斷棧中,後來也不知道被內核社區采納了沒有,總之我現在在3.2的內核源碼中沒有看到那位仁兄的補丁代碼了,當然也可能是那個補丁已經長成現在的代碼樣子了。
現在的Linux內核中采用的是內核棧與中斷棧分離的設計,下面我們從源碼層面來看一看這種分離是如何完成的。
內核棧與中斷棧分離的核心代碼發生在do_IRQ() --> handle_irq() --> execute_on_irq_stack()
最後一個函數字面上的意思大約是在中斷棧中執行中斷處理例程,也就是說中斷的處理函數會在獨立於被中斷進程的上下文中執行。execute_on_irq_stack的函數實現為:
<arch/x86/kernel/irq_32.c>
- static inline int
-
- execute_on_irq_stack(int overflow, struct irq_desc *desc, int irq)
-
- {
- union irq_ctx *curctx, *irqctx;
- u32 *isp, arg1, arg2;
-
- curctx = (union irq_ctx *) current_thread_info();
- irqctx = __this_cpu_read(hardirq_ctx);
- /*
- * this is where we switch to the IRQ stack. However, if we are
- * already using the IRQ stack (because we interrupted a hardirq
- * handler) we can‘t do that and just have to keep using the
- * current stack (which is the irq stack already after all)
- */
-
- if (unlikely(curctx == irqctx))
- return 0;
-
- /* build the stack frame on the IRQ stack */
- isp = (u32 *) ((char *)irqctx + sizeof(*irqctx));
- irqctx->tinfo.task = curctx->tinfo.task;
- irqctx->tinfo.previous_esp = current_stack_pointer;
-
- /*
- * Copy the softirq bits in preempt_count so that the
- * softirq checks work in the hardirq context.
- */
-
- irqctx->tinfo.preempt_count =
- (irqctx->tinfo.preempt_count & ~SOFTIRQ_MASK) |
- (curctx->tinfo.preempt_count & SOFTIRQ_MASK);
-
- if (unlikely(overflow))
- call_on_stack(print_stack_overflow, isp);
-
- asm volatile("xchgl %%ebx,%%esp \n"
- "call *%%edi \n"
- "movl %%ebx,%%esp \n"
- : "=a" (arg1), "=d" (arg2), "=b" (isp)
- : "0" (irq), "1" (desc), "2" (isp),
- "D" (desc->handle_irq)
- : "memory", "cc", "ecx");
-
- return 1;
- }
2. 中斷棧的分配
獨立的中斷棧所在內存空間的分配發生在arch/x86/kernel/irq_32.c的irq_ctx_init函數中(如果是多處理器系統,那麽每個處理器都會有一個獨立的中斷棧),函數使用__alloc_pages在低端內存區分配2個物理頁面(2的THREAD_ORDER次方),也就是8KB大小的空間。有趣的是,這個函數還會為softirq分配一個同樣大小的獨立堆棧,如此說來,softirq將不會在hardirq的中斷棧上執行,而是在自己的上下文中執行。
總結一下,系統中每個進程都會擁有屬於自己的內核棧,而系統中每個CPU都將為中斷處理準備了兩個獨立的中斷棧,分別是hardirq棧和softirq棧。草圖如下:
最後,關於設備驅動程序的中斷處理例程中調用可能引起阻塞函數的問題,可以簡單歸結為在中斷處理上下文中能否進行調度的問題。現實中,絕對不應該這樣做,因為這會引起很多問題。但是從理論實現的角度,如果調度器願意,它找到被中斷進程的上下文並不存在技術上的障礙,這意味著在中斷處理函數中如果發生進程切換,被中斷進程被再次調度是可能的,如果調度器願意這麽做的話。
(原文首發:http://www.embexperts.com/forum.php/forum.php?mod=viewthread&tid=499&extra=page%3D1,略有改動)
Linux內核中的中斷棧與內核棧的補充說明【轉】