4. DMA池的使用
任务目标
本案例演示如何使用Linux内核DMA池API(dma_pool_create / dma_pool_alloc / dma_pool_free / dma_pool_destroy)管理固定大小的DMA缓冲区。DMA池适用于频繁分配/释放相同大小缓冲区的场景,可避免每次调用页分配器带来的开销,并保证缓冲区在指定DMA区域内物理连续且对齐。
核心实现
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/dmapool.h>
#define POOL_SIZE 256 /* 每个缓冲区大小(字节) */
#define POOL_ALIGN 32 /* 对齐要求 */
#define POOL_BOUND 0 /* 无边界限制 */
#define ALLOC_NUM 4 /* 测试分配次数 */
static struct dma_pool *pool;
static void *vaddr[ALLOC_NUM];
static dma_addr_t dma_addr[ALLOC_NUM];
static int __init pool_demo_init(void)
{
struct device *dev = &platform_bus;
int i;
/* 1. 创建DMA池:固定大小256字节,32字节对齐 */
pool = dma_pool_create("demo_pool", dev, POOL_SIZE, POOL_ALIGN, POOL_BOUND);
if (!pool) {
pr_err("dma_pool_create failed\n");
return -ENOMEM;
}
/* 2. 从池中分配多个缓冲区 */
for (i = 0; i < ALLOC_NUM; i++) {
vaddr[i] = dma_pool_alloc(pool, GFP_KERNEL, &dma_addr[i]);
if (!vaddr[i]) {
pr_err("dma_pool_alloc %d failed\n", i);
goto err_alloc;
}
pr_info("alloc[%d]: vaddr=%px dma_addr=%pad\n",
i, vaddr[i], &dma_addr[i]);
}
/* 3. 验证:写入并读回数据,确认映射有效 */
for (i = 0; i < ALLOC_NUM; i++) {
memset(vaddr[i], 0x5A + i, POOL_SIZE);
if (((u8 *)vaddr[i])[0] != (0x5A + i) ||
((u8 *)vaddr[i])[POOL_SIZE - 1] != (0x5A + i)) {
pr_err("data check failed on buffer %d\n", i);
goto err_alloc;
}
}
pr_info("data check passed for all %d buffers\n", ALLOC_NUM);
/* 4. 释放缓冲区 */
for (i = 0; i < ALLOC_NUM; i++)
dma_pool_free(pool, vaddr[i], dma_addr[i]);
/* 5. 销毁池 */
dma_pool_destroy(pool);
pr_info("pool demo completed successfully\n");
return 0;
err_alloc:
/* 清理已分配的缓冲区 */
while (i-- > 0)
dma_pool_free(pool, vaddr[i], dma_addr[i]);
dma_pool_destroy(pool);
return -ENOMEM;
}
static void __exit pool_demo_exit(void)
{
/* 模块退出时无需额外操作,所有资源已在init中释放 */
}
module_init(pool_demo_init);
module_exit(pool_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("DMA pool alloc/free demo");
编译与运行
# Makefile(与源码同目录)
obj-m += dma_pool_demo.o
# 编译
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
# 加载(需要root权限)
insmod dma_pool_demo.ko
# 查看内核日志
dmesg | tail -20
# 卸载
rmmod dma_pool_demo
验证方法与判定标准
- 加载成功标志:
dmesg输出包含alloc[0]至alloc[3]四行,每行显示虚拟地址和DMA地址;随后出现data check passed for all 4 buffers和pool demo completed successfully。 - 地址有效性:每个
dma_addr值非零且与虚拟地址对应;不同缓冲区地址互不重叠。 - 数据完整性:每个缓冲区首字节和末字节均等于
0x5A + i,证明CPU可正常访问该DMA缓冲区。 - 释放与销毁:模块卸载(
rmmod)无警告或错误,说明池已正确销毁,无内存泄漏。
关键说明
- 本示例使用
&platform_bus作为DMA设备,适用于无真实硬件依赖的演示场景。实际驱动中应传入具体设备指针(如pdev->dev)。 dma_pool_alloc返回的虚拟地址可直接用于CPU访问;dma_addr_t值用于外设DMA传输。两者通过dma_pool_free成对释放。- 若需在分配后立即将缓冲区交给DMA外设,需根据总线方向调用
dma_map_single或使用dma_pool_alloc配合一致性映射(本池默认已保证一致性)。