为什么需要中断
计算机中有大量的硬件,它们之间通过各种总线连接,比如:
- CPU和内存通过某种专有的高速总线连接。
- 显卡和现代的SSD等高速设备通过PCIe连接到CPU。
- 鼠标、机械硬盘等缓慢的设备通过外设 I/O 总线连接到 CPU。
实际现代 CPU 将内存控制器集成于芯片,采用 DDR4/DDR5 通道,由内部互连(如 Intel UPI、AMD Infinity Fabric)管理多路内存访问。
graph LR
CPU((CPU))
内存[内存]
subgraph 专有高速总线
CPU <--双向传输--> 内存
end
subgraph PCI_Express["PCIe总线"]
PCIe总线[PCIe总线] --> CPU
显卡[[显卡]] --> PCIe总线
NVMe_SSD[[NVMe SSD]] --> PCIe总线
end
subgraph 外设总线["外设I/O总线(如USB/SATA)"]
外设控制器[I/O控制器] --> CPU
鼠标[[鼠标]] --> 外设控制器
机械硬盘[[机械硬盘]] --> 外设控制器
end
classDef cpu fill:#f9d71c,stroke:#333;
classDef memory fill:#66ccff,stroke:#333;
classDef pci fill:#90EE90,stroke:#333;
classDef storage fill:#ff9999,stroke:#333;
classDef io fill:#ffb366,stroke:#333;
class CPU cpu;
class 内存 memory;
class PCIe总线,显卡,NVMe_SSD pci;
class 机械硬盘 storage;
class 外设控制器,鼠标 io;
而这些硬件可以被抽象为接口和内部实现:
- 接口提供寄存器使得外部能够获取信息并操作硬件。
- 内部实现使用者无需关心。
假设,接口提供了两类寄存器:
- 状态寄存器(status):读取并查看当前的硬件状态。
- 命令寄存器(command):让硬件执行具体的命令和操作。
- 数据寄存器(data):发送或读取硬件相关的数据。
那么,一个典型的使用硬件的方式为:
|
|
不停的循环读取硬件状态被称为轮询(polling),显然这是浪费 CPU 时间的设计。就像之前并发控制中使用futex替换自旋锁一样,计算机也应该有某种方式主动通知程序任务已完成。
为此,工程师发明了中断。
什么是中断
中断是硬件设备完成任务后主动通知 CPU,CPU再跳转到操作系统预先设定好的中断处理程序或中断服务例程(ISR)。
sequenceDiagram
participant Device
participant CPU
participant ISR
Device->>CPU: 发送中断信号(INT)
CPU->>CPU: 保存上下文(寄存器状态)
CPU->>ISR: 执行中断服务例程
ISR->>Device: 读取状态/数据寄存器
ISR->>CPU: 恢复上下文
不过,轮询并非一定比中断差:假设我们有一个高速设备,只需CPU一次轮询的时间就能完成命令,那么使用中断反而会导致频繁的进程切换从而降低性能。因此,如果设备很快,轮询反而是更好的方法。
同时,还存在混合模式:初期轮询一小段时间,之后超时转为中断模式。
进一步加速——DMA
从内存往硬盘拷贝数据是十分常见并且比较简单的任务,因此工程师设计了 DMA 硬件来完成这项任务而无需 CPU 介入。
使用 DMA 时,操作系统指示内存的位置、大小以及目标设备。接着,DMA 就会开始传输数据,而操作系统和 CPU 就可以执行其他任务。当 DMA 完成时再抛出中断通知操作系统任务已经完成了。
现代 DMA 控制器支持在一次传输中描述多个非连续内存片段,减少一次次配置开销。
sequenceDiagram
participant 应用程序
participant CPU
participant DMA控制器
participant 硬盘
应用程序->>CPU: 调用write()函数
CPU->>DMA控制器: 配置传输参数(地址/长度)
CPU->>CPU: 立即返回执行其他任务
DMA控制器->>硬盘: 启动数据传输
硬盘-->>DMA控制器: 完成通知
DMA控制器->>CPU: 触发IRQ完成中断