当你从硬盘拷贝大文件时,CPU 是否全程忙碌?当网卡收到数据包时,是谁负责搬进内存?答案就是 DMA(Direct Memory Access,直接存储器访问)。今天,我们就来拆解这项让外设“零 CPU 干预”直接访问内存的底层技术。
在计算机体系结构中,CPU 并非万能。如果每次数据搬运都需要 CPU 亲自出马,那么系统性能将大打折扣——尤其是在高速外设(如 SSD、万兆网卡、GPU)场景下。DMA 技术正是为了解决这个瓶颈而生。本文将从原理、工作模式、控制器结构到现代演进,带你彻底搞懂 DMA。
一、为什么需要 DMA?
在没有 DMA 的老式系统中,外设与内存之间的数据传输流程如下(以读磁盘为例):
- CPU 发出读取指令。
- 磁盘控制器将数据读入内部缓冲区。
- 磁盘控制器向 CPU 发起中断,告知数据已准备好。
- CPU 将数据从磁盘缓冲区逐字节搬移到内存。
- 重复步骤 3-4 直到所有数据传输完成。
问题:整个过程 CPU 充当“搬运工”,无法执行其他任务,尤其对于大数据量传输,CPU 占用率极高,系统吞吐量大幅下降。
DMA 解决方案:引入DMA 控制器(DMAC),由它接管数据搬运工作。CPU 只需告诉 DMAC“从哪里来、到哪里去、搬多少”,然后继续执行其他任务。传输完成后,DMAC 再通知 CPU。
二、DMA 的工作原理
2.1 DMA 控制器结构
一个典型的 DMAC 包含以下寄存器:
| 寄存器 | 作用 |
|---|---|
| 源地址寄存器(SAR) | 存放数据来源地址(内存或外设 I/O 接口) |
| 目标地址寄存器(DAR) | 存放数据去向地址 |
| 字节计数寄存器(BCR) | 需要传输的字节数 |
| 控制/状态寄存器(CSR) | 控制传输方向、传输模式、中断使能,以及状态标志 |
此外,DMAC 内部有地址递增逻辑、总线请求/应答逻辑,以及用于临时缓存的数据缓冲器。
2.2 单字节传输与块传输
DMAC 支持两种基本传输模式:
- 单字节传输(Cycle Steal):每次传输一个字节/字后,释放总线,让 CPU 或其他 DMA 设备使用。适用于速度较慢的外设。
- 块传输(Burst Mode):DMAC 获得总线控制权后,连续传输整个数据块,期间不释放总线。适用于高速外设(如 SSD、网卡)。
2.3 典型工作流程(以块传输为例)
┌──────┐ 1.配置DMAC ┌────────┐ │ CPU │──────────────>│ DMAC │ └──────┘ └────┬───┘ ^ │ 2.请求总线 │ v │ ┌──────┐ │ 3.总线授予 │ 总线 │ │<──────────────────│ 仲裁 │ │ └──────┘ │ │ │ │ 4.传输数据 │ v │ ┌─────────┐ │ │ 内存/IO │ │ └────┬────┘ │ │ └─────────5.中断─────────┘流程图解(ASCII):
- CPU 设置 DMAC 的源地址、目标地址、字节计数,以及传输模式,然后启动 DMAC。
- DMAC 向总线仲裁器发送
HOLD信号(总线请求)。 - CPU 在适当时候回复
HLDA(总线应答),并释放地址总线、数据总线、控制总线的控制权。 - DMAC 接管总线,将数据从源地址逐字搬到目标地址。每次传输后 SAR/DAR 自动递增,BCR 递减。
- 当 BCR 降为 0,DMAC 发送中断信号给 CPU,报告传输完成。CPU 重新获得总线控制权。
2.4 非内存到内存传输
通常 DMA 用于外设与内存之间(如磁盘→内存),但也可用于内存到内存(例如大块内存拷贝)。不过现代 CPU 的内存拷贝速度更快,所以内存到内存 DMA 不常用。
三、DMA 的工作模式
除了单字节和块传输,还有以下几种常见模式:
| 模式 | 描述 | 适用场景 |
|---|---|---|
| 轮询/单次 | 一次传输一个数据字,然后重新仲裁 | 低速外设 |
| 请求模式 | 外设每次断言 DRQ(数据请求)就传输一个单位 | 需要流控制的外设(如声卡) |
| 级联模式 | 多个 DMA 控制器串联,扩展通道数 | 多个高速外设 |
| 自动初始化模式 | 传输完成后自动重装载寄存器的初始值,重复相同传输 | 周期性数据采集 |
| 多通道 DMA | 一个 DMAC 支持多个独立通道,可并行处理多个传输 | 多设备系统(如 PC 芯片组) |
四、DMA 与 CPU 的交互细节
4.1 总线仲裁
DMA 设备需要获得总线控制权才能访问内存。仲裁方式通常与总线架构相关:
- 集中仲裁:一个中心仲裁器(如北桥或芯片组中的 DMA 控制器)决定谁获得总线。
- 分布式仲裁:每个设备都有总线优先级,通过菊花链或独立请求线竞争。
4.2 缓存一致性
现代 CPU 有多级缓存。当 DMA 将数据写入内存时,缓存中可能存有旧数据(缓存不一致)。解决方案:
- 非缓存内存:为 DMA 缓冲区设置内存区域为不可缓存(如 Linux 中的
dma_alloc_coherent)。 - 缓存刷新/无效:DMA 传输前,CPU 刷新缓存;传输后,CPU 使无效缓存行。
- 硬件一致性管理:较新架构(如 x86 的 Intel VT-d、ARM 的 SMMU)支持 I/O 一致性,硬件自动维护缓存一致性。
五、实战案例:磁盘读数据到内存
- CPU 初始化 DMA 控制器:
- 源地址 = 磁盘控制器的数据寄存器端口(I/O 地址)
- 目标地址 = 内存缓冲区首地址
- 字节计数 = 4096 字节
- 控制字:读操作,块传输,传输完成后中断
- CPU 启动 DMA,然后执行其他任务。
- 磁盘控制器将数据准备好到内部 FIFO,向 DMAC 发送 DMA 请求(DRQ)。
- DMAC 获得总线控制权,读取磁盘 FIFO,写入内存,每次传输后地址+1,计数-1。
- 传输完 4096 字节后,DMAC 向 CPU 发送中断。
- CPU 响应中断,知道数据已可用,继续处理。
六、DMA 的优缺点
优点
- 降低 CPU 占用率:CPU 从繁重的数据搬运中解放,可以并发执行计算任务。
- 提高吞吐量:DMA 传输速度通常比 CPU 搬运用时更短(专用硬件流水线)。
- 支持高速外设:如 NVMe SSD、万兆网卡,没有 DMA 无法达到标称速度。
缺点
- 硬件成本:需要 DMA 控制器,以及总线仲裁逻辑。
- 缓存一致性复杂:需要额外处理机制。
- 总线竞争:大量 DMA 传输可能抢占总线,影响 CPU 访问内存的延迟。
七、现代 DMA 技术演进
7.1 RDMA(Remote Direct Memory Access)
RDMA 允许数据在网络中直接从一个服务器的内存传输到另一台服务器的内存,完全绕过双方 CPU 和操作系统内核。典型实现:InfiniBand、RoCE(RDMA over Converged Ethernet)、iWARP。多用于高性能计算(HPC)和分布式存储。
7.2 IOMMU(I/O Memory Management Unit)
IOMMU 是类似 CPU 的 MMU 的设备,为 I/O 设备提供地址转换能力。主要作用:
- 设备虚拟化:允许虚拟机直接访问物理设备,IOMMU 将设备的 DMA 地址映射到虚拟机物理地址。
- 内存保护:限制外设只能访问特定的内存区域,提高安全性。
- 绕过内核缓冲区:零拷贝网络传输(如 DPDK)利用 IOMMU 将数据直接从网卡传输到用户态内存。
7.3 共享虚拟内存(SVM)
在 GPU 和加速器中,SVM 允许 CPU 与设备共享同一页表,设备可以直接访问 CPU 的虚拟内存地址,无需显式映射物理地址和 DMA 复制。NVIDIA CUDA 的 Unified Memory 就是类似概念。
八、常见问题与答案
Q1:DMA 和中断的关系?
A:DMA 用于数据传输,中断用于告知传输完成。二者协同工作:传输过程中无需中断,结束后触发一次中断即可。
Q2:所有外设都支持 DMA 吗?
A:不是。低速设备(如键盘、鼠标)依靠中断 + PIO(Programmed I/O)就足够。高速块设备(磁盘、网卡)、音视频设备、GPU 等则必须支持 DMA。
Q3:DMA 传输数据时,CPU 能同时访问内存吗?
A:如果总线是共享的,DMA 占用总线时 CPU 无法访问内存(但 CPU 可以执行内部指令或访问缓存)。多总线架构(如分离的 CPU 本地总线和 I/O 总线)可缓解此问题,但通常仍需等待。
Q4:如何编写使用 DMA 的驱动?
A:在 Linux 内核中,使用dma_map_single()映射内存,设置 DMA 描述符,配置硬件寄存器,然后等待中断。用户态可通过ioctl或mmap配合零拷贝技术。
九、总结
DMA 技术是高性能 I/O 的基石。它让外设能够绕过 CPU 直通内存,大大释放了 CPU 的算力。从早期的 ISA DMA 控制器到现代的 PCIe BM(Bus Master)设备,再到 RDMA 和 IOMMU,DMA 的演进始终围绕着“更少 CPU 介入、更高带宽、更安全”这一目标。
掌握 DMA 原理,不仅能帮你写出更高效的驱动程序,还能让你在系统性能调优时有的放矢。
思考题:在 Linux 内核中,memcpy是否可能利用 DMA 加速?为什么?
(提示:现代 CPU 的memcpy使用 SIMD 指令或rep movsb,已经很快;DMA 设置开销大,仅适合大块 I/O 场景。)
如果觉得有帮助,欢迎点赞、收藏、转发~
本文首发于 CSDN,未经授权禁止转载。