先给一句话定义
中断(Interrupt)是硬件或软件发出的一个异步信号,告诉 CPU:"有件更紧急的事要马上处理"。CPU 暂停当前手头的工作,跳去执行一段叫作中断处理程序(ISR)的代码,处理完再回来继续刚才的任务。
它解决的核心矛盾是:CPU 跑得极快、外设(键盘/网卡/磁盘)慢得离谱,不可能让 CPU 干等着。中断让"快"和"慢"解耦——外设准备好再叫 CPU,CPU 平时该干啥干啥。
分类中断有哪些种类
先分清"谁来打断、为什么打断",后面三层才接得上。
按来源分
- 外部硬件中断(IRQ):外设通过中断线拉高电平/边沿触发,如键盘、网卡、磁盘完成。
- 内部异常(Exception):CPU 自己执行指令时出的事——除零、缺页、非法指令、断点。本质是"同步中断"。
- 软件中断 / 陷阱(Trap / Syscall):程序故意用 int 0x80 或 syscall 指令主动陷入内核,比如请求系统调用。
按可屏蔽性分
- 可屏蔽中断(INTR):大多数 IRQ,可用 CLI/STI 或 CPU 的 IF 标志位关闭,处理不急。
- 不可屏蔽中断(NMI):内存校验错、看门狗超时等致命硬件错误,关不掉,必须处理。
- 中断 vs 异常 vs 陷阱:中断=异步、来自外部;异常=同步、指令出错;陷阱=同步、主动陷入。三者共用同一套"向量→处理程序"机制。
全景三层分别在干什么
一张图看全"信号怎么来、CPU 跳去哪、软件怎么接"。颜色对应下面的分章。
图:中断从硬件触发,经中断控制器送给 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 收到后回一个信号,控制器才释放总线、给出向量号——保证中断不被丢失。
图:多个设备共享 APIC,控制器决定优先级并把中断定向到某个核,附带向量号交给 CPU。
边沿触发 vs 电平触发:边沿=信号跳变那一刻触发一次(怕丢);电平=信号保持有效期间持续请求(怕重复进)。现代大多用电平+ACK 配合,确保既不丢也不重。
操作系统层CPU 跳去哪、到底干了啥
关键词:中断向量表(IDT)、现场保存、ISR、上半部/下半部、中断上下文。
CPU 收到中断后的固定动作
- 看 IF 标志是否允许可屏蔽中断(NMI 无视)。
- 关中断(或至少屏蔽同级),保证处理不被打断。
- 保存现场:把当前 PC、寄存器压栈(陷入内核栈)。
- 用向量号查 IDT,取出ISR入口地址,跳转执行。
- ISR 执行完 IRET 恢复现场、重新开中断、回到被中断的程序。
OS 层的"接入操作"
- 建表:内核启动时把 IDT 填满,每个向量指向对应处理入口。
- 注册 ISR:驱动用 request_irq(irq, handler, flags, name, dev) 把自己的函数挂到某 IRQ。
- 共享中断:多个设备可共用一条 IRQ(如 PCI),触发时内核挨个调用该 IRQ 上的所有 handler,由 handler 自己判断"是不是我的设备"。
- 上半部 / 下半部(见下):把"必须马上做"和"可以稍后做"拆开。
图: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);
交互一次中断的完整旅程(点开看)
下面是一段可步进的时序动画。点「下一步」看信号从设备一路走到你的程序;「自动播放」会自己跑一遍。
步骤 0 / 9
提示:动画里"设备→APIC→CPU→驱动→应用"的传递,正是前面三层的串联。CPU 那条会显示"保存现场→查 IDT→上半部→下半部→IRET"。
对比中断 vs 轮询 vs DMA
三种让 CPU 与外设协作的机制,常搭配使用,不是一个取代另一个。
| 机制 | 思路 | 优点 | 缺点 | 典型场景 |
| 轮询 Polling | CPU 反复读状态寄存器"好了没" | 实现简单、无延迟抖动、可控 | 空转浪费 CPU、响应慢 | 秒级慢设备、实时性要求极高的控制循环 |
| 中断 Interrupt | 设备就绪主动通知 CPU | CPU 不空等、响应快、省算力 | 高频中断时上下文切换开销大、可能丢/重 | 键盘、网卡、磁盘完成等中低频事件 |
| DMA | 设备直接写内存,不经 CPU | 搬运数据零 CPU 占用 | 需总线仲裁、配置复杂 | 磁盘/网卡大量数据搬移(常与中断配合:DMA 完再中断通知 CPU) |
它们怎么配合:网卡收到包 → DMA 把包直接搬进内存 → 搬完发一个中断叫醒 CPU → 驱动 ISR 处理 → 上层取走。DMA 解决"搬数据",中断解决"通知时机",轮询只在少数慢设备上单独用。
答疑常见疑问
中断会丢失吗?
控制器有锁存,边沿来了先记下;但同一 IRQ 在 ISR 还没 ACK 时又来一次,可能合并。所以驱动要"清中断+读状态"配对,确保处理完所有pending源。
多核下中断给谁?
APIC 的 Redirection Table 可指定目标核(亲和性),也可做负载均衡(如把网卡中断分摊到不同核),避免单核被打爆。
系统调用算中断吗?
机制同源(都走"向量→处理程序"),但它是同步陷阱,由程序主动发起、立刻进入内核,和异步外部中断是两回事。现代多用专用 syscall 指令而非 int 0x80。
为什么 ISR 里不能睡眠?
ISR 不在任何进程上下文里、没有"可被调度"的身份,睡眠会让调度器无处可切。所以耗时操作必须丢到下半部(可睡眠的 workqueue)去做。