DMA方式
DMA方式
复习定位
DMA让外设(如硬盘)和内存之间直接交换数据——不需要CPU逐字节参与。CPU只需要告诉DMA控制器"把这块磁盘数据搬到内存0x10000处、长度4KB"——然后CPU就可以去做其他事情——DMA控制器自动完成数据传输——完成后发中断告知CPU。DMA是提高I/O性能和CPU利用率的关键手段——尤其在磁盘、网卡等大块数据传输中。
为什么要DMA
在程序控制I/O方式(C原理I/O)中——CPU执行IN/OUT指令逐字节从设备读取数据再写入内存。如果从磁盘读1MB数据——CPU需要执行约100万次I/O指令——这段时间内CPU完全被数据搬运占据——无法做其他任务。而DMA将数据搬运动作从CPU卸载到专用控制器——CPU只需初始化DMA——然后去调度其他进程——由DMA控制器独立完成数据搬运。
DMA控制器
DMA控制器(DMAC)是一个能够独立于CPU控制总线的硬件组件。可CPU控制的寄存器:
- 源地址寄存器:数据从哪里来(外设端口地址或内存地址)
- 目的地址寄存器:数据到哪里去(内存地址)
- 传输计数器:要传输的字节数
- 控制寄存器:传输方向(读还是写)、传输单位(字节/字/块)、传输类型(单个/块/请求)
DMA传输过程
- CPU配置DMA控制器——设置源地址、目的地址、字节数、启动。
- 当外设准备好传输时——向DMA控制器发送DMA请求(DREQ)。
- DMA控制器向CPU发送总线请求信号(HOLD)——申请总线使用权。
- CPU在当前总线周期结束后释放总线控制权——向DMA回复HLDA(总线应答)——CPU暂停对总线的使用或暂时不暂停(依靠DMA的传输方法不同)
- DMA控制器获得总线控制权——在外设和内存之间直接传输数据——每传输一个单元(字节/字)——地址加1——计数器减1。
- 当计数器减到0——传输完成——DMA控制器向CPU发送中断请求信号——通知CPU传输完成。
- CPU响应中断——处理下一步操作。
DMA的传输方式
成组传送(Block Transfer)——DMA获得总线使用权后——连续传输整个数据块——传输完成才释放总线。CPU在整个传输期间无法访问内存(但可以访问Cache中的内容)——如果传输时间过长——会严重影响CPU的响应时间和实时性。
周期挪用(Cycle Stealing)——DMA在每个总线周期之间"偷"一个周期进行传输——每传一个数据单元——就释放总线——让CPU继续工作——再在下一个周期的间隙再偷一个周期。这种方式对CPU的影响比成组传送小——因为每次传输的时间非常短——但整体传输速度较慢。
透明DMA(Transparent DMA)——DMA只在CPU不访问内存的周期内传输——对CPU完全透明。这种模式下——CPU不会等待DMA——但实现复杂且传输速率取决于CPU的空闲情况。
复习检查
DMA为什么比程序控制I/O快——因为数据传输由专用硬件完成——不需要CPU逐字节执行I/O指令——CPU可以在这段时间里执行其他指令。
DMA控制器如何知道要传输多少数据——CPU在启动DMA时设置了传输计数器——每传输一个单元计数器减1——归零传输结束。
成组传送期间——CPU可以执行指令吗?CPU可访问Cache(L1/L2)中的内容——如果所有指令都在Cache中——CPU可以继续执行——只有需要访问内存的指令(缺Cache)才会被阻塞。
周期挪用——DMA每次只传一个或少量数据单元就释放总线——CPU和DMA交替使用总线——为什么这种模式适合高速设备与慢速设备之间的平衡?
为什么现代NVMe SSD使用DMA(Direct Memory Access)传输数据——NVMe通过PCIe直连CPU——设备的主控器直接通过DMA将数据写入系统内存——整个过程不需要CPU介入——释放CPU处理其他工作以充分发挥并行处理能力。
DMA与中断驱动的I/O性能对比
中断驱动的I/O——每个数据块传输完成后设备发中断通知CPU——CPU中断处理程序将数据从设备寄存器复制到内存缓冲区。对于高速设备(如网卡、NVMe SSD)——每个数据包都触发中断——中断处理的开销(保存/恢复寄存器、返回/恢复执行路径)可能消耗大量CPU时间——在高吞吐场景下(如10Gbps网络——每秒钟可能有数十万个数据包)——中断的频率可能高到使CPU无法做其他有效工作。
DMA的改进:设备通过DMA将数据直接写入内存——只在整个传输完成(或成批传输完成)后发一次中断——CPU只需处理一次中断而不是每传输一小块都中断一次——中断频率从"每块一次"降低为"每整个DMA传输一次"——大幅减少了CPU的中断处理开销。现代网卡使用NAPI(中断+轮询结合)技术——在高数据率时中断收敛到轮询——进一步降低中断频次。
DMA传输中CPU的状态分析
在成组传送(Block Transfer)模式下——DMA控制器占用总线连续传输整个数据块——CPU不能访问内存——但CPU可以继续执行已经在指令Cache(L1I)中缓存的指令——只要这些指令不涉及访存操作(寄存器运算指令)。如果程序已经全部在Cache中(如紧凑的循环)——CPU可以在DMA传输期间继续执行——不受影响。
如果CPU需要访问内存(如取下一个指令不在Cache中或读写数据)——CPU必须等待DMA释放总线——因此成组传送会导致CPU在传输期间对内存的访问延迟增加——但这对于外设大量数据搬运的性能提升利大于弊。
周期挪用(Cycle Stealing)模式下——DMA只在CPU不使用总线的空闲周期传输——CPU对内存的访问几乎不受影响——但总的传输速率较低——因为DMA需要等待CPU的空闲周期。周期挪用在不需要极高传输速率的设备(如声卡)中比较适用。
DMA控制器在现代芯片中的位置
在早期的IBM PC架构中——DMA控制器是主板上的独立芯片(Intel 8237)——CPU通过I/O端口与DMA控制器通信。现代CPU/SOC中——DMA控制器通常集成在设备端(如NVMe SSD控制器内部自带DMA引擎——网卡内部集成了DMA控制器)——每个高性能设备都有自己的DMA引擎——可以独立从系统内存读写数据而无需中央DMA控制器的参与。这种"分布式DMA"架构避免了中央DMA控制器的瓶颈——使多个高速设备可以同时在不同的总线上进行DMA传输。
IOMMU——DMA的安全机制
在没有IOMMU(输入输出内存管理单元)的系统上——DMA设备可以使用任何物理地址直接访问系统内存——这意味着一个有缺陷或被恶意控制的设备可以读取或破坏内核或其他进程的数据。IOMMU为DMA设备提供了一个独立的地址映射表——将设备发出的DMA地址转换为物理地址——限制设备只能访问为其分配的内存范围——不能越界读写。在虚拟机场景中——IOMMU使设备可以直接分配给虚拟机(PCIe Passthrough)而不破坏宿主机与其他VM的隔离性。
DMA的配置寄存器与编程示例
// 伪代码——在Legacy DMA控制器上配置一次DMA传输
void setup_dma(unsigned char *source, unsigned char *dest, int count) {
outb(0x00, 0x0A); // 屏蔽DMA通道
outb(0xFF, 0x0C); // 清除字节指针触发器
outb((unsigned int)source & 0xFF, 0x04); // 设置源地址低字节
outb((unsigned int)source >> 8, 0x04); // 设置源地址高字节
outb((unsigned int)dest & 0xFF, 0x05); // 设置目的地址低字节
outb((unsigned int)dest >> 8, 0x05); // 设置目的地址高字节
outb(count & 0xFF, 0x06); // 设置传输字节数低字节
outb(count >> 8, 0x06); // 设置传输字节数高字节
outb(0x01, 0x0A); // 启用DMA通道
}现代设备(NVMe SSD)的DMA配置更为简单——CPU只需在设备的内存映射寄存器(MMIO)中写入源地址/目的地址/传输长度——设备自行完成DMA传输——完成后写Completion Queue Entry并触发中断。
复习检查(续)
中断驱动I/O与DMA方式的性能对比——中断驱动每个数据块传输都触发一次中断——高速设备(网卡如10Gbps)的中断频率极高——处理中断消耗大量CPU时间——DMA只在整批传输完成后发一次中断——CPU中断处理频率显著降低。
成组传送DMA模式下CPU能否继续执行——CPU可以执行已经在Cache中的指令——但访存指令需等待DMA传输结束释放总线——Cache中的计算密集型循环不受影响——需要访问内存的指令受总线等待的阻塞。
周期挪用的适应场景——低速外设(声卡、低速率传感器)——不需要极高的传输带宽——DMA与CPU交替使用内存周期——对CPU的整体性能影响小。
IOMMU在DMA安全中的作用——限制DMA设备只能访问为其分配的物理内存范围——防止有缺陷或恶意的设备读写内核或其他进程的内存——在虚拟机设备直通场景中保证VM间隔离。
现代分布式DMA架构的优势——每个高性能设备(SSD/网卡/显卡)拥有独立的DMA引擎——通过PCIe直接与系统内存通信——不需要中央DMA控制器——多个设备可以同时进行DMA传输——不受单一控制器的限制。
DMA传输与Cache一致性
当外设通过DMA向系统内存写入数据时——DMA直接写入物理内存——绕过CPU的Cache。如果CPU在Cache中缓存了该内存地址的旧数据——在DMA写入后CPU读到的将是Cache中的旧数据而不是DMA写入的新数据——这就是Cache一致性问题。
解决DMA导致的Cache一致性问题的典型方法:
一致性DMA映射(Coherent DMA Mapping)——在DMA传输期间——CPU以页粒度取消该内存区域的Cache映射——使CPU对该区域的访问直接从内存读取——不经过Cache。适用于传输频率高、数据量小的场景(网卡描述符环)。
流式DMA映射(Streaming DMA Mapping)——DMA传输前——CPU主动将Cache中对应的脏行刷回内存(保证DMA看到最新的CPU修改)——DMA传输后——CPU主动使Cache中对应的行失效(保证CPU下次读取时从内存获取DMA写入的新值)。适用于大块数据传输(磁盘I/O)。
这些Cache一致性维护操作由操作系统内核中的DMA API自动完成——设备驱动开发者不需要手动处理。
DMA在Linux内核中的API
Linux内核提供了一套统一的DMA API——使设备驱动程序可以方便地使用DMA而不必关心底层硬件平台(不同CPU架构和IOMMU)的差异:
// 分配DMA缓冲区
dma_addr_t dma_handle;
void *cpu_addr = dma_alloc_coherent(dev, size, &dma_handle, GFP_KERNEL);
// 流式DMA映射(用于从设备到内存的数据传输)
dma_map_single(dev, cpu_addr, size, DMA_FROM_DEVICE);
// 流式DMA同步
dma_sync_single_for_cpu(dev, dma_handle, size, DMA_FROM_DEVICE);
// 释放DMA映射
dma_unmap_single(dev, dma_handle, size, DMA_FROM_DEVICE);这些API封装了Cache一致性维护、IOMMU地址映射等底层操作——使设备驱动开发者可以专注于设备逻辑而不用处理底层硬件细节。
虚拟化环境中的DMA
在虚拟化环境中——设备可以被直接分配给虚拟机(PCIe Passthrough, VFIO)——虚拟机内部的设备驱动可以直接与物理设备通信——不需要经过宿主机内核。此时——虚拟机中的DMA传输必须通过IOMMU/SMMU进行地址转换——将虚拟机中的"客户物理地址"(GPA)转换为宿主机物理地址(HPA)——确保虚拟机不能通过DMA访问不属于它的宿主机内存区域。
SR-IOV(Single Root I/O Virtualization)允许一个物理设备呈现为多个独立的虚拟设备(VF)——每个VF可以被分配给不同的虚拟机——各VF具有独立的DMA地址空间——通过IOMMU隔离——使性能接近物理设备独占——广泛用于数据中心的高性能网络和存储场景。
复习检查(续二)
DMA传输中的Cache一致性问题——DMA直接写入内存——绕过CPU Cache——如果Cache中有该内存地址的旧数据——CPU将读到旧值——导致数据不一致。
一致性DMA映射与流式DMA映射的区别——一致性映射长期取消Cache映射——适合频繁小数据量传输——流式映射在传输前后刷Cache——适合大块数据传输。
Linux DMA API的功能封装——提供统一的DMA缓冲区分配、映射、同步、取消映射接口——设备驱动无需处理底层Cache一致性和IOMMU映射细节。
虚拟化环境中IOMMU在DMA中的作用——将虚拟机中的客户物理地址转换为宿主机物理地址——确保虚拟机DMA不能越界访问宿主机内存。
SR-IOV在虚拟化DMA中的作用——一个物理设备呈现为多个VF——每个VF拥有独立的DMA地址空间和IOMMU映射——可以直接分配给不同的虚拟机——接近物理性能。
DMA传输在现代数据中心的广泛使用
现代数据中心中的高性能存储和网络设备几乎全部依赖DMA进行数据传输:
- NVMe SSD通过PCIe DMA直接读写系统内存——CPU仅在提交I/O请求和处理完成中断时参与
- 100Gbps网卡通过DMA将接收到的数据包直接存入内存中的Ring Buffer——CPU只负责协议栈处理
- GPU通过DMA读取系统内存中的训练数据——CPU只需在DMA传输前将数据准备好
- SmartNIC/DPU通过DMA与CPU共享内存中的控制和数据队列——CPU和加速器协同处理
DMA已经将CPU从大量的数据搬运工作中解放出来——使CPU可以集中精力处理逻辑密集的任务——这是所有高性能计算和存储系统基础架构的重要特征。
不同DMA传输模式在适用场景中的指标差异
程序控制I/O(非DMA):
适用——极低数据量(键盘输入、单个寄存器读写)
CPU占用——100%搬运
延迟——即时(CPU主动做)
吞吐——低(CPU速度限制)
周期挪用的DMA:
适用——较低数据率的外设(声卡、低速存储)
CPU占用——低(只在释放总线的空时隙CPU维持高利用率)
延迟——中等(每次传输等待轮流)
吞吐——受限于CPU的总线空闲周期频率
成组传送(Block DMA):
适用——高速设备(NVMe SSD、网卡、GPU)
CPU占用——极低(仅在初始化+完成中断)
延迟——低(设备→内存直接传输)
吞吐——极高(由设备性能和总线带宽决定)现代高性能设备几乎全部使用成组传送模式的DMA——由设备端内置的DMA引擎完成大块数据的直接传输——CPU只在初始化和接收完成中断时参与。