3、中断处理流程:中断响应、上下文保存、中断分发、中断处理、上下文恢复
好,咱们接着往下聊。上一节我讲了中断向量表和中断控制器,算是把中断的“硬件骨架”搭起来了。这一节,咱们要真正走一遍中断处理的完整流程。
说白了,就是CPU收到中断信号后,从“正在干活”到“去处理中断”,再到“回来继续干活”的整个过程。我把它拆成五个阶段:中断响应、上下文保存、中断分发、中断处理、上下文恢复。
这五个阶段,缺一不可。任何一个环节出问题,系统就可能崩溃。我在做嵌入式项目时,就遇到过上下文保存不完整导致系统跑飞的情况,那叫一个头疼。
3.1 中断响应——CPU的“本能反应”
中断响应是硬件自动完成的。CPU检测到中断信号后,会做以下几件事:
- 关中断:防止新的中断打扰当前的中断处理。
- 保存当前PC值:把正在执行的指令地址压栈,这样中断返回时才知道从哪继续。
- 跳转到中断向量表:根据中断号,找到对应的中断服务程序入口地址。
嗯,这里要注意:不同架构的CPU,中断响应的细节略有不同。比如ARM Cortex-M系列,硬件会自动保存一部分寄存器到栈中,而x86架构则需要软件手动保存。
3.2 上下文保存——把“现场”保护好
中断响应后,CPU就跳到了中断服务程序。但这时候,CPU的寄存器里还存着被中断任务的“现场数据”。如果不保存,等中断处理完回来,任务就乱套了。
上下文保存要保存哪些东西?我列个清单:
| 保存内容 | 说明 |
|---|---|
| 通用寄存器 | R0-R12(ARM)或EAX、EBX等(x86) |
| 程序状态寄存器 | CPSR(ARM)或EFLAGS(x86) |
| 栈指针 | SP,指向当前栈顶 |
| 链接寄存器 | LR(ARM)或返回地址 |
在微内核中,我习惯用内核栈来保存上下文。每个线程都有自己的内核栈,这样中断嵌套时不会互相干扰。
// 伪代码:上下文保存
void interrupt_entry(void) {
// 保存通用寄存器到栈中
push_all_registers();
// 保存程序状态寄存器
push_psr();
// 保存栈指针
push_sp();
// 现在可以安全地调用中断处理函数了
handle_interrupt();
}
3.3 中断分发——找到“谁该干活”
上下文保存完后,就要决定这个中断到底该由谁处理。在微内核中,中断分发通常由中断管理器负责。
分发策略一般有两种:
- 静态分发:每个中断号固定对应一个处理函数。简单,但不灵活。
- 动态分发:根据中断优先级、当前CPU负载等因素,动态选择处理函数。微内核常用这种方式。
我个人比较推荐动态分发。你想想看,如果高优先级中断来了,但当前CPU正在处理一个不太紧急的任务,动态分发可以把中断交给另一个空闲的CPU核心去处理。
分发过程大致是这样的:
- 中断管理器读取中断号。
- 查找中断号对应的处理函数表。
- 如果该中断有注册的处理函数,就调用它。
- 如果没有,就调用默认的“未处理中断”函数(通常是打印错误信息)。
3.4 中断处理——真正的“干活”阶段
到了这一步,才真正开始处理中断。中断处理函数(ISR)要完成具体的业务逻辑。
在微内核中,中断处理有个重要原则:快进快出。ISR里不要做耗时操作,比如文件读写、网络通信等。这些应该交给内核线程去完成。
我一般把中断处理分成两部分:
- 上半部(Top Half):在ISR中执行,只做最紧急的事,比如读取硬件寄存器、清除中断标志。
- 下半部(Bottom Half):推迟到内核线程中执行,处理耗时的逻辑。
// 伪代码:中断处理函数
void timer_isr(void) {
// 上半部:清除定时器中断标志
clear_timer_interrupt_flag();
// 记录中断发生时间
current_time = read_timer_counter();
// 下半部:通知内核线程处理时间片轮转
schedule_bottom_half(timer_tick_handler);
}
3.5 上下文恢复——“回到现场”
中断处理完了,该回去了。上下文恢复就是上下文保存的逆操作。
恢复过程:
- 从栈中弹出之前保存的寄存器值。
- 恢复程序状态寄存器。
- 恢复PC值(也就是返回地址)。
- 开中断(如果之前关了的话)。
这里有个细节:如果中断处理过程中发生了任务切换(比如时间片到了),那恢复的就不是原来的任务上下文了,而是新任务的上下文。这就是操作系统的调度机制在起作用。
// 伪代码:上下文恢复
void interrupt_exit(void) {
// 检查是否需要任务切换
if (need_reschedule) {
// 切换到新任务的上下文
switch_to_next_task();
}
// 恢复通用寄存器
pop_all_registers();
// 恢复程序状态寄存器
pop_psr();
// 恢复PC,返回被中断的任务
return_from_interrupt();
}
3.6 完整流程图
我把整个流程画成了SVG图,方便你理解:
3.7 总结
好了,中断处理流程就讲完了。我再用几句话帮你捋一捋:
- 中断响应:硬件自动完成,保存PC、关中断、跳转。
- 上下文保存:保存CPU寄存器,保护现场。
- 中断分发:找到对应的处理函数。
- 中断处理:执行具体的业务逻辑,要快进快出。
- 上下文恢复:恢复现场,继续执行被中断的任务。
下一节,我会讲中断嵌套和优先级管理。到时候咱们再聊聊,多个中断同时来了怎么办。嗯,那才是真正考验微内核设计能力的地方。