一、原理介绍
DMA 的英文全称是 Direct Memory Access,直译过来就是“直接内存访问”。简单说,它可以在不经过 CPU 的情况下,把数据从一个地址空间复制到另一个地址空间,实现外设与存储器之间、或者存储器与存储器之间的高速数据传输。
它的核心价值,在于解决大量数据传输过度消耗 CPU 资源的问题。DMA 把 CPU 从繁重的数据搬运中解放出来,让 CPU 专注于真正需要计算和决策的任务。
DMA 的本质就是实现数据的直接传输——把内存中某块地址的数据搬到另一块地址空间。既然是数据传输,自然就涉及三个基本要素:源地址、目标地址、传输长度。
理解 DMA 并不难,我们可以把计算机系统想象成一个国家的交通系统:
- CPU:国王/总统,拥有最高权力,所有重要决策都由他做出。
- 总线:连接国家核心区域的唯一一条超级高速公路,通行权至高无上。
- 内存:中央国库,存储着国家所有的核心物资和数据。
- 外设(硬盘、网卡、声卡等):分布在各地的专用仓库或工厂——硬盘是粮仓,网卡是邮局,声卡是剧院。
- DMA 控制器:国王麾下的特遣车队总指挥及其精英车队。
围绕这五种角色,可以划分出四类典型的数据传输场景:
-
外设 → 内存(粮仓 → 国库)
从“硬盘粮仓”运入大批粮食到“中央国库”。总指挥(DMA)负责快速、成批地运入,无需国王(CPU)亲自押运每一辆车。
-
内存 → 外设(国库 → 剧院)
为一场盛大演出,从国库调拨布景和乐器到歌剧院。总指挥确保物资准确、及时送达,不让国王为琐事分心。
-
内存 → 内存(国库 A 区 → 国库 B 区)
国库内部进行大规模数据整理,把 A 区的宝藏搬迁到新扩建的 B 区。这是纯粹的内部物流,由总指挥全权负责,效率极高,堪称国王的“御用搬运工”。
-
外设 → 外设(工厂 A → 工厂 B)
把一个工厂的半成品直接运到另一个工厂加工。这种场景较少,但总指挥同样能处理,避免了先运回国库再发出的迂回路线。
DMA 的工作范围覆盖了内核中 Kernel 层面的数据通路设计,理解它就是理解系统中“谁在搬运数据”的关键一步。
二、传输过程
DMA 的传输过程,本质上是一场井然有序的权力交接。
第一幕:请命(DMA 请求)
国王(CPU)正在高速公路上亲自驾驶“指令跑车”飞奔,执行当前总线周期。这时,特遣车队指挥(DMA 控制器)发来无线电请求:“陛下,有万吨粮食急需从粮仓运入国库,请求授予高速公路的临时独占权!”
国王回复:“准奏!但我得先完成手上这个急弯(当前总线周期结束)。你做好准备。”
第二幕:交权(DMA 响应)
国王的跑车冲过终点线(当前总线周期结束)的瞬间,立刻驶入旁边的“皇家休息区”,并通过对讲机宣布:“道路已清空,现在由特遣车队指挥接管高速公路!”
这一刻,总线控制权从 CPU 手中正式、临时地移交给了 DMA 控制器。
第三幕:雷霆行动(DMA 传输)
最壮观的景象出现了:高速公路被特遣车队完全独占。
一辆接一辆的重型卡车(Burst Transfer),以最大的载重量(Transfer Width)组成连绵不绝的车队(Burst Size),风驰电掣地行驶在道路上。国库和粮仓之间的大门全开,数据被高速搬运。
而此刻,CPU 在做什么?它并没有闲着! 在不受交通干扰的“休息区”(CPU 核心内部,缓存足够的情况下),它继续处理国家大事——执行不访问总线的指令和运算。这正是 DMA 提升系统性能的关键:并行工作。
第四幕:归政与汇报(DMA 结束)
当最后一辆卡车驶离高速,所有物资搬运完毕,DMA 控制器立刻通过无线电宣告:“任务完成!请求交还控制权!”同时向 CPU 发出一个“任务完成”的信号——中断请求。
CPU 收到信号后,从休息区驶回主路,重新收回总线控制权。随后,CPU 会执行中断服务程序,检查 DMA 传输是否正确,一切恢复正常。
DMA 的本质,是一场精心策划的“权力真空期”。CPU 是系统的永恒统治者,但它明智地意识到:对于大规模、重复性的数据搬运,交给专业的代理人(DMA)更高效。因此,CPU 愿意短暂让渡最核心的总线控制权,换取整个系统更高的运行效率。整个过程从请命、交权、执行到归政汇报,权责清晰、有始有终,形成完美闭环。正是这种机制,让现代计算机能够“一心多用”,实现惊人的数据处理能力。
三、传输宽度与传输数量
为了更好地理解 DMA 的具体参数,不妨把传输过程比作一个快递车队从 A 仓库(源地址)运送货物到 B 仓库(目标地址):
- CPU:公司的调度总监,负责初始派单和最终验收,但运送过程不插手。
- DMA 控制器:快递车队的队长及其车队,负责具体货物搬运。
- 总线:城市道路,一次只能由一个车队独占行驶。
- I/O 接口:A 仓库的装卸主管,负责把货物搬上车并呼叫车队。
3.1 Transfer Width(传输宽度)
Transfer Width 指的是每辆快递车一次能装载的货物箱尺寸。
- 8-bit:像骑摩托车送货,一次只能运一个小件(1 字节)。灵活,但运送大宗货物效率低。
- 16-bit:像开小面包车送货,一次能运两个小件或一个中等件(2 字节)。适合音频数据这类“标准件”。
- 32-bit:像标准厢式货车,一次能运 4 个小件(4 字节)。这是很多系统总线的标准“车道宽度”,效率很高。
- 64-bit:像重型卡车,一次能运 8 个小件(8 字节)。用于需要极高吞吐量的场景。
为什么需要它?为了匹配设备和总线的“装卸能力”,并提升效率。
- 设备匹配:一个音频设备(如扬声器)的“货舱门”可能只设计成 16 位宽。如果用 32 位的“卡车”强行塞数据,它会“不知所措”。必须用 16 位的“面包车”来匹配。
- 效率最大化:在内存之间搬运数据时,没有设备限制,自然希望用尽可能宽的“卡车”(如 64 位)来跑。跑一趟运的货多,总趟数就少,效率自然更高。
在 struct dma_device 中,驱动会检查 DMA 控制器支持哪些传输宽度(8/16/32/64 位),然后根据源设备和目标设备的特性,选择一个最合适的“车辆型号”。
3.2 Burst Size(突发传输大小)
Burst Size 指的是快递车队在获得一次道路使用权(总线控制权)后,连续行驶的车辆数。
DMA 控制器内部有一个小缓存(Buffer),就像车队在路边的一个临时中转站。
场景一:内存是源(读操作)
车队队长(DMA 控制器)获得道路权后,不是派一辆车去 A 仓库取一件货就回来,而是派一个车队(Burst)连续不断地进入 A 仓库。比如一次派出 4 辆卡车(Burst Size = 4),每辆车都装满,然后全部开到临时中转站卸货。
之后,队长把道路权交还给 CPU 总监。在接下来的时间里,队长再安排小车,从容不迫地把中转站里的货物按节奏(每个时钟节拍)一件件送到目标设备,而不需要再次申请占用主路。
[内存] --(Burst 4x32-bit)--> [DMA中转站] --(逐个32-bit)--> [外设]
场景二:内存是目标(写操作)
队长先从外设那里把货物一点点收集到自己的临时中转站里。当累积的货物够装满一个车队时(例如 4 辆卡车),队长才去申请道路权。获得许可后,派整个车队(Burst)连续不断地开往 B 仓库,一次性将中转站的所有货物清空。之后,立即归还道路权。
[外设] --(逐个32-bit)--> [DMA中转站] --(Burst 4x32-bit)--> [内存]
为什么需要它?为了减少“申请道路权”的次数,从而降低总线访问的开销和延迟。申请道路权(总线仲裁)是一个相对耗时的“行政流程”。如果运一点货就申请一次,效率极低。Burst 传输相当于“批发”,一次申请办理一大笔业务,大大提高了总线的利用率和整体传输效率。
在 struct dma_device 中,min_burst / max_burst 的含义如下:
max_burst:DMA 控制器支持的最大车队规模。比如 max_burst = 16,意味着它一次最多能连续传输 16 个数据单元(单元大小由 transfer width 决定)。它反映了 DMA 控制器内部中转站(Buffer)的最大容量。
min_burst:DMA 控制器要求的最低车队规模。有些 DMA 控制器设计上要求每次传输必须至少达到某个最小突发量,否则无法正常工作。通常这个值可能是 1。
四、DMA 导致的问题
把整个系统比作一个现代化的办公室:
- CPU:忙碌的经理
- CPU 缓存:经理桌上的文件托盘(L1/L2/L3 缓存,访问极快)
- 内存:办公室的共享文件柜(速度较慢,但容量大)
- DMA 控制器:专门的快递/文书团队
- 外设:其他部门或分公司
4.1 缓存一致性问题
场景 1:经理先更新,DMA 后读取(脏数据问题)
经理(CPU)收到一份需要修改的文件。为了效率,他从文件柜(内存)取出文件,放在自己托盘(缓存)里修改。修改完成后,新版本只在托盘中,还没放回文件柜。
此时,快递团队(DMA)接到指令:“去文件柜取这份文件发给外部门”。他们从文件柜取走的还是未修改的旧版本。后果就是:外部门收到了错误或过时的数据。
场景 2:DMA 先更新,经理后读取(陈旧数据问题)
外部门通过快递团队(DMA)将一份更新后的文件送到办公室文件柜(内存)中。经理(CPU)需要阅读这份文件,他习惯性地先看自己的托盘(缓存),发现里面有一份旧的副本,于是直接阅读了旧文件。后果:经理基于过时信息做出了错误决策。
这两个场景揭示了 DMA 与 Memory Management 中缓存机制之间最经典的矛盾:数据在缓存和主存之间不同步。理解了这一点,下面的解决方案就顺理成章了。
4.2 解决方案
4.2.1 一致性 DMA 映射(Coherent DMA Mapping)
比喻:设立一个“公告板”区域。
办公室专门划出一面公告板(一致性 DMA 缓冲区)。这个区域有个硬性规定:任何人都禁止把这里的文件带回个人托盘(禁止缓存)!所有对这个区域的读写,都必须直接在这个公告板上进行。
操作实现:
dma_alloc_coherent() 就是去申请并设立这样一块专用的“公告板”(通过 ioremap_nocache 映射,并确保缓存被刷新)。
- 经理(CPU)和快递团队(DMA)都直接在这块板上读写。
特点:
- 简单粗暴:从根本上杜绝了缓存问题,因为压根不用缓存。
- 性能代价:每次访问都像走到公告板前操作,比从自己托盘里拿要慢。
- 长期占用:这块公告板一旦设立,就长期为特定任务服务(驱动生命周期),不轻易拆除。
适用场景:用于长期存在、被 CPU 和 DMA 频繁交替访问的缓冲区,比如设备控制块、状态描述符环等。
4.2.2 流式 DMA 映射(Streaming DMA Mapping)
比喻:严格的“文件交接流程”。
文件可以放在办公室的任何地方(普通 kmalloc 内存),也可以被经理放入托盘(缓存)。但在快递团队要取件或送件的前后,必须执行严格的流程来保证双方看到的是同一版本。
操作实现:
- DMA 输出前(CPU → Device):调用
dma_map_single。这相当于经理必须把自己托盘里修改过的文件,全部整理好放回文件柜(writeback 缓存)。确保快递团队拿到的是最新数据。
- DMA 输入后(Device → CPU):调用
dma_unmap_single 或 dma_sync_single_for_cpu。这相当于把经理托盘中关于这个文件的旧副本全部作废(invalidate 缓存)。强制经理下次必须去文件柜取最新版本。
特点:
- 灵活高效:内存来自普通池,不长期占用特殊资源。在非传输时段,CPU 仍可享受缓存带来的速度提升。
- 流程复杂:需要驱动开发者严格、正确地调用映射/解映射 API,否则极易出错。
- 短期使用:映射生命周期很短,仅在单次 DMA 传输期间有效。
适用场景:用于一次性的、大数据块的传输,比如网络数据包、磁盘读写缓冲区。
DMA 机制看似只是“搬运数据”,但真正深入下去,会发现它与总线仲裁、缓存一致性、中断处理紧密交织。理解 DMA,绝不只是背下几个 API 而已——它要求你站在系统整体的视角,看清数据在 CPU、内存和外设之间流动的完整路径。