核心判断
一套专题如果最终不能沉淀成模板、清单和快查表,它的复用价值会迅速衰减。真正高杠杆的做法,不是把知识记成印象,而是把知识压缩成以后还能直接调用的工程资产。
这一章解决什么问题
前面十一章已经把 DMA 从问题、原理、设计、实战、排障到表达都讲完了。最后这一章要做的,是把它们压缩成工具箱。
也就是把“看过”变成“以后还用得上”。
一、DMA 常用 API 使用时机快查
API / 动作 | 什么时候用 | 你真正要确认什么 |
dma_set_mask_and_coherent() | probe 早期 | 设备到底能看多大地址空间 |
dma_alloc_coherent() | 控制面、descriptor、状态结构 | 是否优先要简单一致性语义 |
dma_map_single() | 高吞吐数据面(单 buffer) | 是否需要显式同步与严格生命周期 |
dma_map_sg() | 分散内存拼成一条传输 | 硬件是否支持 SG descriptor |
dma_sync_single_for_device() | CPU 写完,设备准备读之前 | 设备下一步能否看到最新内容 |
dma_sync_single_for_cpu() | 设备写完,CPU/用户准备读之前 | CPU 下一步能否看到设备刚写的新内容 |
dma_pool_create() / dma_pool_alloc() | 频繁分配的小块一致性对象 | descriptor / command buffer 是否值得池化 |
barrier / dma_wmb() / dma_rmb() | descriptor、状态位、doorbell 之间 | 设备会不会看到半成品顺序 |
mmap | 需要共享数据面给用户态 | 数据面和控制面有没有分开 |
poll | 需要事件驱动完成通知 | 是否只承担通知,而不偷渡复杂语义 |
一点二五、Coherent / Streaming / Pool 快查
类型 | 更适合什么 | 你真正换来的是什么 |
dma_alloc_coherent() | descriptor、ring、状态结构、第一版稳定闭环 | 更简单的一致性语义,代价是灵活性和数据面性能未必最优 |
dma_map_single() / dma_unmap_single() | 高吞吐单 buffer 数据面 | 更贴近真实收发路径,但生命周期和同步纪律更重 |
dma_map_sg() / dma_unmap_sg() | 分散内存拼装成一条 DMA 事务 | 解决不连续内存问题,但必须把整组 SG 当成一个事务治理 |
dma_pool_create() / dma_pool_alloc() | 频繁分配小块一致性对象 | 减少小对象反复分配成本,适合 descriptor / command buffer 池化 |
一点五、方向与同步动作快查
场景 | 优先检查什么 | 常见失误 |
CPU 写完,设备准备读 | 是否已把 CPU 新内容对设备可见 | buffer 还没刷回去就提交 DMA |
设备写完,CPU 准备读 | 是否已把 CPU 旧 cache 作废 | DMA 明明完成了,CPU 还在读旧副本 |
descriptor 写完后敲 doorbell | 顺序是否已被 barrier 约束 | 设备看到半成品 descriptor |
reset / timeout 后重启事务 | 旧 completion / 旧状态是否已清理 | 新事务被旧生命周期污染 |
一点七五、map / unmap 配对速记
资源形态 | 典型建立动作 | 典型收尾动作 | 最容易犯的错 |
单 buffer streaming | dma_map_single() | dma_unmap_single() | 完成后忘记 unmap,或 map 失败还继续下发 descriptor |
SG 事务 | dma_map_sg() | dma_unmap_sg() | 只盯某一段回收,没有把整条 SG 事务当整体治理 |
一致性 descriptor / ring | dma_alloc_coherent() | dma_free_coherent() | 把它误当成 streaming buffer,再额外乱加 map/unmap |
用户态共享 coherent 区 | dma_alloc_coherent() • dma_mmap_coherent() | dma_free_coherent() • fd / 映射生命周期收口 | 以为 mmap 自动附带了 submit / consume / reclaim 协议 |
一点九、最小地址视图速记
- CPU 虚拟地址:驱动自己访问内存时使用。
- DMA 地址:设备真正发起 DMA 时使用。
- 用户虚拟地址:
mmap之后用户态看到的地址。
三者可能都不同,但它们可能映射到同一批物理页。
所以看到
mmap 成功时,脑子里应该自动补一句:数据面桥接完成了,但控制面协议还没自动出现。
一点九五、长度与失败守卫速记
检查点 | 最低要求 |
长度边界 | transfer_size <= buffer_size |
映射结果 | 立刻检查 dma_mapping_error() |
地址能力 | probe 早期确认 dma_set_mask*() 是否成功 |
顺序边界 | descriptor 完整后再 doorbell,必要处补 barrier |
二、最小 slot 状态机模板
推荐迁移规则
FREE -> CPU_OWNED:prepare / alloc
CPU_OWNED -> DEVICE_OWNED:submit 后正式交给设备
DEVICE_OWNED -> READY_FOR_CPU:completion + 必要同步后
READY_FOR_CPU -> FREE:consume / reclaim
- 任意关键状态 ->
ERROR:timeout / fault / reset 前异常
如果你的驱动连这张最小状态图都画不出来,那先别谈优化,先把秩序补上。
三、ring buffer 设计检查清单
- ring 的 producer / consumer 分别是谁推进?
- completion 是按 slot、period 还是 batch 到达?
- slot 回收的边界点是 completion 还是 consume?
- reset 后哪些 slot 必须整体作废?
- 用户态慢消费时,系统是阻塞、丢弃还是回压?
ring 真正难的,从来不是“画一圈数组”,而是定义并发世界里的秩序。
四、零拷贝协议模板
推荐最小协议
动作 | 语义 |
GET_WRITE_SLOT | 获取当前可写 slot |
SUBMIT | 声明写完并交给设备 |
WAIT_DONE / PEEK_DONE | 等待或查询完成 slot |
CONSUME / ACK | 声明结果已使用完,可回收 |
RESET / CANCEL / QUERY | 异常控制与状态查询 |
协议设计原则
- 数据面走
mmap
- 控制面走
ioctl/poll
- 状态迁移必须可验证
- 用户态
SUBMIT后不得再写该 slot
CONSUME不是装饰,而是回收语义的边界
五、probe / remove 模板清单
probe
- 建立设备私有结构体
- 初始化锁、wait queue、ring 元数据
- 声明 DMA mask
- 分配 buffer / descriptor
- 注册 IRQ
- 注册字符设备或上层接口
remove
- 阻止新请求进入
- 停止硬件 DMA
- 作废或回收 in-flight slot
- 注销接口
- 释放 IRQ
- 释放 DMA 资源
probe 看的是“怎么生”,remove 看的是“怎么死”。能死干净,系统才算真正活过。
六、最值钱的日志模板
状态迁移日志
时序日志
异常日志
七、最常见错误速查
错误现象 | 优先怀疑什么 |
设备读到旧数据 | 提交前同步缺失,或 CPU 仍在改 buffer |
CPU 读到旧结果 | 完成后同步缺失,或状态过早切换 |
偶发错帧 | slot 过早回收 / ring 边界不清 |
reset 后随机炸 | 旧 completion / 旧状态污染新事务 |
remove 时崩溃 | DMA 未停机,资源先释放 |
随机内存污染 | DMA 长度越界,覆盖相邻对象 |
八、调试开关与运行时观测口
推荐内核配置
常用运行时入口
九、阅读建议:不同目标怎么走
如果你是第一次系统学 DMA
推荐顺序:
- 01 问题定义
- 02 全链路图
- 03 地基
- 04 中轴
- 05-07 实战路径
如果你正在写驱动
推荐顺序:
如果你在准备面试
推荐顺序:
别从附录开始学,但写代码或复盘时,附录最适合拿来当操作台。
如果你正在现场写代码
建议把本页和下面几页并排看:
因为真正写代码时,你最需要的不是“概念完整”,而是骨架 + 生命周期 + 排障抓手 同时在线。
十、本专题压缩成一句总纲
DMA 不是“调几个 API”,而是围绕数据路径、ownership、生命周期、同步和异常治理构建的一套工程系统。
本章结论
- 真正有杠杆的知识,一定要沉淀成模板、清单和快查表。
- DMA 最该反复复用的资产,不是定义,而是状态机、协议、日志和检查表。
- 当这些资产建立起来后,你后面写新的驱动,不会再从零开始靠灵感乱撞。






