type
Post
date
Feb 10, 2026
slug
linux_dma_technology_guide
category
🥳嵌入式Linux开发
icon
password
本教程系统梳理Linux内核DMA(Direct Memory Access)技术,覆盖核心API、调用流程、驱动开发实践及常见问题,适用于内核/驱动开发与面试复习。
一、DMA核心概念
1.1 DMA是什么
DMA允许外设绑过CPU直接访问系统内存,实现高速数据传输。
1.2 缓存一致性问题说明
DMA绕过CPU直接访问内存,而CPU通过Cache访问内存,这导致Cache与内存数据可能不一致。
问题场景图示:
1.3 缓存一致性矛盾
DMA缓存一致性的核心命题:
如何在 读性能(可用性) 和 写一致性(正确性) 之间找到最优平衡点。
矛盾分析(CAP定理视角):
选择CP(一致性优先):
选择AP(可用性优先):
为什么P(分区)不可避免?
- 物理隔离:CPU Cache和内存是两个独立存储单元
- 通讯延迟:Cache同步操作需要时间(纳秒级,但非零)
- 异步访问:DMA控制器与CPU并行工作,访问内存时机不同
1.4 CPU Cache原子操作
CPU提供硬件级原子操作来保证Cache一致性,这是DMA能够可靠工作的基础。
Clean(清除/写回)操作
原子性保证:
- 不可中断:Cache line的Clean操作在硬件层面是原子的,不会被其他CPU核心或中断打断
- 立即生效:操作完成后,内存中的数据立即可见于所有观察者(CPU核心、DMA控制器)
- 顺序一致:配合内存屏障,确保操作顺序符合预期
Invalidate(失效)操作
原子性保证:
- 即时失效:Cache line被标记为无效后,CPU下次读取必定从内存获取最新数据
- 粒度保证:以Cache line(通常32-64字节)为单位,避免部分失效
- 一致性协议:通过MESI/MOESI等协议确保多核一致性
解决方案时序图
以下时序图对应1.2节的问题场景,展示如何通过Cache原语解决一致性问题。
场景1解决:Invalidate解决FROM_DEVICE问题
场景2解决:Clean解决TO_DEVICE问题
1.5 内存屏障配合
内存屏障(Memory Barrier)确保CPU和编译器不会重排序关键操作,是Cache原子操作正确工作的必要补充。
屏障类型
屏障类型 | 函数 | 使用场景 |
通用屏障 | mb() | 最强保证,性能开销最大 |
写屏障 | wmb() | CPU写数据后启动DMA |
读屏障 | rmb() | DMA完成后CPU读取数据 |
DMA写屏障 | dma_wmb() | 更新DMA描述符字段后标记有效 |
DMA读屏障 | dma_rmb() | 读取DMA描述符状态前 |
编译器屏障 | barrier() | 仅需阻止编译器优化时使用 |
使用示例
屏障与Cache操作的关系
1.6 DMA缓存策略对比
两种模式的一致性保证
关键结论
- TO_DEVICE:映射时自动Clean,确保CPU写入的数据刷到内存
- FROM_DEVICE:传输完成后必须调用
dma_sync_for_cpu()进行Invalidate
- BIDIRECTIONAL:两个方向都需要同步
没有完美的缓存策略,只有适合具体场景的权衡。硬件提供的原子操作(Clean/Invalidate)是这种权衡的基础工具。
二、内核DMA核心接口
2.1 一致性DMA(Coherent DMA)
核心API
函数调用栈
关键特性
- 返回两个地址:CPU虚拟地址(用于软件访问)+ DMA总线地址(用于设备访问)
- 内存标记为非缓存(uncached)或使用硬件一致性机制
- 分配开销较大,适合长期使用的缓冲区
2.2 流式DMA(Streaming DMA)
核心API
传输方向枚举
函数调用栈
2.3 Cache同步接口
流式DMA必须在正确时机同步Cache:
同步时序图
2.4 DMA内存池(DMA Pool)
适用于频繁分配小块一致性DMA内存的场景:
三、驱动开发示例
3.1 一致性DMA示例:环形缓冲区
3.2 流式DMA示例:网络驱动收发
3.3 Scatter-Gather DMA示例
四、DMA地址与内存模型
4.1 地址类型对比
地址类型 | 描述 | 获取方式 |
虚拟地址 | CPU访问的地址 | kmalloc()、vmalloc() |
物理地址 | 真实内存地址 | virt_to_phys() |
总线地址 | 设备看到的地址 | dma_map_xxx() |
DMA地址 | 通过IOMMU转换后的地址 | dma_map_xxx() |
4.2 IOMMU的作用
4.3 DMA内存布局与进程关系
4.3.1 64位系统完整内存空间划分
4.3.2 单个进程视角的完整布局(ARM64)
4.3.3 DMA缓冲区三种分配方式的内存视图
方式1: dma_alloc_coherent() - 一致性DMA
方式2: dma_alloc_wc() - 写合并DMA
方式3: 流式DMA (dma_map_single)
4.3.4 进程通过mmap访问DMA缓冲区
驱动实现mmap的关键代码:
4.3.5 多进程场景下的隔离与共享
4.3.6 DMA溢出破坏场景的物理内存视图
本节场景基于流式DMA:驱动通过kmalloc()分配缓冲区,再用dma_map_single()映射。因此 DMA 缓冲区位于 kmalloc/slab 堆区(~0x8100_xxxx),与相邻 slab 对象物理紧邻,与 4.3.2 宏观布局一致。
破坏链路分析:
典型破坏场景示例:
场景1:覆盖相邻Slab对象(kmalloc区域内)
场景2:覆盖相邻DMA缓冲区(CMA区域内)
4.3.7 核心要点总结
要点 | 说明 |
内核空间共享 | 所有进程看到相同的内核虚拟地址空间(包括DMA区) |
物理内存唯一 | DMA缓冲区占用固定物理页帧,不同虚拟地址可映射到同一物理页 |
隔离机制 | 页表控制每个进程只能访问自己的用户空间 |
mmap桥接 | 允许用户进程直接访问DMA物理内存(零拷贝技术) |
溢出危害 | DMA控制器直接写物理内存,绕过所有保护机制,必须严格校验 |
4.4 DMA掩码设置
五、常见问题与解决方案
5.1 Cache一致性问题
问题现象:DMA传输后CPU读到旧数据,或CPU写入后设备读到旧数据
根因:Cache与内存数据不一致
解决方案:
5.2 DMA缓冲区溢出
问题现象:系统崩溃、数据损坏、随机Oops
根因:DMA控制器不做边界检查,传输长度超过缓冲区大小
解决方案:
5.3 DMA内存分配失败
问题现象:
dma_alloc_coherent() 返回NULL可能原因:
- 请求内存过大
- 系统内存碎片化
- CMA区域耗尽
解决方案:
5.4 DMA映射错误检测
问题现象:使用无效DMA地址导致传输失败
解决方案:
5.5 32位设备访问高端内存
问题现象:32位PCI设备无法访问4GB以上物理内存
解决方案:
5.6 中断与DMA同步问题
问题现象:收到DMA完成中断但数据未就绪
根因:中断可能先于DMA写入完成到达(Posted Write)
解决方案:
六、调试技巧
6.1 DMA调试内核配置
6.2 运行时调试
6.3 常用调试打印
七、面试高频问题
Q1:一致性DMA和流式DMA的区别?
一致性DMA:硬件保证Cache一致性,分配的内存标记为非缓存,适合长期使用的描述符/环形缓冲区,分配开销大。
流式DMA:需要软件显式同步Cache,性能更优,适合单次传输场景(网络包、块I/O),映射/解映射开销小。
Q2:什么时候调用dma_sync_for_cpu()和dma_sync_for_device()?
- CPU→设备:dma_map_single(TO_DEVICE)自动Clean;若重复使用缓冲区,CPU重新写入后调用sync_for_device()
- 设备→CPU:DMA完成后、CPU读取前调用sync_for_cpu()进行Invalidate
Q3:为什么DMA缓冲区需要页对齐?
1. Cache一致性:Cache操作以cacheline为粒度,非对齐可能影响相邻数据
2. IOMMU要求:IOMMU页表以页为粒度管理
3. 硬件限制:部分DMA控制器要求地址对齐
Q4:如何处理设备只支持32位DMA地址?
1. 调用dma_set_mask(&dev, DMA_BIT_MASK(32))告知内核
2. 内核自动使用bounce buffer:将高端内存数据复制到低端内存再传输
3. 或启用IOMMU进行地址重映射
Q5:DMA传输后CPU读到旧数据怎么办?
检查是否正确调用了dma_sync_single_for_cpu()。该函数会Invalidate CPU Cache,确保CPU从内存而非Cache读取数据。
八、快速参考表
API | 用途 | Cache行为 |
dma_alloc_coherent() | 分配一致性DMA内存 | 无需手动同步 |
dma_map_single() | 映射单个缓冲区 | 自动sync_for_device |
dma_map_sg() | 映射scatter-gather列表 | 自动sync_for_device |
dma_sync_single_for_cpu() | CPU准备读取 | Invalidate |
dma_sync_single_for_device() | 设备准备读取 | Clean |
dma_pool_alloc() | 从池分配小块内存 | 无需手动同步 |
- Author:felixfixit
- URL:http://www.felixmicrospace.top/article/linux_dma_technology_guide
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!








