中断系统到底是怎么做的?

从"设备扯了一下 CPU 的袖子"到"你的程序收到信号",逐层拆开硬件、操作系统、上层软件三层各自干了什么、又是怎么"接入"的。下面有可交互的时序动画,建议点开看一遍。

先给一句话定义

中断(Interrupt)是硬件或软件发出的一个异步信号,告诉 CPU:"有件更紧急的事要马上处理"。CPU 暂停当前手头的工作,跳去执行一段叫作中断处理程序(ISR)的代码,处理完再回来继续刚才的任务。

它解决的核心矛盾是:CPU 跑得极快、外设(键盘/网卡/磁盘)慢得离谱,不可能让 CPU 干等着。中断让"快"和"慢"解耦——外设准备好再叫 CPU,CPU 平时该干啥干啥。

分类中断有哪些种类

先分清"谁来打断、为什么打断",后面三层才接得上。

按来源分

  • 外部硬件中断(IRQ):外设通过中断线拉高电平/边沿触发,如键盘、网卡、磁盘完成。
  • 内部异常(Exception):CPU 自己执行指令时出的事——除零、缺页、非法指令、断点。本质是"同步中断"。
  • 软件中断 / 陷阱(Trap / Syscall):程序故意用 int 0x80syscall 指令主动陷入内核,比如请求系统调用。

按可屏蔽性分

  • 可屏蔽中断(INTR):大多数 IRQ,可用 CLI/STI 或 CPU 的 IF 标志位关闭,处理不急。
  • 不可屏蔽中断(NMI):内存校验错、看门狗超时等致命硬件错误,关不掉,必须处理。
  • 中断 vs 异常 vs 陷阱:中断=异步、来自外部;异常=同步、指令出错;陷阱=同步、主动陷入。三者共用同一套"向量→处理程序"机制。

全景三层分别在干什么

一张图看全"信号怎么来、CPU 跳去哪、软件怎么接"。颜色对应下面的分章。

① 硬件层(设备 + 中断控制器) 键盘/网卡/磁盘 → 拉中断线 → 8259A/PIC 或 APIC 仲裁、编号(IRQ 向量号)→ 拉 CPU 的 INTR 引脚 接入动作:设备连中断线;控制器编程(边沿/电平触发、优先级、目标 CPU);向量号映射 INTR 引脚 ─┐ ② 操作系统层(CPU + 内核) CPU 查 IDT[向量] → 保存现场 → 跳 ISR → 上半部(关中断速处理)/ 下半部(软中断/ tasklet/ 工作队列) 接入动作:内核启动时建 IDT、注册 ISR;驱动用 request_irq() 把自己的处理函数挂到某 IRQ 上 ← 查 IDT ─┘ ③ 上层软件层(驱动 / 运行时 / 应用) 驱动读寄存器取数据 → 交给协议栈/文件系统 → 用信号(SIGIO)、回调、epoll 事件通知用户程序 接入动作:注册驱动回调;应用注册信号处理函数 / 监听 fd;或主动 syscall 走"陷阱"那条路

图:中断从硬件触发,经中断控制器送给 CPU,CPU 在内核里查表跳转到处理程序,最后由驱动/系统把结果送回上层软件。三层的"接入点"各不相同。

硬件层信号是怎么传到 CPU 的

关键词:中断线、中断请求 IRQ、中断控制器(PIC/APIC)、中断向量号、CPU 的 INTR 引脚。

核心零件

  • 中断线 / IRQ:设备想说话就拉这根线。早期只有 15 条(8259A 级联),不够用。
  • PIC(8259A):老式可编程中断控制器,负责接收多路 IRQ、按优先级仲裁、转发给 CPU。单核时代的标配。
  • APIC(I/O APIC + Local APIC):现代多核方案。I/O APIC 收外部中断,可把中断定向分发到指定 CPU 核;每核自带 Local APIC。支持更多中断、负载均衡。
  • 中断向量号:控制器给每个中断编个号(如 32~255 留给外部 IRQ),CPU 拿这个号去查表。

硬件层的"接入操作"

  • 设备侧:把中断请求连到指定 IRQ 线(电路设计 / 设备树 / ACPI 描述)。
  • 控制器编程:设置触发方式(边沿触发 vs 电平触发)、优先级目标 CPU(APIC 的 Redirection Table)。
  • 屏蔽/使能:用 IMR(中断屏蔽寄存器)或 CLI/STI 控制哪些中断暂时不理。
  • 中断确认(ACK):CPU 收到后回一个信号,控制器才释放总线、给出向量号——保证中断不被丢失。
CPU INTR 引脚 I/O APIC 仲裁+向量号+目标核 键盘 IRQ1 网卡 IRQn 磁盘 IRQm 向量号+INTA 设备拉线 → APIC 仲裁/编号 → CPU 查向量

图:多个设备共享 APIC,控制器决定优先级并把中断定向到某个核,附带向量号交给 CPU。

边沿触发 vs 电平触发:边沿=信号跳变那一刻触发一次(怕丢);电平=信号保持有效期间持续请求(怕重复进)。现代大多用电平+ACK 配合,确保既不丢也不重

操作系统层CPU 跳去哪、到底干了啥

关键词:中断向量表(IDT)、现场保存、ISR、上半部/下半部、中断上下文。

CPU 收到中断后的固定动作

  1. 看 IF 标志是否允许可屏蔽中断(NMI 无视)。
  2. 关中断(或至少屏蔽同级),保证处理不被打断。
  3. 保存现场:把当前 PC、寄存器压栈(陷入内核栈)。
  4. 用向量号查 IDT,取出ISR入口地址,跳转执行。
  5. ISR 执行完 IRET 恢复现场、重新开中断、回到被中断的程序。

