跳转至

中断、DMA 与缓存层次

CPU 可以逐字节搬运设备数据,但这样会浪费大量指令并把计算核心变成昂贵的数据搬运工。DMA 让设备控制器直接在设备与内存之间传输数据,中断或轮询则用于报告完成和回收资源。

三种基本传输方式

程序控制 I/O

CPU 反复读写设备寄存器并搬运数据。实现简单,适合极少量控制信息;大块传输会占用大量 CPU。

中断驱动 I/O

CPU 启动操作后去执行其他任务,设备准备好时中断。减少空等,但若每个极小事件都中断,高事件率会形成中断风暴。

DMA

CPU/驱动准备内存缓冲和描述符,设备控制器成为总线主设备,在设备与内存间搬运。完成后通过中断或完成队列通知。

CPU 构造描述符
  -> 设备读取描述符
  -> DMA 传输数据
  -> 设备写完成状态
  -> 中断或轮询
  -> CPU 回收请求

DMA 减少按字节 CPU 搬运,但设置描述符、映射内存、处理中断和协议仍消耗 CPU。

描述符环

网卡和高速存储常使用环形队列:

  • 驱动在提交环放入缓冲地址、长度和标志。
  • 设备消费描述符并执行 DMA。
  • 设备在完成环或描述符状态中写结果。
  • 驱动批量回收并补充新缓冲。

生产者和消费者索引必须按内存序发布。若先更新可见尾指针,再把描述符内容写完整,设备可能读取半初始化请求。

中断入口与延后处理

设备发出中断后,快速处理通常:

  1. 识别和确认中断来源。
  2. 读取必要状态或屏蔽进一步通知。
  3. 安排延后处理。
  4. 尽快返回。

延后部分批量处理完成队列、协议栈、唤醒等待线程并重新启用通知。这样缩短高优先级路径,却增加少量排队延迟。

中断合并

若每完成一个包都中断,事件率 \(r\) 等于中断率,固定处理开销为 \(c_i\),仅入口开销就约为:

\[ CPU_{interrupt}=r c_i. \]

设备可按数量或定时器合并多个完成再中断。吞吐提高,但第一个完成要等待批次形成,增加延迟。自适应合并会根据负载调整。

轮询与混合模式

低负载时中断节能且避免空转;高负载时轮询可以批量处理并避免中断风暴。网络子系统常采用混合方式:中断通知有工作后,暂时关闭或抑制中断并轮询一批数据,队列清空再恢复中断。

这不是“中断和轮询谁更先进”,而是根据事件率选择固定开销更低的一方。

DMA 缓冲区的要求

设备看到的是总线地址或 I/O 虚拟地址,不一定等于 CPU 虚拟地址。驱动需要通过 DMA 映射接口获得设备可用地址,并满足:

  • 生命周期覆盖整个设备访问期间。
  • 对齐和长度符合设备要求。
  • 页面被固定或以系统允许方式映射,不能在传输时被随意回收。
  • 方向和缓存同步操作正确。
  • 设备只访问授权范围。

把普通栈指针直接交给设备通常错误:栈对象可能提前失效,虚拟地址也不是设备能理解的地址。

缓存一致性问题

在硬件缓存一致的系统中,DMA 与 CPU 缓存由平台保证基本一致,但仍需正确内存屏障和 DMA API 来排序描述符与完成状态。

在非一致平台上:

  • CPU 写给设备前,可能需要把脏缓存行写回内存。
  • 设备写入后,CPU 读取前可能需要使旧缓存行失效。

否则 CPU 和设备会看到同一缓冲的不同版本。

IOMMU

IOMMU 为设备 DMA 提供地址翻译与权限检查,类似 CPU MMU:

  • 设备使用 I/O 虚拟地址。
  • 内核配置映射到允许的物理页。
  • 越界 DMA 可被阻止和报告。

它提高隔离、支持设备直通和非连续缓冲映射,但增加映射管理、IOTLB 和失效成本。高性能系统会批量映射、复用缓冲或使用大页降低开销。

页缓存、设备缓存与 CPU 缓存

“缓存”一词对应不同层:

层次 缓存什么 由谁管理
CPU cache 最近内存缓存行 硬件
TLB 地址翻译 硬件与内核
页缓存 文件数据页 操作系统
设备控制器缓存 待写/常用块 控制器固件
介质内部缓存 闪存映射与数据 设备

应用的 write 返回时,数据可能只到页缓存;内核发出写后,可能只到易失设备缓存。持久化协议必须跨越所有相关层。

双缓冲与流水线

若只有一个缓冲,设备传输时 CPU 不能安全复用它。双缓冲允许:

设备 DMA 到 buffer A 时,CPU 处理 buffer B
设备切到 buffer B 时,CPU 处理 buffer A

理想稳定吞吐受较慢阶段限制:

\[ throughput\le\min(throughput_{device},throughput_{cpu}). \]

更多缓冲可以吸收抖动,却增加排队延迟和内存占用。最终仍需要背压。

Scatter-Gather I/O

逻辑数据可能分散在多个非连续页。scatter-gather 描述符让设备在一次请求中遍历多个片段,避免先复制到一块连续缓冲。

代价是描述符更多、设备段数有限、IOMMU 映射成本和边界条件更复杂。上层仍可能因协议头、校验和或加密需要访问数据。

错误恢复

设备可能:

  • 超时且不产生中断。
  • 返回校验、介质或总线错误。
  • 在热拔出时仍有未完成 DMA。
  • 固件失去响应,需要重置队列或设备。

驱动必须停止新请求、取消或等待 DMA、回收描述符、避免重复完成,并向上层传播可理解错误。重置路径和正常路径并发,是驱动最容易出错的区域之一。

安全风险

没有 IOMMU 或映射错误时,恶意/故障设备可 DMA 到任意物理内存,绕过 CPU 页表。设备直通给虚拟机时尤其要精确配置隔离。

驱动解析设备提供的长度、索引和状态时也不能盲目信任;设备固件、外设或虚拟设备都可能返回异常数据。

性能权衡

  • 小批次:低延迟,高中断和门铃开销。
  • 大批次:高吞吐,排队延迟更大。
  • 复制缓冲:生命周期简单,消耗内存带宽。
  • 零拷贝/固定页:减少复制,固定内存与回收更复杂。
  • 中断:空闲效率高,高负载开销大。
  • 轮询:低延迟可控,空闲时浪费 CPU 和能量。

性能优化应同时看吞吐、尾延迟、CPU 占用和功耗。

自测

  1. DMA 减少了哪类 CPU 工作,哪些工作仍存在?
  2. 为什么设备看到的地址不能直接使用用户虚拟地址?
  3. 中断合并怎样影响吞吐和延迟?
  4. IOMMU 与 CPU MMU 的共同点和对象区别是什么?
  5. write 返回后,数据可能停在哪些缓存层?
参考思路

DMA 避免逐字节搬运,提交、映射、协议和完成处理仍需 CPU。设备使用总线/I/O 地址。合并摊薄中断成本但等待成批。两者都翻译并检查权限,MMU 服务 CPU,IOMMU 服务设备。数据可能仍在用户库缓冲、页缓存、控制器缓存或介质内部缓存,取决于接口和时刻。