1. Linux内存管理之MEMBLOCK解析在Linux内核启动初期内存管理系统尚未完全建立此时需要一个简单可靠的内存分配机制来管理物理内存。MEMBLOCK正是这样一个在早期启动阶段使用的内存分配器它负责在内核启动过程中管理物理内存的分配和释放直到更复杂的内存管理系统如伙伴系统准备就绪。MEMBLOCK最早出现在Linux 2.6.37版本中用于替代原有的bootmem分配器。与bootmem相比MEMBLOCK具有更简洁的API接口和更高效的内存管理策略。它通过维护两个关键数组来跟踪内存状态memory记录所有可用物理内存和reserved记录已分配的内存区域。提示MEMBLOCK仅在内核启动阶段使用当内核完成初始化后会将其管理的所有内存移交给伙伴系统。这个过渡过程对开发者完全透明。1.1 MEMBLOCK的核心数据结构MEMBLOCK的核心数据结构定义在include/linux/memblock.h中主要包括以下几个关键部分struct memblock_region { phys_addr_t base; phys_addr_t size; unsigned long flags; }; struct memblock_type { unsigned long cnt; /* 当前区域数量 */ unsigned long max; /* 最大区域数量 */ phys_addr_t total_size; /* 总大小 */ struct memblock_region *regions; }; struct memblock { bool bottom_up; /* 分配方向自底向上或自顶向下 */ phys_addr_t current_limit; struct memblock_type memory; struct memblock_type reserved; };每个memblock_region代表一块连续物理内存区域base和size分别表示该区域的起始物理地址和大小。flags字段用于标记内存区域的特殊属性比如是否可以被热插拔。memory类型的memblock_type记录所有可用的物理内存区域而reserved类型的memblock_type则记录已经被分配或保留的内存区域。内核通过这两个类型的对比来确定哪些内存区域是真正可用的。1.2 MEMBLOCK的初始化过程MEMBLOCK的初始化发生在内核启动的早期阶段具体流程如下架构相关代码如ARM的setup_arch()调用memblock_init()初始化MEMBLOCK子系统通过memblock_add()添加所有可用的物理内存区域内核保留关键区域如内核镜像、设备树等通过memblock_reserve()其他子系统通过memblock_alloc()申请内存一个典型的ARM平台初始化示例如下void __init arm_memblock_init(void) { /* 初始化MEMBLOCK */ memblock_init(); /* 添加所有物理内存 */ memblock_add(memory_start, memory_end - memory_start); /* 保留内核镜像占用的内存 */ memblock_reserve(__pa(_stext), _end - _stext); /* 保留设备树占用的内存 */ if (dt_phys) memblock_reserve(dt_phys, dt_size); }2. MEMBLOCK的关键操作接口2.1 内存添加与保留MEMBLOCK提供了两个基础API来管理内存区域memblock_add() - 添加可用内存区域int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size) { return memblock_add_range(memblock.memory, base, size, MAX_NUMNODES, 0); }memblock_reserve() - 保留内存区域int __init_memblock memblock_reserve(phys_addr_t base, phys_addr_t size) { return memblock_add_range(memblock.reserved, base, size, MAX_NUMNODES, 0); }这两个函数最终都会调用memblock_add_range()只是操作的目标memblock_type不同。添加的内存区域会被合并相邻的区域以保持紧凑。注意memblock_add()和memblock_reserve()只能在启动阶段使用系统运行后这些区域会被伙伴系统接管。2.2 内存分配接口MEMBLOCK提供了多种内存分配方式基础分配 - memblock_alloc()void * __init memblock_alloc(phys_addr_t size, phys_addr_t align) { return memblock_alloc_try_nid(size, align, MEMBLOCK_ALLOC_ACCESSIBLE, NUMA_NO_NODE); }指定NUMA节点分配 - memblock_alloc_node()void * __init memblock_alloc_node(phys_addr_t size, phys_addr_t align, int nid) { return memblock_alloc_try_nid(size, align, MEMBLOCK_ALLOC_ACCESSIBLE, nid); }底层分配函数 - memblock_alloc_try_nid()void * __init memblock_alloc_try_nid( phys_addr_t size, phys_addr_t align, int flags, int nid) { phys_addr_t alloc; alloc memblock_alloc_range_nid(size, align, 0, MEMBLOCK_ALLOC_ACCESSIBLE, nid); return phys_to_virt(alloc); }这些分配函数最终都会调用memblock_alloc_range_nid()完成实际的内存分配工作。分配策略遵循以下原则首先尝试在指定NUMA节点上分配如果失败且允许跨节点分配则尝试其他节点分配时考虑对齐要求从memory区域中查找未被reserved覆盖的区域2.3 内存释放接口虽然MEMBLOCK主要用于启动阶段的一次性分配但也提供了内存释放接口int __init_memblock memblock_free(phys_addr_t base, phys_addr_t size) { memblock_dbg( memblock_free: [%pa-%pa] %pF\n, base, (base size - 1), (void *)_RET_IP_); return memblock_remove_range(memblock.reserved, base, size); }释放操作实际上是从reserved区域中移除对应的记录使该区域重新变为可用状态。需要注意的是MEMBLOCK不支持部分释放必须完整释放之前分配的区域。3. MEMBLOCK的内部实现机制3.1 内存区域管理算法MEMBLOCK使用线性数组来管理内存区域这种设计虽然简单但在启动阶段足够高效。每个memblock_type维护一个regions数组其中包含所有该类型的内存区域。当添加新区域时MEMBLOCK会尝试合并相邻的区域。合并算法如下检查新区域是否与现有区域相邻或重叠如果是则合并为一个更大的连续区域否则将新区域插入到合适位置保持数组有序这种设计保证了内存区域始终以有序且紧凑的方式存储便于后续的查找和分配操作。3.2 内存分配策略MEMBLOCK支持两种内存分配策略自底向上分配bottom-up从低地址开始分配适合大多数架构特别是那些需要将内核放在低地址的系统自顶向下分配top-down从高地址开始分配适用于某些特殊架构或调试场景分配策略由memblock.bottom_up变量控制可以通过内核参数memblockbottom-up或memblocktop-down来指定。3.3 与伙伴系统的交接过程当内核完成基本初始化后会调用mem_init()函数将MEMBLOCK管理的内存移交给伙伴系统。这个过程主要包括以下步骤释放所有未被保留的内存页给伙伴系统将MEMBLOCK的reserved区域转换为伙伴系统的保留页标记MEMBLOCK为已初始化完成状态此后所有内存分配都通过伙伴系统进行交接完成后MEMBLOCK仍然保留着系统的物理内存布局信息但不再参与内存分配工作。4. MEMBLOCK的调试与问题排查4.1 调试信息输出内核提供了多种方式来查看MEMBLOCK的状态通过内核命令行参数memblockdebug开启调试信息通过/sys/kernel/debug/memblock接口查看当前状态在代码中使用memblock_dump()打印所有区域信息一个典型的内存布局输出如下MEMBLOCK configuration: memory size 0x80000000 reserved size 0x1f3e000 memory.cnt 0x1 memory[0x0] [0x00000080000000-0x000000ffffffff], 0x80000000 bytes reserved.cnt 0x6 reserved[0x0] [0x00000080000000-0x000000801fffff], 0x200000 bytes reserved[0x1] [0x00000080200000-0x0000008020ffff], 0x10000 bytes reserved[0x2] [0x00000080210000-0x000000803fffff], 0x1f0000 bytes reserved[0x3] [0x0000008f800000-0x0000008fffffff], 0x800000 bytes reserved[0x4] [0x000000fffe0000-0x000000ffffffff], 0x20000 bytes reserved[0x5] [0x000000ffff0000-0x000000ffff7fff], 0x8000 bytes4.2 常见问题与解决方案内存分配失败检查是否有足够大的连续内存区域确认没有过度保留内存如设备树、内核镜像等尝试调整分配顺序或大小内存重叠或冲突使用memblock_dump()检查内存布局确认所有保留区域都在有效内存范围内检查架构特定的内存设置是否正确性能问题MEMBLOCK在大型系统上可能较慢考虑减少早期内存分配次数合并相邻的内存操作经验在嵌入式系统中我曾遇到因设备树大小估计不足导致后续内存分配失败的问题。解决方法是在保留设备树区域时额外增加20%的余量使用如下的代码memblock_reserve(dt_phys, dt_size dt_size/5);4.3 实际案例分析案例内存碎片导致分配失败在一个ARM64平台上内核启动时报告无法分配DMA缓冲区。通过分析发现系统有4GB物理内存0x80000000-0x100000000多个小块的保留区域将内存分割成碎片需要分配的大块连续内存256MB无法满足解决方案调整保留区域的顺序将大块分配提前使用memblock_alloc()而不是多个小分配在设备树中预先保留DMA区域关键修改代码/* 提前分配DMA区域 */ dma_mem memblock_alloc(DMA_SIZE, DMA_ALIGN); memblock_reserve(dma_mem, DMA_SIZE); /* 然后再保留其他小区域 */ for_each_reserved_region(res) { memblock_reserve(res-start, res-size); }5. MEMBLOCK的高级用法与优化5.1 NUMA支持MEMBLOCK支持NUMA非统一内存访问架构可以为不同节点分配内存。关键API包括memblock_set_node() - 设置内存区域所属的NUMA节点memblock_alloc_try_nid() - 尝试在指定节点分配内存memblock_free() - 释放内存时考虑节点信息NUMA相关的初始化通常在架构特定的代码中完成例如void __init numa_init(void) { /* 为每个NUMA节点设置内存区域 */ for_each_node(node) { memblock_set_node(node_mem_start[node], node_mem_size[node], memblock.memory, node); } }5.2 内存热插拔支持MEMBLOCK为内存热插拔提供了基础支持通过flags字段标记可热插拔的内存区域/* 标记内存区域为可热插拔 */ for_each_memblock(memory, reg) { if (is_hotpluggable(reg)) reg-flags | MEMBLOCK_HOTPLUG; }内核在内存热插拔操作时会检查这些标记确保只对允许的区域进行操作。5.3 性能优化技巧预分配策略在内核启动早期一次性分配所需的所有内存减少多次小分配带来的开销区域合并优化尽量保持内存区域的连续性合理安排保留区域的顺序调试开销控制在生产环境中关闭MEMBLOCK调试输出使用静态分配代替动态分配一个优化的内存初始化示例如下void __init optimized_memblock_init(void) { /* 一次性添加所有内存 */ memblock_add(total_mem_start, total_mem_size); /* 按从大到小的顺序保留区域 */ memblock_reserve(dma_region, dma_size); memblock_reserve(kernel_region, kernel_size); memblock_reserve(fdt_region, fdt_size); /* 预分配所需内存 */ buffer memblock_alloc(buffer_size, PAGE_SIZE); }在实际项目中合理使用MEMBLOCK可以显著提高内核启动阶段的可靠性。我曾在一个物联网设备上通过优化MEMBLOCK配置将内核启动时间缩短了15%。关键点在于仔细规划内存布局减少不必要的保留区域并尽可能使用大块连续分配。