4、CMA分配API:dma_alloc_from_contiguous()、cma_alloc()函数原型、参数含义与返回值

在CMA子系统中,驱动开发者主要通过两个核心API向CMA区域申请连续物理内存:dma_alloc_from_contiguous() 和 cma_alloc()。前者是DMA层封装接口,后者是CMA核心分配函数。理解两者的原型、参数语义与返回值,是正确使用CMA缓冲的基础。

4.1 核心分配函数:cma_alloc()

cma_alloc() 是CMA子系统最底层的分配入口,直接操作CMA区域位图,负责从指定CMA区域中分配连续物理页。其函数原型如下:

struct page *cma_alloc(struct cma *cma, unsigned long count,
                       unsigned int align, bool no_warn);

参数含义详解

参数 类型 含义 典型值/说明
cma struct cma * 目标CMA区域对象指针 通过 cma_get_base() 或全局 cma_areas 获取
count unsigned long 请求分配的连续物理页数量 例如 4 表示16KB(4页×4KB)
align unsigned int 对齐阶数(2^align 字节对齐) 0 表示页对齐,4 表示64KB对齐
no_warn bool 分配失败时是否抑制内核警告 true 静默失败,false 打印警告

返回值语义

  • 成功:返回指向连续物理页块首地址的 struct page * 指针。
  • 失败:返回 NULL。失败原因包括:CMA区域内存不足、无法满足对齐要求、迁移页面时发生竞争或超时。
关键提示: cma_alloc() 在分配过程中会触发页面迁移(migrate),将已分配给可移动页面的用户态内存移走,腾出连续空间。因此该函数可能阻塞,不能在原子上下文中调用。

4.2 DMA层封装:dma_alloc_from_contiguous()

dma_alloc_from_contiguous() 是DMA子系统对 cma_alloc() 的封装,专为DMA设备驱动设计,自动处理设备与CMA区域的绑定关系。其函数原型如下:

struct page *dma_alloc_from_contiguous(struct device *dev, size_t size,
                                       unsigned int count, unsigned int align,
                                       bool no_warn);

参数含义详解

参数 类型 含义 与 cma_alloc() 的差异
dev struct device * 发起分配请求的设备对象 用于查找设备绑定的CMA区域
size size_t 请求分配的字节数 新增参数,cma_alloc() 无此参数
count unsigned int 请求分配的连续物理页数量 与 cma_alloc() 相同
align unsigned int 对齐阶数 与 cma_alloc() 相同
no_warn bool 分配失败时是否抑制内核警告 与 cma_alloc() 相同
设计意图: dma_alloc_from_contiguous() 的 size 参数用于校验请求大小是否超过设备DMA能力,同时内部通过 dev->cma_area 自动定位CMA区域,驱动无需手动管理CMA对象指针。

4.3 分配流程与内部机制

下图展示了从驱动调用到物理页分配完成的完整流程:

CMA分配API调用流程 CMA分配API调用流程 驱动调用 dma_alloc_from_contiguous() DMA层:解析 dev→cma_area cma_alloc() 位图分配 页面迁移(migrate) 返回 struct page * 说明:dma_alloc_from_contiguous() 内部自动完成设备→CMA区域映射,驱动无需手动指定 cma 对象

4.4 两个API的对比与选择

对比维度 dma_alloc_from_contiguous() cma_alloc()
使用层级 DMA设备驱动层 CMA核心层
CMA区域定位 通过 dev->cma_area 自动绑定 需手动传入 struct cma *
参数复杂度 5个参数(含size校验) 4个参数
适用场景 标准DMA驱动,设备树已配置CMA区域 CMA子系统内部、非DMA场景、自定义区域
错误处理 封装了设备一致性校验 更底层,需调用者自行校验
避坑指南: 不要混用两个API的CMA区域指针。如果设备通过DMA API绑定CMA区域,应始终使用 dma_alloc_from_contiguous();直接使用 cma_alloc() 传入 dev->cma_area 虽然能工作,但会绕过DMA层的设备校验,可能导致DMA地址空间不匹配。

4.5 典型调用示例

以下代码展示了一个V4L2摄像头驱动在缓冲区初始化阶段使用 dma_alloc_from_contiguous() 的典型模式:

#include <linux/dma-contiguous.h>

static int cam_buf_alloc(struct device *dev, struct cam_buffer *buf,
                         size_t size)
{
    struct page *page;
    unsigned int count, align;
    dma_addr_t dma_handle;

    /* 计算所需页数和对齐阶数 */
    count = PAGE_ALIGN(size) >> PAGE_SHIFT;
    align = get_order(size);  /* 2^align 字节对齐 */

    /* 从设备绑定的CMA区域分配连续内存 */
    page = dma_alloc_from_contiguous(dev, size, count, align, false);
    if (!page) {
        dev_err(dev, "CMA alloc failed: size=%zu, count=%u\n",
                size, count);
        return -ENOMEM;
    }

    /* 将物理页映射到DMA地址空间 */
    dma_handle = dma_map_page_attrs(dev, page, 0, size,
                                    DMA_BIDIRECTIONAL,
                                    DMA_ATTR_SKIP_CPU_SYNC);
    if (dma_mapping_error(dev, dma_handle)) {
        dma_release_from_contiguous(dev, page, count);
        return -EIO;
    }

    buf->page = page;
    buf->dma_addr = dma_handle;
    buf->size = size;
    return 0;
}

4.6 分配失败常见原因

  • CMA区域耗尽:所有可移动页面已被锁定,无法迁移出足够连续空间。
  • 对齐冲突:请求的对齐阶数超过CMA区域最大对齐能力。
  • 迁移超时:页面迁移过程中被高优先级任务抢占,导致分配超时。
  • 设备未绑定CMA:dev->cma_area 为NULL,dma_alloc_from_contiguous() 直接返回NULL。
调试建议: 在 /sys/kernel/debug/cma/ 目录下可以查看每个CMA区域的 used、max_used、bitmap 等信息,帮助定位分配失败原因。设置 cma_debug=1 内核参数可开启CMA分配跟踪日志。