内存管理工程实践:memblock内存分配与故障定位实战 - 第2节
2.1 三层架构:从全局到区域
memblock 分配器采用三层数据结构来管理物理内存:全局控制器 → 类型描述符 → 区域节点。这种分层设计使得内存区域的增删、遍历和属性查询都极为高效。
核心认知: 三层结构对应三种粒度——
struct memblock 管理全局状态,struct memblock_type 管理同类区域集合,struct memblock_region 描述单块连续物理内存。
2.2 struct memblock:全局内存管家
struct memblock 是 memblock 分配器的顶层结构体,全局唯一实例为 memblock。它持有两个类型描述符(memory 和 reserved)以及当前分配状态。
// include/linux/memblock.h
struct memblock {
bool bottom_up; /* 是否从低地址向上分配 */
phys_addr_t current_limit; /* 当前分配上限地址 */
struct memblock_type memory; /* 可用物理内存区域 */
struct memblock_type reserved; /* 已预留内存区域 */
};
| 字段 | 类型 | 说明 |
|---|---|---|
| bottom_up | bool | 分配方向标志:true 表示从低地址向高地址分配,false 则相反。早期启动阶段通常为 true。 |
| current_limit | phys_addr_t | 动态分配上限,用于限制分配范围,防止侵占关键区域。 |
| memory | struct memblock_type | 记录系统所有可用物理内存的 region 集合。 |
| reserved | struct memblock_type | 记录已被预留(分配出去)的物理内存 region 集合。 |
调试技巧: 在早期启动代码中打印
memblock.current_limit 可以快速判断当前分配器的工作范围,常用于排查 DMA 或设备内存映射冲突。
2.3 struct memblock_type:同类区域的集合
struct memblock_type 描述一组相同属性的内存区域。它维护一个动态增长的 region 数组,并保证数组按基地址升序排列、无重叠、相邻区域已合并。
struct memblock_type {
unsigned long cnt; /* region 数组中有效条目数 */
unsigned long max; /* region 数组当前容量 */
phys_addr_t total_size; /* 所有 region 的总大小 */
struct memblock_region *regions; /* 指向 region 数组的指针 */
char *name; /* 类型名称,如 "memory" 或 "reserved" */
};
| 字段 | 说明 |
|---|---|
| cnt | 当前已使用的 region 数量。每次添加或删除区域时更新。 |
| max | region 数组的最大容量。当 cnt 达到 max 时,会触发数组扩容。 |
| total_size | 所有 region 的物理大小之和,用于快速获取该类内存的总量。 |
| regions | 指向 struct memblock_region 数组的指针。早期使用静态数组,后期可能重分配。 |
| name | 调试用字符串,在 /sys/kernel/debug/memblock 中可见。 |
注意: region 数组的排序和合并操作是 memblock 的核心维护逻辑。每次插入新区域时,memblock 会执行线性扫描,合并相邻或重叠区域,确保数组始终紧凑有序。这一操作的时间复杂度为 O(n),在 region 数量较少时效率很高。
2.4 struct memblock_region:最小管理单元
struct memblock_region 描述一段连续的物理内存区域。它是 memblock 分配器操作的最小粒度,所有内存的添加、预留、释放最终都归结为对 region 的增删改。
struct memblock_region {
phys_addr_t base; /* 区域起始物理地址 */
phys_addr_t size; /* 区域大小(字节) */
enum memblock_flags flags; /* 区域属性标志 */
};
| 字段 | 说明 |
|---|---|
| base | 物理基地址,必须与体系结构页对齐(通常 4KB)。 |
| size | 区域长度,单位字节。base + size 不能溢出。 |
| flags | 描述内存属性的位掩码,如可缓存、不可缓存、设备内存等。 |
flags 字段常用的枚举值包括:
- MEMBLOCK_NONE (0) — 普通内存,无特殊属性。
- MEMBLOCK_HOTPLUG — 支持热插拔的内存区域。
- MEMBLOCK_MIRROR — 镜像内存,用于高可靠性场景。
- MEMBLOCK_NOMAP — 不映射到内核虚拟地址空间(如某些固件保留区)。
实战要点: 在调试内存预留问题时,重点关注
flags 字段。例如,DMA 缓冲区通常要求 MEMBLOCK_NONE,而设备 MMIO 区域应标记 MEMBLOCK_NOMAP 以防止内核误映射。
2.5 数据结构关系总结
三个结构体之间的协作关系可以概括为:
- 全局入口:
struct memblock是唯一全局变量,通过memblock.memory和memblock.reserved访问两个类型集合。 - 类型管理:
struct memblock_type维护一个动态 region 数组,提供按地址排序、合并、扩容等内部操作。 - 区域描述:
struct memblock_region是原子单元,记录单块连续内存的基址、大小和属性。
| 结构体 | 角色 | 关键操作 |
|---|---|---|
| struct memblock | 全局控制器 | 分配方向控制、分配上限管理 |
| struct memblock_type | 区域集合 | 添加/删除 region、排序合并、扩容 |
| struct memblock_region | 最小单元 | 记录 base/size/flags,被遍历和修改 |
学习路径建议: 理解这三层结构后,下一步应关注 memblock 的 添加内存(memblock_add)和 预留内存(memblock_reserve)两个核心 API,它们直接操作 region 数组的插入和合并逻辑。