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 分配流程与内部机制
下图展示了从驱动调用到物理页分配完成的完整流程:
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分配跟踪日志。