DMA性能分析:Linux驱动高吞吐传输调试与优化 - 第4节
4.1 DMA 通道的申请与释放:dma_request_channel / dma_release_channel
在 Linux DMA 引擎框架中,DMA 通道(channel)是执行数据传输的核心抽象。驱动必须首先向 DMA 引擎申请一个可用通道,才能发起传输。本节详细解析通道的申请与释放 API,以及背后的资源管理机制。
4.1.1 dma_request_channel:通道申请
dma_request_channel 用于从指定的 DMA 控制器(DMA controller)中获取一个满足特定能力的通道。其原型如下:
struct dma_chan *dma_request_channel(dma_cap_mask_t mask,
dma_filter_fn filter_fn,
void *filter_param);
| 参数 | 类型 | 说明 |
|---|---|---|
mask |
dma_cap_mask_t |
位掩码,指定所需 DMA 传输能力(如 DMA_SLAVE、DMA_CYCLIC) |
filter_fn |
dma_filter_fn |
回调函数,用于进一步筛选通道(可 NULL) |
filter_param |
void * |
传递给 filter_fn 的私有参数 |
典型调用流程:
- 使用
dma_cap_mask_t声明并初始化能力掩码。 - 调用
dma_cap_set设置所需能力位。 - 调用
dma_request_channel获取通道。 - 检查返回值是否为 NULL(申请失败)。
关键点: filter_fn 通常用于匹配特定 DMA 控制器或通道编号。例如,在嵌入式平台中,通过 filter_fn 绑定到指定的硬件 DMA 请求线(DMA request line)。
4.1.2 dma_release_channel:通道释放
当驱动不再使用 DMA 通道时,必须调用 dma_release_channel 将其归还给系统,以便其他驱动复用。
void dma_release_channel(struct dma_chan *chan);
使用注意事项:
- 必须在所有未完成的 DMA 传输完成或终止后调用。
- 释放后,通道描述符指针变为悬空,不可再使用。
- 通常在驱动的
remove回调或错误处理路径中调用。
常见错误: 忘记释放通道会导致 DMA 引擎资源泄漏,长时间运行后系统可能无法分配新通道。务必在驱动卸载路径中配对调用。
4.1.3 通道生命周期 SVG 图
下图展示了从申请到释放的完整通道生命周期,以及各阶段的关键操作:
4.2 一致性 DMA 内存分配:dma_alloc_coherent
在 DMA 传输中,CPU 和设备需要共享内存缓冲区。dma_alloc_coherent 分配一段一致性(coherent)内存,保证 CPU 和设备看到的缓存一致性,无需手动维护缓存同步。
4.2.1 函数原型与参数
void *dma_alloc_coherent(struct device *dev, size_t size,
dma_addr_t *dma_handle, gfp_t flag);
| 参数 | 说明 |
|---|---|
dev |
DMA 设备指针,用于关联内存区域 |
size |
分配大小(字节),会被对齐到页大小 |
dma_handle |
输出参数,返回设备可访问的 DMA 总线地址 |
flag |
GFP 标志(如 GFP_KERNEL、GFP_ATOMIC) |
返回值: 内核虚拟地址(CPU 访问),失败返回 NULL。
4.2.2 使用场景与限制
- 适用场景: 控制结构、描述符环、小数据块传输(如 USB 控制传输)。
- 性能特点: 分配时已保证一致性,读写无额外开销,但分配/释放成本较高。
- 大小限制: 通常用于分配较小缓冲区(几 KB 到几 MB),大块分配可能失败。
最佳实践: 对于高频小数据交换(如 DMA 描述符表),优先使用一致性分配。对于大数据流(如视频帧),考虑流式映射(streaming DMA mapping)以获得更好性能。
4.2.3 释放函数:dma_free_coherent
void dma_free_coherent(struct device *dev, size_t size,
void *cpu_addr, dma_addr_t dma_handle);
必须使用与分配时完全相同的 size、cpu_addr 和 dma_handle 进行释放。
4.2.4 一致性内存分配流程 SVG 图
4.3 DMA 内存池:dma_pool
当驱动需要频繁分配和释放大量固定大小的一致性 DMA 缓冲区时,直接使用 dma_alloc_coherent 会产生较高的开销。dma_pool 提供了一种预分配 + 复用的机制,显著提升小对象分配效率。
4.3.1 核心 API
| 函数 | 功能 |
|---|---|
dma_pool_create |
创建 DMA 内存池 |
dma_pool_alloc |
从池中分配一个对象 |
dma_pool_free |
释放对象回池 |
dma_pool_destroy |
销毁整个内存池 |
4.3.2 dma_pool_create 参数详解
struct dma_pool *dma_pool_create(const char *name,
struct device *dev,
size_t size,
size_t align,
size_t boundary);
| 参数 | 说明 |
|---|---|
name |
池名称,用于调试和统计 |
dev |
DMA 设备 |
size |
每个对象的大小(字节) |
align |
对齐要求(字节),必须是 2 的幂 |
boundary |
边界跨越限制(0 表示无限制),避免跨边界导致硬件问题 |
4.3.3 使用示例
struct dma_pool *pool;
void *cpu_addr;
dma_addr_t dma_addr;
// 创建对象大小为 64 字节、16 字节对齐、无边界限制的池
pool = dma_pool_create("my_dma_pool", dev, 64, 16, 0);
if (!pool) {
dev_err(dev, "Failed to create DMA pool\n");
return -ENOMEM;
}
// 从池中分配一个对象
cpu_addr = dma_pool_alloc(pool, GFP_KERNEL, &dma_addr);
if (!cpu_addr) {
dev_err(dev, "Failed to allocate from DMA pool\n");
dma_pool_destroy(pool);
return -ENOMEM;
}
// ... 使用缓冲区 ...
// 释放对象回池
dma_pool_free(pool, cpu_addr, dma_addr);
// 销毁池
dma_pool_destroy(pool);
4.3.4 性能对比:dma_alloc_coherent vs dma_pool
| 特性 | dma_alloc_coherent | dma_pool |
|---|---|---|
| 分配粒度 | 页对齐,最小 4KB | 任意大小(如 64 字节) |
| 分配开销 | 高(页分配 + 映射) | 低(预分配,快速复用) |
| 适用场景 | 大块、低频分配 | 小块、高频分配 |
| 内存碎片 | 可能产生内部碎片 | 固定大小,无碎片 |
| 对齐控制 | 有限(页对齐) | 灵活(任意 2 的幂) |
典型应用: 网络驱动的 TX/RX 描述符环、USB 的 URB 缓冲区、音频驱动的周期缓冲区。这些场景中,对象大小固定且分配频繁,使用 dma_pool 可降低 50% 以上的分配延迟。
4.3.5 DMA 内存池架构 SVG 图
4.4 本章小结
- dma_request_channel / dma_release_channel:管理 DMA 通道的生命周期,是驱动与 DMA 引擎交互的入口。
- dma_alloc_coherent / dma_free_coherent:分配一致性 DMA 内存,保证 CPU 和设备缓存一致性,适用于控制结构和小数据块。
- dma_pool:通过预分配和对象复用机制,大幅提升固定大小 DMA 缓冲区的分配效率,是高吞吐驱动中的关键优化手段。
下节预告: 第 5 章将深入讲解流式 DMA 映射 API(dma_map_single / dma_map_sg),以及如何在驱动中正确使用这些接口实现高性能数据传输。