深入理解 Linux CMA:连续内存分配器与大页池化机制深度实战

你的关注是我创作的最大动力!

关键词:Linux内核, CMA, 连续内存分配, HugeTLB, hugetlbfs, 大页池, DMA, 内存碎片, dma_alloc_coherent, 内存管理

描述:深度解析Linux内核CMA(Contiguous Memory Allocator)连续内存分配器的设计原理、算法实现与生产级调优。涵盖CMA区域初始化、迁移类型机制、设备树绑定、HugeTLB池化架构、hugetlbfs文件系统、透明大页THP与显式大页对比、内存碎片整理与CMA协同策略,以及GPU/DMA/多媒体等实际设备驱动中的CMA集成案例。

一、为什么需要连续内存:DMA 的物理约束

现代 SoC 上,大量外设(GPU 显示控制器、视频编解码器、DMA 控制器、Camera ISP、PCIe 端点设备)都需要物理上连续的内存块来进行 DMA 传输。原因很直接:

  • 硬件限制:传统 DMA 控制器只有一个基地址寄存器 长度寄存器,无法做 scatter-gather
  • 性能考量:连续内存避免 IOMMU 映射开销、TLB 抖动和多次传输调度
  • 带宽需求:视频帧缓冲、GPU 纹理动辄需要数百 KB 至数十 MB 的连续区域

在 x86 架构上,由于 IOMMU(VT-d)普及,这个问题相对容易解决——通过 IOMMU 可以把多个物理不连续的页面映射为一个连续的IOVA。但在嵌入式 ARM/ARM64 领域,大量低成本 SoC 没有 IOMMU 或 IOMMU 能力有限,此时物理连续内存就是刚需。

Linux 内核启动早期通过 memblock 预留专用内存给特殊设备,但这种静态预留方式笨拙且利用率低。CMA 的出现改变了这一切。

二、CMA 架构总览

2.1 核心思想

CMA(Contiguous Memory Allocator,连续内存分配器)的核心设计非常巧妙:

预留但不锁定——在系统内存中划出一块区域标记为 CMA,但允许普通可移动页面(movable pages)在其上分配。当设备驱动需要连续内存时,通过页面迁移(page migration)把已占用的可移动页面搬走,腾出连续空间。

这本质上是一种"先礼后兵"策略:

  1. 平时 CMA 区域可以被普通分配器用作可移动页面
  2. 设备需要时,通过迁移回收这些页面
  3. 不需要时归还给 buddy allocator

2.2 内核配置

CONFIG_CMA=y
CONFIG_CMA_DEBUG=y
CONFIG_CMA_DEBUGFS=y
CMA_SIZE_MBYTES=256

CMA 区域大小可通过以下方式指定:

  • 内核命令行参数:cma=256M 或 cma=256M@0x10000000-0x20000000
  • 设备树(Device Tree):linux,cma 节点
  • 内核配置 CMA_SIZE_MBYTES

2.3 CMA 初始化流程

start_kernel()
  └── setup_arch()
      └── arm64_memblock_init() / x86_64 equivalent
          └── memblock_alloc()  /* 早期预留 */
  └── mm_init()
      └── cma_init()
          └── cma_declare_contiguous()  /* CMA 区域声明 */
              └── memblock_alloc_range()  /* memblock 级预留 */
  └── ...
  └── device_initcall()
      └── cma_init_reserved_areas()  /* 释放到 buddy */

关键两步:

  1. 早期预留:在 memblock 阶段保留物理区域,防止被 buddy allocator 吃掉
  2. 延迟释放:在 CMA 子系统就绪后,将预留区域以 MIGRATE_CMA 迁移类型释放给 buddy allocator

2.4 MIGRATE_CMA 迁移类型

CMA 的核心机制建立在页面的迁移类型(migration type)之上。Linux 的 buddy allocator 将空闲页面按 MIGRATE_TYPES 分链表管理:

enum migratetype {
    MIGRATE_UNMOVABLE,   /* 内核代码、页表等 */
    MIGRATE_MOVABLE,     /* 用户页面、缓存等 */
    MIGRATE_RECLAIMABLE, /* 可回收的内核页面 */
    MIGRATE_PCPTYPES,    /* Per-CPU 页面缓存 */
    MIGRATE_HIGHATOMIC,  /* 高原子分配 */
    MIGRATE_ISOLATE,     /* 不可分配 */
    MIGRATE_TYPES,
};

对于 CMA 区域,使用 MIGRATE_CMA——这实际上是一个特殊标记的 MIGRATE_MOVABLE。当 CMA 分配请求到来时,分配器优先在 MIGRATE_CMA 链表中寻找,如果找不到,就迁移已分配的 MIGRATE_MOVABLE 页面到其他区域。

struct cma {
    unsigned long   base_pfn;
    unsigned long   count;
    unsigned long   *bitmap;
    unsigned int    order_per_bit; /* 一个 bit 表示 order 阶页面 */
    struct cma      *next;         /* 多个 CMA 区域链表 */
    const char      *name;
#define CMA_FLAGS_DEV    (1                        
                    
点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部
/* 跳过导航链接 (无障碍) */ .skip-link { position: absolute; top: -100px; left: 15px; z-index: 99999; padding: 8px 16px; background: #007bff; color: #fff; font-size: 14px; border-radius: 0 0 4px 4px; text-decoration: none; transition: top 0.2s; } .skip-link:focus { top: 0; outline: 3px solid #0056b3; }