OS 层的"接入操作"

  • 建表:内核启动时把 IDT 填满,每个向量指向对应处理入口。
  • 注册 ISR:驱动用 request_irq(irq, handler, flags, name, dev) 把自己的函数挂到某 IRQ。
  • 共享中断:多个设备可共用一条 IRQ(如 PCI),触发时内核挨个调用该 IRQ 上的所有 handler,由 handler 自己判断"是不是我的设备"。
  • 上半部 / 下半部(见下):把"必须马上做"和"可以稍后做"拆开。
IDT 中断向量表 0 除零异常 14 缺页异常 32 时钟 IRQ0 n 网卡 ISR ←命中 … 更多向量 ① 查 IDT[n] ② 保存现场(压栈) ③ 上半部(关中断) ④ 下半部(软中断) 处理完成 · 取设备数据 / 清中断 · 唤醒等待进程 / 入队 · IRET 恢复现场 → 回原程序 下半部把"慢活"延后,缩短关中断时间

图:CPU 拿向量号查 IDT,命中网卡 ISR,先上半部速处理(关中断),再靠下半部(softirq/tasklet/workqueue)做耗时工作,最后 IRET 返回。

为什么分上下半部?ISR 里是中断上下文——不能睡眠、不能调调度器,且关中断期间别的活都卡着。所以"必须立刻干"的(读状态、清中断、取数据)放上半部;"可以稍后"的(协议处理、拷贝)推到下半部,开中断后再跑,系统才不会被卡死。

上层软件层你(程序)是怎么接入的

关键词:设备驱动、read/mmap、epoll/select、信号(SIGIO)、回调、系统调用陷阱。

驱动(内核态)

  • request_irq 注册 ISR
  • ISR 里读设备寄存器/ 取 DMA 数据
  • 把数据放入内核缓冲区 / 通知队列
  • 唤醒在该设备上睡眠的进程

运行时 / 框架

  • epoll 把"可读"事件通知应用
  • signal(SIGIO) 异步信号
  • 中断回调 / 事件循环(Node、libuv)
  • 游戏引擎的"垂直同步"VSync 中断

应用(用户态)

  • 调用 read()/recv() 拿数据
  • 注册 信号处理函数
  • 监听文件描述符事件
  • 主动 syscall 走"陷阱"路
两种"接入姿势":(1)被动接收——设备中断自动把你唤醒,典型如网卡来包、键盘按键;(2)主动陷入——你的程序主动用 syscall/int 0x80 触发"软件中断/陷阱",让内核替你干活(读文件、发网络包)。后者虽叫"中断",其实是同步的、由你自己发起的。

一段 Linux 驱动注册 ISR 的骨架(示意)

// 驱动里把处理函数挂到某个 IRQ 上 static irqreturn_t my_isr(int irq, void *dev) { // 上半部:读状态、清中断、取数据 struct mydev *d = dev; u32 status = readl(d->regs + STATUS); if (!(status & RX_READY)) return IRQ_NONE; // 不是我的设备 copy_to_ring(d, status); writel(RX_READY, d->regs + STATUS); // 清中断 return IRQ_HANDLED; } // 初始化时接入:这就是"上层软件"在中断系统里的挂载点 request_irq(d->irq, my_isr, IRQF_SHARED, "mydev", d);

交互一次中断的完整旅程(点开看)

下面是一段可步进的时序动画。点「下一步」看信号从设备一路走到你的程序;「自动播放」会自己跑一遍。

设备 APIC CPU/内核 驱动 应用
硬件 内核 软件
步骤 0 / 9

提示:动画里"设备→APIC→CPU→驱动→应用"的传递,正是前面三层的串联。CPU 那条会显示"保存现场→查 IDT→上半部→下半部→IRET"。

对比中断 vs 轮询 vs DMA

三种让 CPU 与外设协作的机制,常搭配使用,不是一个取代另一个。

机制思路优点缺点典型场景
轮询 PollingCPU 反复读状态寄存器"好了没"实现简单、无延迟抖动、可控空转浪费 CPU、响应慢秒级慢设备、实时性要求极高的控制循环
中断 Interrupt设备就绪主动通知 CPUCPU 不空等、响应快、省算力高频中断时上下文切换开销大、可能丢/重键盘、网卡、磁盘完成等中低频事件
DMA设备直接写内存,不经 CPU搬运数据零 CPU 占用需总线仲裁、配置复杂磁盘/网卡大量数据搬移(常与中断配合:DMA 完再中断通知 CPU)
它们怎么配合:网卡收到包 → DMA 把包直接搬进内存 → 搬完发一个中断叫醒 CPU → 驱动 ISR 处理 → 上层取走。DMA 解决"搬数据",中断解决"通知时机",轮询只在少数慢设备上单独用。

答疑常见疑问

中断会丢失吗?

控制器有锁存,边沿来了先记下;但同一 IRQ 在 ISR 还没 ACK 时又来一次,可能合并。所以驱动要"清中断+读状态"配对,确保处理完所有pending源。

多核下中断给谁?

APIC 的 Redirection Table 可指定目标核(亲和性),也可做负载均衡(如把网卡中断分摊到不同核),避免单核被打爆。

系统调用算中断吗?

机制同源(都走"向量→处理程序"),但它是同步陷阱,由程序主动发起、立刻进入内核,和异步外部中断是两回事。现代多用专用 syscall 指令而非 int 0x80

为什么 ISR 里不能睡眠?

ISR 不在任何进程上下文里、没有"可被调度"的身份,睡眠会让调度器无处可切。所以耗时操作必须丢到下半部(可睡眠的 workqueue)去做。