4. 设备树中配置CMA:节点语法、reg属性、alignment与no-map标志
在嵌入式Linux系统中,CMA(Contiguous Memory Allocator)区域的配置通常通过设备树(Device Tree)的 reserved-memory 节点完成。设备树为内核提供了描述系统内存布局的标准化方式,CMA区域的起始地址、大小、对齐要求以及映射属性都通过特定的DT属性和标志来定义。本节将深入解析这些核心语法元素。
4.1 reserved-memory 节点基本语法
设备树中,所有预留内存区域(包括CMA)都集中定义在根节点下的 reserved-memory 子节点中。每个具体的预留区域作为一个子节点,通过 compatible 属性标识其用途(如 shared-dma-pool 用于CMA),并通过 reg 属性描述物理地址范围。
compatible = "shared-dma-pool" 并配合 reusable 属性。
// 设备树中 CMA 区域的标准定义结构
/ {
reserved-memory {
#address-cells = <2>; // 64位地址空间
#size-cells = <2>;
ranges;
// 定义一个 CMA 区域
linux,cma {
compatible = "shared-dma-pool";
reg = <0x00 0x10000000 0x00 0x10000000>; // 起始 0x10000000,大小 256MB
alignment = <0x00 0x00400000>; // 4MB 对齐
reusable; // 可复用(CMA 核心标志)
linux,cma-default; // 标记为默认 CMA 池
};
};
};
4.2 reg 属性:定义物理地址与大小
reg 属性是设备树中描述设备物理地址空间的标准方式。对于CMA区域,reg 定义了两部分信息:起始物理地址和区域大小。其编码格式由父节点(reserved-memory)的 #address-cells 和 #size-cells 决定。
| 参数 | 说明 | 示例值(32位) | 示例值(64位) |
|---|---|---|---|
| 起始地址 | CMA区域的物理基地址,必须与系统内存映射对齐 | <0x10000000> |
<0x00 0x10000000> |
| 区域大小 | CMA区域的字节长度,通常为2的幂次 | <0x10000000> (256MB) |
<0x00 0x10000000> (256MB) |
| 地址单元 | #address-cells 决定地址字段的32位字数量 |
1 | 2 |
| 大小单元 | #size-cells 决定大小字段的32位字数量 |
1 | 2 |
#address-cells = <2> 和 #size-cells = <2>,以保持与未来扩展的兼容性。内核解析 reg 时会自动根据父节点属性调整读取宽度。
4.3 alignment 属性:对齐约束与性能
alignment 属性指定了CMA区域起始地址的对齐要求。内核在分配CMA区域时,会尝试将区域起始地址对齐到该属性指定的边界。对齐不仅影响内存布局,还直接关系到DMA操作的效率和TLB(Translation Lookaside Buffer)利用率。
- 对齐粒度: 通常设置为页面大小(4KB)的倍数,常见值为 1MB、2MB、4MB 或 1GB。
- 性能影响: 较大的对齐值(如 1GB)允许使用块映射(Block Mapping),减少页表开销,提升大块DMA传输性能。
- 地址单元: 与
reg类似,alignment的编码宽度也由#address-cells决定。 - 默认行为: 如果未指定
alignment,内核默认使用架构相关的对齐值(ARM64 通常为 2MB)。
// alignment 属性示例:不同对齐粒度
// 4KB 对齐(页面粒度)
alignment = <0x00001000>;
// 2MB 对齐(大页粒度,ARM64 默认)
alignment = <0x00200000>;
// 1GB 对齐(块映射粒度,适合大块 CMA)
alignment = <0x40000000>;
4.4 no-map 标志:映射控制与安全
no-map 是一个布尔标志,指示内核不要为CMA区域创建页表映射。这意味着该区域在虚拟地址空间中不可见,只能通过物理地址直接访问。这一标志在以下场景中至关重要:
- 安全内存: 防止CPU通过虚拟地址访问敏感数据(如加密密钥、安全监控数据)。
- 设备独占内存: 某些外设(如GPU、VPU)需要独占物理内存,不希望CPU缓存干扰。
- 多核共享: 在异构计算中,避免CPU映射导致缓存一致性问题。
| 标志 | 映射行为 | 典型用途 | 与CMA兼容性 |
|---|---|---|---|
| 无 no-map | 内核创建线性映射,CPU可正常读写 | 标准CMA,驱动通过虚拟地址访问 | 完全兼容 |
| no-map | 内核不创建任何页表映射 | 安全内存、设备独占内存 | 需谨慎使用,驱动只能通过DMA或ioremap访问 |
| no-map + reusable | 区域可复用但无CPU映射 | VPU/GPU专用CMA,避免CPU缓存污染 | 部分架构支持,需驱动配合 |
4.5 CMA区域配置完整示例与SVG结构图
以下是一个包含多个CMA区域的完整设备树配置示例,展示了不同用途的预留内存区域如何协同工作。
/ {
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
// 默认 CMA 区域:256MB,4MB 对齐,可复用
linux,cma {
compatible = "shared-dma-pool";
reg = <0x00 0x10000000 0x00 0x10000000>;
alignment = <0x00 0x00400000>;
reusable;
linux,cma-default;
};
// VPU 专用 CMA:128MB,2MB 对齐,no-map 防止CPU缓存干扰
vpu,cma {
compatible = "shared-dma-pool";
reg = <0x00 0x20000000 0x00 0x08000000>;
alignment = <0x00 0x00200000>;
reusable;
no-map;
};
// 安全内存区域:64MB,1MB 对齐,不可复用,无映射
secure,memory {
compatible = "shared-dma-pool";
reg = <0x00 0x28000000 0x00 0x04000000>;
alignment = <0x00 0x00100000>;
no-map;
};
};
};
4.6 内核解析流程与属性校验
内核在启动过程中通过 early_init_dt_scan_reserved_mem() 函数解析 reserved-memory 节点。解析流程遵循以下步骤:
- 节点遍历: 遍历所有
reserved-memory的子节点,检查compatible属性是否为shared-dma-pool。 - 地址解析: 调用
of_read_number()读取reg属性,根据父节点的#address-cells和#size-cells解码起始地址和大小。 - 对齐校验: 读取
alignment属性(如果存在),校验起始地址是否满足对齐要求;若不满足,内核会尝试调整起始地址。 - 标志处理: 检查
no-map和reusable标志,设置对应的内存区域标志位(如MEMBLOCK_NOMAP)。 - 区域注册: 调用
memblock_reserve()或memblock_alloc_range()将区域注册到内存分配器中。
drivers/of/of_reserved_mem.c 中的 __reserved_mem_alloc_size() 函数。该函数负责处理 reg、alignment 和 no-map 的联合校验。
4.7 常见配置错误与调试方法
| 错误类型 | 现象 | 根因 | 解决方案 |
|---|---|---|---|
| reg 地址重叠 | 内核启动报 "reserved memory: overlap" | 多个CMA区域地址范围交叉 | 检查所有 reg 地址范围,确保无重叠 |
| 对齐失败 | CMA区域分配失败,dmesg 显示 "alignment failed" | 指定对齐值大于系统可用连续内存块 | 减小 alignment 值,或调整内存布局 |
| no-map 冲突 | 驱动访问CMA区域时触发页错误 | 驱动未使用 ioremap 直接访问物理地址 |
驱动中调用 ioremap() 创建映射后再访问 |
| 地址单元不匹配 | 解析出的地址为0或异常大值 | #address-cells 与 reg 编码宽度不一致 |
确保父节点和子节点的地址单元声明一致 |
memblock=debug 可以打印详细的内存区域注册信息,包括每个CMA区域的起始地址、大小和对齐状态。使用 cat /proc/iomem | grep "CMA" 可以查看已注册的CMA区域。