共计 1112 个字符,预计需要花费 3 分钟才能阅读完成。
中断上下文的切换
进程上下文的切换
异常处理过程:
下面从逻辑上完整走一遍中断处理过程(结合中断上下文的切换,以定时器中断为例,假设从用户态进入中断):
1. 定时器连接在 8259A 可编程中断控制器 (PIC,Programmable Interrupt Controller) 的 0 号 IRQ 线上,0 号 IRQ 线对应 32+0=32 号中断向量。中断控制器又与 CPU 的 INTR 引脚相连。当定时器产生中断时,中断控制器把对应的中断向量 32 放到一个 I / O 端口上,从而允许 CPU 通过数据总线读到这个向量。然后 PIC 就向 CPU 的中断引脚发送一个低电平,即产生一个中断。CPU 对这个信号做应答,PIC 收到应答后,清 INTR 引脚。
2. 硬件保存现场:SS、SP、eflags、cs、eip,保存到被中断进程的内核堆栈中(tr 寄存器保存当前进程的 tss 段,而 tss 段里有最后一次访问内核栈的指针)。
3. 读 idtr 寄存器指向的中断描述符表 (idt) 的第 30 项,得到相应的中段描述符,并用中断描述符里的段选择符 (还要根据 gdtr 寄存器指向的全局描述符表 gdt 获取段选择符对应的段描述符) 和偏移量装载 CPU 的 cs 和 eip 寄存器。这样就进入到了中断处理程序的入口,CPU 开始执行中断处理程序入口的代码 ENTRY(interrupt)。
4. 而在系统初始化时,通过调用 init_IRQ()函数用 interrupt 数组的每一项来初始化 idt 表,而 interrupt 数组的每一项都是一样的内容,都是 interrupt。所以无论发生哪一种中断,都会跳转到一段汇编代码 ENTRY(interrupt)处,这就是每一个中断处理程序的入口。它首先在内核栈上 push 中断向量号,然后跳转到 common_interrupt 处。
5. common_interrupt 首先使用 SAVE_ALL 继续保存现场(按照 pt_regs 数据结构保存),然后无论哪一个中断都会调用 do_IRQ 函数,这个函数就一个参数即指向 pt_regs 数据结构的指针,使用 %eax 寄存器传递。
6. do_IRQ 函数使用全局数组 irq_desc,irq_desc 既是数组名也是数组中每个元素的数据类型。每个中断号对应一个 irq_desc,irq_desc 里包含 irqaction 链表,我们将每个设备对应的中断服务例程打包成 irqaction,并通过 setup_irq 函数将其加入相应的 irqaction 链表中。handle_IRQ_event()函数负责扫描 action 链表,依次执行,直到找到对应设备的中断服务例程,然后执行。
7. 跳转到 ret_from_intr。
所以在外部看来就是:定时器发生中断了,定时器中断服务例程执行。