一、中断的响应和服务
在前面一篇博文中,我们分析了i386 CPU的中断机制和内核中有关的初始化,现在我们进一步分析中断的响应过程和服务(和异常的响应机制不同)。我们假设外设驱动都已经完成了初始化,并且已把相应的中断服务程序挂入到特定的中断请求队列中,系统正在用户空间正常运行,并且某个外设已经产生了一次中断请求,该请求通过中断控制器到达了CPU的“中断请求”引线INTR。CPU从中断控制器取得中断向量,然后根据具体的中断向量从中断向量表IDT中找到相应的表项,该表项为一个中断门,然后通过该中断门的设置到达了该通道的总服务程序的入口IRQxx_interrupt,即为interrupt[]中的元素(参考前面一篇文章),这里为了方便说明,再次给出IRQxx_interrupt等的源码,如下:
pushl $vector-256
jmp common_interrupt
/* common_interrupt如下 */
common_interrupt:
SAVE_ALL
movl %esp,%eax
call do_IRQ
jmp ret_from_intr
/* SAVE_ALL如下 */
#define SAVE_ALL \
cld; \
pushl %es; \
pushl %ds; \
pushl %eax; \
pushl %ebp; \
pushl %edi; \
pushl %esi; \
pushl %edx; \
pushl %ecx; \
pushl %ebx; \
movl $(__USER_DS), %edx; \
movl %edx, %ds; \
movl %edx, %es;
在SAVE_ALL后,堆栈如图:
接下来将栈顶指针esp存到eax中,然后执行do_IRQ函数,该函数声明如下:
fastcall unsigned int do_IRQ(struct pt_regs *regs)
/* 关键字regparm表示函数到eax寄存器中去找到参数regs的值 */
#define fastcall __attribute__((regparm(3)))
/* 到此可以清楚的看到,regs指向的内容为上图中系统堆栈中的内容 */
struct pt_regs {
long ebx;
long ecx;
long edx;
long esi;
long edi;
long ebp;
long eax;
int xds;
int xes;
long orig_eax;
long eip;
int xcs;
long eflags;
long esp;
int xss;
};
接下来分析do_IRQ()函数,如下:
/**
* do_IRQ执行与一个中断相关的所有中断服务例程.
*/
fastcall unsigned int do_IRQ(struct pt_regs *regs)
{
/* 通过orig_eax读回并屏蔽掉高位,又得到中断号irq */
int irq = regs->orig_eax & 0xff;
#ifdef CONFIG_4KSTACKS
union irq_ctx *curctx, *irqctx;
u32 *isp;
#endif
/**
* irq_enter增加中断嵌套计数
*/
irq_enter();
#ifdef CONFIG_DEBUG_STACKOVERFLOW
/* Debugging check for stack overflow: is there less than 1KB free? */
{
long esp;
__asm__ __volatile__("andl %%esp,%0" :
"=r" (esp) : "0" (THREAD_SIZE - 1));
if (unlikely(esp < (sizeof(struct thread_info) + STACK_WARN))) {
printk("do_IRQ: stack overflow: %ld\n",
esp - sizeof(struct thread_info));
dump_stack();
}
}
#endif
#if