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配合一致性映射(本池默认已保证一致性)。