http://www./forum.php/forum.php?mod=viewthread&tid=499&extra=page%3D1
中斷棧與內(nèi)核棧的話題更多地屬于內(nèi)核的范疇,所以在《深入Linux設(shè)備驅(qū)動(dòng)程序內(nèi)核機(jī)制》第5章“中斷處理”當(dāng)中,基本上沒(méi)怎么涉及到上述內(nèi)容,只是在5.4節(jié)有些許的文字討論中斷棧在中斷嵌套情形下可能的溢出問(wèn)題。
本貼在這個(gè)基礎(chǔ)上對(duì)內(nèi)核棧與中斷棧的話題做些補(bǔ)充,討論基于x86 32位系統(tǒng),因?yàn)?4位系統(tǒng)下Linux內(nèi)核關(guān)于棧的支持原理上是相同的,不過(guò)也有些特性屬于64位特有的,比如IST(Interrupt Stack Table),如果可能將來(lái)會(huì)在processor版塊發(fā)個(gè)帖子專門討論。
1. x86下內(nèi)核棧與中斷棧是否共享的問(wèn)題
我們知道Linux系統(tǒng)下每個(gè)用戶進(jìn)程都有個(gè)task_struct對(duì)象來(lái)表示,同時(shí)在處理器層面還對(duì)應(yīng)一個(gè)TSS(Task State Segment),當(dāng)中斷發(fā)生時(shí),用戶進(jìn)程或者處于用戶態(tài)(特權(quán)級(jí)3)或者處于內(nèi)核態(tài)(特權(quán)級(jí)0),如果是在用戶態(tài),那么會(huì)發(fā)生棧的切換問(wèn)題,也就是會(huì)切換到內(nèi)核態(tài)的棧,如果是在內(nèi)核態(tài),那么就沒(méi)有棧切換的問(wèn)題。但是x86處理器在特權(quán)級(jí)0上只有一個(gè)ESP,這意味著中斷發(fā)生后,只能使用一個(gè)棧,這個(gè)棧就是內(nèi)核棧(kernel stack)。處理器的硬件邏輯會(huì)將被中斷進(jìn)程的下條指令(CS,EIP)以及EFLAG壓入棧,當(dāng)然如果發(fā)生用戶態(tài)棧向內(nèi)核態(tài)棧的切換,處理器還會(huì)把用戶態(tài)的(SS, ESP)也壓入棧,此時(shí)使用的就是內(nèi)核棧。這個(gè)行為屬于處理器的硬件邏輯范疇,不是系統(tǒng)軟件的行為。
至于x86下內(nèi)核棧與中斷棧是否共享的問(wèn)題,其實(shí)是個(gè)內(nèi)核設(shè)計(jì)的問(wèn)題,換言之,中斷??膳c內(nèi)核棧共享,也可重新分配一個(gè)獨(dú)立的中斷棧。2.4的內(nèi)核版本似乎采用中斷棧與內(nèi)核棧共享的設(shè)計(jì),因?yàn)檫@種設(shè)計(jì)的好處是代碼相對(duì)簡(jiǎn)單,如前所述,直接使用ESP0就可以了,但是負(fù)面因素是中斷棧如果發(fā)生嵌套,可能破壞內(nèi)核棧的一些數(shù)據(jù),因?yàn)楫吘构蚕?,所以??臻g有時(shí)候難免會(huì)捉襟見(jiàn)肘。所以在2.5內(nèi)核版本開(kāi)發(fā)中,來(lái)自IBM的一位大俠曾提交過(guò)一個(gè)補(bǔ)丁,試圖在中斷發(fā)生時(shí),從內(nèi)核棧switch到一個(gè)獨(dú)立的中斷棧中,后來(lái)也不知道被內(nèi)核社區(qū)采納了沒(méi)有,總之我現(xiàn)在在3.2的內(nèi)核源碼中沒(méi)有看到那位仁兄的補(bǔ)丁代碼了,當(dāng)然也可能是那個(gè)補(bǔ)丁已經(jīng)長(zhǎng)成現(xiàn)在的代碼樣子了。
現(xiàn)在的Linux內(nèi)核中采用的是內(nèi)核棧與中斷棧分離的設(shè)計(jì),下面我們從源碼層面來(lái)看一看這種分離是如何完成的。
內(nèi)核棧與中斷棧分離的核心代碼發(fā)生在do_IRQ() --> handle_irq() --> execute_on_irq_stack() 最后一個(gè)函數(shù)字面上的意思大約是在中斷棧中執(zhí)行中斷處理例程,也就是說(shuō)中斷的處理函數(shù)會(huì)在獨(dú)立于被中斷進(jìn)程的上下文中執(zhí)行。execute_on_irq_stack的函數(shù)實(shí)現(xiàn)為:
<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;
- }
復(fù)制代碼
代碼中的curctx=(union irq_ctx *) current_thread_info()用來(lái)獲得當(dāng)前被中斷進(jìn)程的上下文,irqctx = __this_cpu_read(hardirq_ctx)用來(lái)獲得hardirq的上下文,其實(shí)就是獲得獨(dú)立的中斷棧起始地址。中斷棧的大小與layout與內(nèi)核棧是完全一樣的。接下來(lái)isp指向中斷棧棧頂,最后的堆棧切換發(fā)生在那段匯編代碼中:當(dāng)前進(jìn)程的內(nèi)核棧ESP指針保存在EBX中,而中斷棧的isp則賦值給了ESP,這樣接下來(lái)的代碼就將使用中斷棧了。call語(yǔ)句負(fù)責(zé)調(diào)用desc->handle_irq()函數(shù),這里會(huì)進(jìn)行中斷處理,設(shè)備驅(qū)動(dòng)程序注冊(cè)的中斷處理函數(shù)會(huì)被調(diào)用到。當(dāng)中斷處理例程結(jié)束返回時(shí),ESP將重新指向被中斷進(jìn)程的內(nèi)核棧。(此處我們應(yīng)該注意到內(nèi)核棧中還保留著中斷發(fā)生時(shí)處理器硬件邏輯所壓入的CS, EIP等寄存器,所以在內(nèi)核棧中做中斷返回是完全正確的)。 2. 中斷棧的分配
獨(dú)立的中斷棧所在內(nèi)存空間的分配發(fā)生在arch/x86/kernel/irq_32.c的irq_ctx_init函數(shù)中(如果是多處理器系統(tǒng),那么每個(gè)處理器都會(huì)有一個(gè)獨(dú)立的中斷棧),函數(shù)使用__alloc_pages在低端內(nèi)存區(qū)分配2個(gè)物理頁(yè)面(2的THREAD_ORDER次方),也就是8KB大小的空間。有趣的是,這個(gè)函數(shù)還會(huì)為softirq分配一個(gè)同樣大小的獨(dú)立堆棧,如此說(shuō)來(lái),softirq將不會(huì)在hardirq的中斷棧上執(zhí)行,而是在自己的上下文中執(zhí)行。 總結(jié)一下,系統(tǒng)中每個(gè)進(jìn)程都會(huì)擁有屬于自己的內(nèi)核棧,而系統(tǒng)中每個(gè)CPU都將為中斷處理準(zhǔn)備了兩個(gè)獨(dú)立的中斷棧,分別是hardirq棧和softirq棧。草圖如下:
關(guān)于在中斷處理函數(shù)中涉及到的阻塞問(wèn)題,我個(gè)人的觀點(diǎn)是:現(xiàn)實(shí)中絕對(duì)不要這么干,其中的原因就不多說(shuō)了。從內(nèi)核理論實(shí)現(xiàn)的角度,調(diào)度其他進(jìn)程是可行的。
|