中断系统
中断系统
复习定位
中断是CPU和外部设备通信的主要机制——设备有事做完了主动通知CPU。从中断请求(IRQ)线→中断控制器→CPU→IDT查表→中断处理程序→IRET恢复——整条路径涉及多个硬件和OS组件。理解中断的完整流程是理解"为什么鼠标动一下CPU就要暂停当前工作"的基础。
中断为什么比轮询好
早期I/O方式——程序查询(Polling)——CPU不断读取键盘控制器的状态寄存器检查是否有按键按下。CPU的大部分时间花在检查"没有按键"上——浪费了大量算力。中断方式——设备在需要CPU注意时才主动告知——CPU在无事时执行其他任务。网络繁忙时每秒数万次中断——中断开销本身可能成为瓶颈——所以现代硬件(网卡)使用NAPI(中断+轮询结合)在中高负载时切换为轮询。
中断的分类
外部中断(硬件中断):由CPU以外的硬件设备产生。可屏蔽中断(INTR)——CPU可以通过cli指令禁止响应——典型如I/O设备的中断。不可屏蔽中断(NMI)——CPU必须立即响应——典型如硬件故障、奇偶校验错、看门狗定时器超时。
内部中断(异常):由CPU内部指令执行所致。故障(fault)——执行完出错的指令后修复并重新执行(缺页异常)。陷阱(trap)——用于系统调用——syscall指令后程序继续执行下一条指令。中止(abort)——严重系统错误——程序无法继续执行(如硬件校验错)。
中断控制器(8259A→I/O APIC)
8259A可编程中断控制器(PIC)有8根中断请求线(IR0-IR7)——两片级联可扩展至15个IR线。每个IRQ分配优先级(IRQ0=时钟最高)——可被CPU通过ICW(初始化命令字)和OCW(操作命令字)编程。8259A的流程:
1. 外设发IRQ信号到8259A的某个IR线
2. 8259A判断该IRQ未被屏蔽—检测优先级是否高于当前正在服务的中断
3. 8259A拉INTR线通知CPU—CPU在执行完当前指令后回复INTA脉冲
4. 8259A收到INTA—把IRQ对应的中断向量号(0-255)加载到数据总线
5. CPU读取中断向量号—根据IDT表找到中断门—跳转到中断处理程序现代x86用I/O APIC(高级可编程中断控制器)替代了8259A——每个CPU核有自己的Local APIC。I/O APIC支持256个中断向量、支持处理器间中断(IPI)——用于多核系统中核间通信和调度负载均衡。
中断响应流程(x86-64)
CPU响应中断时自动执行以下操作:
1. 从IDT加载中断门描述符(含目标段选择子和偏移)
2. 若中断门特权级低于当前CPL(当前特权级):从TSS加载目标特权级的栈指针(RSP)—切换到内核栈
3. 按顺序压栈:SS:RSP(如果切换了栈)、EFLAGS/RFLAGS、CS:RIP、错误码(如果有)
4. 清IF位—屏蔽后续可屏蔽中断(CPU自动在处理中断时将IF复位的操作可被保留—用`sti`可以提前开中断)
5. 跳转到中断门指定的CS:RIP开始执行中断服务程序中断返回指令iret/iretq——按逆序弹出RIP/CS/RFLAGS/RSP/SS——恢复中断前的执行流。注意IRETQ在弹出CS时要检查CPL是否发生变化——如果从内核态返回用户态需重新加载栈段(SS显示加载)。
中断优先级与嵌套
可屏蔽中断可被更高优先级的中断打断——形成中断嵌套。x86中cli/sti指令全局控制IF标志——IF=1且某IRQ未被IFR中的某位禁止时CPU才响应。在中断处理程序中,如果在早期就sti允许更高级的中断——处理期间可以被其他高优先级中断打断——反之在执行需要原子性(如更新重要的trap源数据结构)的代码时应该用cli关闭中断——完成后立即sti打开。
中断向量表(IDT)
x86-64的IDT有256个条目——每个条目16字节。条目类型分为中断门(Interrupt Gate,自动清IF不可被同优先级中断打断)、陷阱门(Trap Gate,不清IF——允许嵌套中断)和任务门(用于硬件任务切换——现代OS很少使用)。CPU根据中断向量号N→IDT基址+N×16→取出门描述符→检查DPL与CPL决定是否阻止用户态触发该中断。
Linux将IDT中0-31号向量用于CPU异常:0=除法错、3=断点(INT3)、6=非法操作码、8=双重错误、13=通用保护异常、14=缺页异常。32-255号用于硬件中断和系统调用——Linux把系统调用向量设为0x80。
复习检查
8259A级联——两片8259A最多可提供多少个IRQ——主片和从片的中断响应接线如何处理?
中断嵌套——如果低优先级中断处理程序正在执行而高优先级中断到来——CPU如何决定是否打断?
x86-64中iretq执行时弹出CS寄存器后——CPU需要检查什么条件并在条件满足时重新加载SS?
缺页异常属于fault还是trap——为什么fault可以在修复错误条件后重新执行出错的指令?
Linux为什么把系统调用入口设为向量0x80——INT 0x80执行时CPU从IDT[0x80]中读到了什么信息?DPL字段被设置为什么才能允许用户态程序触发这个中断?