第一百三十七章:DMA
合道期涉及内核源码:
一
林小源在设备山脉的一处深谷中,发现了一条隐秘的通道。
通道从一块磁盘设备的底部直通内存区域——不经过 CPU,不经过任何中间节点。通道内流淌着金色的光流,速度极快,像是被某种力量直接牵引。
"这是什么?"林小源趴在通道入口,往里张望。
"DMA 通道。"守山老者说,"Direct Memory Access——硬件直接访问内存。"
林小源回头看了看正常的路径——从磁盘到 CPU,再从 CPU 到内存。数据要经过 CPU 的手,逐字节地搬运。
"那不是浪费 CPU 的时间吗?"
"对。"老者说,"没有 DMA 时,CPU 要亲自搬运每一个字节。读一个 4K 的磁盘块,CPU 要做 4096 次内存拷贝。这期间 CPU 什么都干不了——不能算数、不能调度进程、不能响应中断。"
"有了 DMA 呢?"
"CPU 只需要做一件事——告诉 DMA 控制器:'把磁盘上的这块数据,搬到内存的这个地址。'"老者说,"然后 CPU 就可以去做别的事了。数据搬运由 DMA 控制器和硬件自己完成,CPU 完全不参与。"
/*
* DMA 的工作原理:
*
* 没有 DMA:
* 硬件 → CPU → 内存
* CPU 需要逐字节复制
*
* 有 DMA:
* 硬件 → DMA 控制器 → 内存
* CPU 只需设置 DMA 控制器
* 数据传输由硬件完成
*
* DMA 的类型:
* 一致性 DMA: CPU 和设备看到相同的内存
* 流式 DMA: 需要显式同步
*
* DMA 映射:
* dma_map_single — 映射单个缓冲区
* dma_unmap_single — 取消映射
* dma_map_sg — 映射 scatter-gather 列表
*
* DMA 的问题:
* 一致性 — CPU 和设备可能看到不同的数据
* 缓存 — 需要刷新缓存
*/
printf("=== DMA — 硬件直接访问内存 ===\n\n");
printf("DMA vs 无 DMA:\n\n");
printf("无 DMA:\n");
printf(" 硬件 → CPU → 内存\n");
printf(" CPU 逐字节复制\n");
printf(" CPU 开销大\n\n");
printf("有 DMA:\n");
printf(" 硬件 → DMA 控制器 → 内存\n");
printf(" CPU 只设置 DMA\n");
printf(" 数据传输由硬件完成\n\n");
printf("--- DMA 的类型 ---\n");
printf("一致性 DMA:\n");
printf(" CPU 和设备看到相同的内存\n");
printf(" 不需要显式同步\n");
printf(" 适合频繁读写\n\n");
printf("流式 DMA:\n");
printf(" 需要显式同步\n");
printf(" 性能更好\n");
printf(" 适合大块数据\n\n");
printf("--- DMA 映射 ---\n");
printf("dma_map_single(dev, buf, size, dir):\n");
printf(" 映射缓冲区\n");
printf(" 返回 DMA 地址\n\n");
printf("dma_unmap_single(dev, addr, size, dir):\n");
printf(" 取消映射\n\n");
printf("--- DMA 的问题 ---\n");
printf("一致性:\n");
printf(" CPU 和设备可能看到不同的数据\n");
printf(" 需要内存屏障\n\n");
printf("缓存:\n");
printf(" 需要刷新缓存\n");
printf(" dma_sync_single_for_cpu/device\n\n");
printf("--- 使用示例 ---\n");
printf("1. 分配 DMA 缓冲区\n");
printf("2. 填充数据\n");
printf("3. 映射缓冲区\n");
printf("4. 设置 DMA 控制器\n");
printf("5. 启动 DMA 传输\n");
printf("6. 等待完成中断\n");
printf("7. 取消映射\n");#include <stdio.h>
/*
* DMA 的工作原理:
*
* 没有 DMA:
* 硬件 → CPU → 内存
* CPU 需要逐字节复制
*
* 有 DMA:
* 硬件 → DMA 控制器 → 内存
* CPU 只需设置 DMA 控制器
* 数据传输由硬件完成
*
* DMA 的类型:
* 一致性 DMA: CPU 和设备看到相同的内存
* 流式 DMA: 需要显式同步
*
* DMA 映射:
* dma_map_single — 映射单个缓冲区
* dma_unmap_single — 取消映射
* dma_map_sg — 映射 scatter-gather 列表
*
* DMA 的问题:
* 一致性 — CPU 和设备可能看到不同的数据
* 缓存 — 需要刷新缓存
*/
int main() {
printf("=== DMA — 硬件直接访问内存 ===\n\n");
printf("DMA vs 无 DMA:\n\n");
printf("无 DMA:\n");
printf(" 硬件 → CPU → 内存\n");
printf(" CPU 逐字节复制\n");
printf(" CPU 开销大\n\n");
printf("有 DMA:\n");
printf(" 硬件 → DMA 控制器 → 内存\n");
printf(" CPU 只设置 DMA\n");
printf(" 数据传输由硬件完成\n\n");
printf("--- DMA 的类型 ---\n");
printf("一致性 DMA:\n");
printf(" CPU 和设备看到相同的内存\n");
printf(" 不需要显式同步\n");
printf(" 适合频繁读写\n\n");
printf("流式 DMA:\n");
printf(" 需要显式同步\n");
printf(" 性能更好\n");
printf(" 适合大块数据\n\n");
printf("--- DMA 映射 ---\n");
printf("dma_map_single(dev, buf, size, dir):\n");
printf(" 映射缓冲区\n");
printf(" 返回 DMA 地址\n\n");
printf("dma_unmap_single(dev, addr, size, dir):\n");
printf(" 取消映射\n\n");
printf("--- DMA 的问题 ---\n");
printf("一致性:\n");
printf(" CPU 和设备可能看到不同的数据\n");
printf(" 需要内存屏障\n\n");
printf("缓存:\n");
printf(" 需要刷新缓存\n");
printf(" dma_sync_single_for_cpu/device\n\n");
printf("--- 使用示例 ---\n");
printf("1. 分配 DMA 缓冲区\n");
printf("2. 填充数据\n");
printf("3. 映射缓冲区\n");
printf("4. 设置 DMA 控制器\n");
printf("5. 启动 DMA 传输\n");
printf("6. 等待完成中断\n");
printf("7. 取消映射\n");
return 0;
}二
"听起来 DMA 很好用。"林小源说,"那驱动怎么使用 DMA?"
老者从地上捡起一块金色的水晶,递给林小源。水晶内部浮现出一个函数名:。
"驱动要使用 DMA,首先要做映射。"老者说,"dma_map_single(dev, buf, size, dir) 把一块内核缓冲区映射给设备使用。它返回一个 DMA 地址——这个地址是设备能看到的物理地址,不是内核的虚拟地址。"
"为什么要映射?不能直接给设备一个地址吗?"
"因为设备看到的内存和 CPU 看到的不一定一样。"老者说,"有些系统有 IOMMU——I/O 内存管理单元。设备发出的地址会被 IOMMU 转换。映射操作会处理这些转换,确保设备能正确访问内存。"
林小源想了想:"那 DMA 有两种类型——一致性 DMA 和流式 DMA?它们有什么区别?"
"一致性 DMA 保证 CPU 和设备看到的内存内容是一致的——任何时候,CPU 写入的数据,设备都能立刻看到;设备写入的数据,CPU 也能立刻看到。不需要额外的同步操作。"
"代价呢?"
"性能。"老者说,"一致性 DMA 通常需要禁用缓存或者使用特殊的内存区域,访问速度比普通内存慢。流式 DMA 使用普通内存,性能好,但需要你手动做同步——在设备访问内存之前,告诉 CPU 把缓存里的数据刷到内存;在 CPU 访问内存之前,告诉设备把数据写完。"
三
"缓存……"林小源皱起眉头,"你是说 CPU 的缓存会导致问题?"
老者的表情变得严肃。他指着 DMA 通道旁边的另一条路——那是一条弯弯曲曲的缓存通道,里面的数据流动得很慢,但每个节点都亮着光。
"CPU 有 L1、L2、L3 缓存。"老者说,"当你写入一块内存时,数据可能还在缓存里,没有到达物理内存。如果你把这块内存的物理地址交给 DMA 控制器,它直接读物理内存——读到的可能是旧数据。"
林小源倒吸一口凉气。
"反过来也一样。"老者继续说,"DMA 控制器把数据写到物理内存,但 CPU 的缓存里还是旧数据。CPU 去读那块内存,读到的是缓存里的旧值,不是 DMA 写入的新值。"
"所以需要刷新缓存。"
"对。"老者说," 在 CPU 访问之前调用——让设备的数据对 CPU 可见。 在设备访问之前调用——让 CPU 的数据对设备可见。"
林小源蹲在 DMA 通道入口,看着那条金色的光流,心中感叹:DMA 看似简单——让硬件自己搬运数据——但背后涉及映射、缓存一致性、IOMMU,每一个环节都可能出问题。高效和安全,从来都不是免费的。
道藏笔记
内核启示
DMA 让硬件直接访问内存,减少 CPU 开销。
DMA 的类型:
- 一致性 DMA — 不需要显式同步
- 流式 DMA — 需要显式同步
DMA 映射:
- — 映射缓冲区
- — 取消映射
DMA 的问题:
- 缓存一致性 — 需要刷新缓存
- 内存屏障 — 保证顺序
DMA 是"高效"的传输——让硬件自己搬运数据。
DMA 之试
本章地下暗河让设备直接访问内存、减少 CPU 搬运负担,这种机制叫什么?