内核在编译的时候设置了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一个,每个占用一个单独的页框。
关于切换到中断栈的方法,请看下面的代码:
- /*
- * do_IRQ handles all normal device IRQ's (the special
- * SMP cross-CPU interrupts have their own specific
- * handlers).
- */
- unsigned int do_IRQ(struct pt_regs *regs)
- {
- struct pt_regs *old_regs;
- /* high bit used in ret_from_ code */
- int overflow;
- unsigned vector = ~regs->orig_ax;
- struct irq_desc *desc;
- unsigned irq;
- old_regs = set_irq_regs(regs);
- irq_enter();
- irq = __get_cpu_var(vector_irq)[vector];
- overflow = check_stack_overflow();
- desc = irq_to_desc(irq);
- if (unlikely(!desc)) {
- printk(KERN_EMERG "%s: cannot handle IRQ %d vector %#x cpu %d\n",
- __func__, irq, vector, smp_processor_id());
- BUG();
- }
- if (!execute_on_irq_stack(overflow, desc, irq)) {
- if (unlikely(overflow))
- print_stack_overflow();
- desc->handle_irq(irq, desc);
- }
- irq_exit();
- set_irq_regs(old_regs);
- return 1;
- }
- 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 = hardirq_ctx[smp_processor_id()];
- /*
- * 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;
- }
最后,思考一下标题中的问题:linux内核在中断路径内不能睡眠/调度的原因
Linux是以进程为调度单位的,调度器只看到进程内核栈,而看不到中断栈。在独立中断栈的模式下,如果linux内核在中断路径内发生了调度(从技术上讲,睡眠和调度是一个意思),那么linux将无法找到“回家的路”,未执行完的中断处理代码将再也无法获得执行机会。文章来自:http://www.cnblogs.com/hainange/
先把中断处理流程给出来
1.进入中断处理程序--->2.保存关键上下文---->3.开中断(sti指 令)--->4.进入中断处理程序的handler--->5.关中断(cli指令)---->6.写EOI寄存器(表示中断处理完 成)---->7.开中断。
硬中断:
对应于上图的1、2、3步骤,在这几个步骤中,所有中断是被屏蔽的,如果在这个时候睡眠了,操作系统不会收到任何中断(包括时钟中断),系统就基本处于瘫痪状态(例如调度器依赖的时钟节拍没有等等……)
软中断:
对应上图的4(当然,准确的说应该是4步骤的后面一点)。这个时候不能睡眠的关键是因为上下文。
大家知道操作系统以进程调度为单位,进程的运行在进程的上下文中,以进程描述符作为管理的数据结构。进程可以睡眠的原因是操作系统可以切换不同进程的上下文,进行调度操作,这些操作都以进程描述符为支持。
中断运行在中断上下文,没有一个所谓的中断描述符来描述它,它不是操作系统调度的单位。一旦在中断上下文中睡眠,首先无法切换上下文(因为没有中断描述符,当前上下文的状态得不到保存),其次,没有人来唤醒它,因为它不是操作系统的调度单位。
此外,中断的发生是非常非常频繁的,在一个中断睡眠期间,其它中断发生并睡眠了,那很容易就造成中断栈溢出导致系统崩溃。
如 果上述条件满足了(也就是有中断描述符,并成为调度器的调度单位,栈也不溢出了,理论上是可以做到中断睡眠的),中断是可以睡眠的,但会引起很多问题.例 如,你在时钟中断中睡眠了,那操作系统的时钟就乱了,调度器也失去了依据;例如,你在一个IPI(处理器间中断)中,其它CPU都在死循环等你答复,你确 睡眠了,那其它处理器也不工作了;例如,你在一个DMA中断中睡眠了,上面的进程还在同步的等待I/O的完成,性能就大大降低了……还可以举出很多例子。 所以,中断是一种紧急事务,需要操作系统立即处理,不是不能做到睡眠,是它没有理由睡眠。