在學習Linux核心中斷棧的時候發現的資料,備份轉載如下:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
<a href="http://www.embexperts.com/forum.php/forum.php?mod=viewthread&tid=499&extra=page%3D1" target="_blank">http://www.embexperts.com/forum.php/forum.php?mod=viewthread&tid=499&extra=page%3D1</a>
<b>Linux核心中的中斷棧與核心棧的補充說明</b>
中斷棧與核心棧的話題更多地屬于核心的範疇,是以在《深入Linux裝置驅動程式核心機制》第5章“中斷處理”當中,基本上沒怎麼涉及到上述内容,隻是在5.4節有些許的文字讨論中斷棧在中斷嵌套情形下可能的溢出問題。
本貼在這個基礎上對核心棧與中斷棧的話題做些補充,讨論基于x86 32位系統,因為64位系統下Linux核心關于棧的支援原理上是相同的,不過也有些特性屬于64位特有的,比如IST(Interrupt Stack Table),如果可能将來會在processor版塊發個文章專門讨論。
1. x86下核心棧與中斷棧是否共享的問題
我們知道Linux系統下每個使用者程序都有個task_struct對象來表示,同時在處理器層面還對應一個TSS(Task State Segment),當中斷發生時,使用者程序或者處于使用者态(特權級3)或者處于核心态(特權級0),如果是在使用者态,那麼會發生棧的切換問題,也就是會切換到核心态的棧,如果是在核心态,那麼就沒有棧切換的問題。但是x86處理器在特權級0上隻有一個ESP,這意味着中斷發生後,隻能使用一個棧,這個棧就是核心棧(kernel stack)。處理器的硬體邏輯會将被中斷程序的下條指令(CS,EIP)以及EFLAG壓入棧,當然如果發生使用者态棧向核心态棧的切換,處理器還會把使用者态的(SS, ESP)也壓入棧,此時使用的就是核心棧。這個行為屬于處理器的硬體邏輯範疇,不是系統軟體的行為。
至于x86下核心棧與中斷棧是否共享的問題,其實是個核心設計的問題,換言之,中斷棧可與核心棧共享,也可重新配置設定一個獨立的中斷棧。2.4的核心版本似乎采用中斷棧與核心棧共享的設計,因為這種設計的好處是代碼相對簡單,如前所述,直接使用ESP0就可以了,但是負面因素是中斷棧如果發生嵌套,可能破壞核心棧的一些資料,因為畢竟共享,是以棧空間有時候難免會捉襟見肘。是以在2.5核心版本開發中,來自IBM的一位大俠曾送出過一個更新檔,試圖在中斷發生時,從核心棧switch到一個獨立的中斷棧中,後來也不知道被核心社群采納了沒有,總之我現在在3.2的核心源碼中沒有看到那位仁兄的更新檔代碼了,當然也可能是那個更新檔已經長成現在的代碼樣子了。
現在的Linux核心中采用的是核心棧與中斷棧分離的設計,下面我們從源碼層面來看一看這種分離是如何完成的。
核心棧與中斷棧分離的核心代碼發生在do_IRQ() --> handle_irq() --> execute_on_irq_stack()
最後一個函數字面上的意思大約是在中斷棧中執行中斷處理例程,也就是說中斷的處理函數會在獨立于被中斷程序的上下文中執行。execute_on_irq_stack的函數實作為:
點選(此處)折疊或打開
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;
}
代碼中的curctx=(union irq_ctx *) current_thread_info()用來獲得目前被中斷程序的上下文,irqctx = __this_cpu_read(hardirq_ctx)用來獲得hardirq的上下文,其實就是獲得獨立的中斷棧起始位址。中斷棧的大小與layout與核心棧是完全一樣的。接下來isp指向中斷棧棧頂,最後的堆棧切換發生在那段彙編代碼中:目前程序的核心棧ESP指針儲存在EBX中,而中斷棧的isp則指派給了ESP,這樣接下來的代碼就将使用中斷棧了。call語句負責調用desc->handle_irq()函數,這裡會進行中斷處理,裝置驅動程式注冊的中斷處理函數會被調用到。當中斷處理例程結束傳回時,ESP将重新指向被中斷程序的核心棧。(此處我們應該注意到核心棧中還保留着中斷發生時處理器硬體邏輯所壓入的CS, EIP等寄存器,是以在核心棧中做中斷傳回是完全正确的)。
2. 中斷棧的配置設定
獨立的中斷棧所在記憶體空間的配置設定發生在arch/x86/kernel/irq_32.c的irq_ctx_init函數中(如果是多處理器系統,那麼每個處理器都會有一個獨立的中斷棧),函數使用__alloc_pages在低端記憶體區配置設定2個實體頁面(2的THREAD_ORDER次方),也就是8KB大小的空間。有趣的是,這個函數還會為softirq配置設定一個同樣大小的獨立堆棧,如此說來,softirq将不會在hardirq的中斷棧上執行,而是在自己的上下文中執行。
總結一下,系統中每個程序都會擁有屬于自己的核心棧,而系統中每個CPU都将為中斷處理準備了兩個獨立的中斷棧,分别是hardirq棧和softirq棧。草圖如下:
關于在中斷處理函數中涉及到的阻塞問題,我個人的觀點是:現實中絕對不要這麼幹,其中的原因就不多說了。從核心理論實作的角度,排程其他程序是可行的。