created_at: “2026-04-28”
updated_at: “2026-04-28”
tags:
Linux
Kernel
memory management
memblock
boot memory
physical memory knowledge_sources:
page: concepts/memory-management version: “6.12.17” quoted_at: “2026-04-28”
本文分析 Linux 内核启动阶段的物理内存分配器 memblock 的设计原理、核心数据结构和关键 API。重点回答以下问题:
为什么需要 memblock?它替代了什么?
memblock 如何管理物理内存区域?
核心分配和保留 API 的执行流程是什么?
何时以及如何将内存管理权移交给伙伴系统?
本文分析基于 Linux 7.0 源码。
mm/memblock.c | |
include/linux/memblock.h | |
git://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git |
理解 memblock 需要以下基础知识:
物理内存布局:系统启动时,BIOS/UEFI 或设备树提供物理内存区域信息
伙伴系统(Buddy System):Linux 常规运行时的物理内存分配器,在启动后期才初始化
Bootmem:旧的启动期内存分配器,基于位图(bitmap)实现,已被 memblock 替代
Early Boot 阶段:内核解压、早期初始化到伙伴系统就绪之前的阶段,此阶段无法使用 kmalloc/vmalloc
memblock 从 Linux 3.0 左右开始替代 bootmem,成为主流的早期内存分配器。与 bootmem 的位图方式不同,memblock 基于区域(region)列表管理内存,支持不连续内存和更灵活的区域属性标记。
Open Question: memory-management.md 中标注"6.12.17 移除了 bootmem,使用 memblock",但 memblock 实际在 ~3.0 内核就已引入。该页面的 kernel_version 标注与内容存在不一致,需进一步确认具体移除 bootmem 的内核版本。
memblock 的核心设计是将物理内存视为连续区域(regions)的集合,而非单个页帧。所有操作都在 region 级别进行。
/* include/linux/memblock.h:34 */struct memblock_region {phys_addr_t base; /* 区域基地址 */phys_addr_t size; /* 区域大小 */enum memblock_flags flags; /* 区域属性标志 */int nid; /* NUMA 节点 ID(仅 CONFIG_NUMA 时存在) */};
每个 region 描述一段连续的物理内存,包含基地址、大小、属性标志和 NUMA 节点信息。
/* include/linux/memblock.h:53 */struct memblock_type {unsigned long cnt; /* 当前区域数量 */unsigned long max; /* 数组容量上限 */phys_addr_t total_size; /* 所有区域总大小 */struct memblock_region *regions; /* 区域数组指针 */char *name; /* 类型名称("memory"/"reserved"/"physmem") */};
memblock_type 管理一组同类型的 region。cnt 记录实际使用数量,max 记录数组容量。当 region 数量超过 max 时,会自动触发数组扩容。
/* include/linux/memblock.h:79 */struct memblock {bool bottom_up; /* 分配方向:true=自底向上,false=自顶向下 */phys_addr_t current_limit; /* 当前分配上限物理地址 */struct memblock_type memory; /* 可用内存区域 */struct memblock_type reserved; /* 已保留区域 */struct memblock_type physmem; /* 实际物理内存(仅部分架构支持) */};
全局实例(编译时静态初始化):
/* mm/memblock.c:130 */struct memblock memblock __initdata_memblock = {.memory.regions = memblock_memory_init_regions,.memory.max = INIT_MEMBLOCK_MEMORY_REGIONS, /* 默认 128 */.memory.name = "memory",.reserved.regions = memblock_reserved_init_regions,.reserved.max = INIT_MEMBLOCK_RESERVED_REGIONS, /* 默认 128 */.reserved.name = "reserved",.bottom_up = false,.current_limit = MEMBLOCK_ALLOC_ANYWHERE,};
/* mm/memblock.c:125-127 */static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_MEMORY_REGIONS] __initdata_memblock;static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdata_memblock;
memblock 在编译时静态初始化,无需动态分配。memory 和 reserved 各有 128 个初始 region 槽位(INIT_MEMBLOCK_REGIONS = 128)。这保证了它在启动最早期就可使用。
MEMBLOCK_NONE | ||
MEMBLOCK_HOTPLUG | ||
MEMBLOCK_MIRROR | ||
MEMBLOCK_NOMAP | ||
MEMBLOCK_DRIVER_MANAGED | ||
MEMBLOCK_RSRV_NOINIT | ||
MEMBLOCK_RSRV_KERN | ||
MEMBLOCK_KHO_SCRATCH |
下图展示了 memblock 的三级层次结构:

上图说明:
顶层:全局 struct memblock 实例,编译时静态初始化
中间层:三个 struct memblock_type(memory、reserved、physmem),各自管理一组 region
底层:region 数组,初始各 128 个槽位,运行时动态扩容
memblock 相关配置项定义在 mm/Kconfig 中。以下列出与本文分析相关的配置项:
# mm/Kconfigconfig HAVE_MEMBLOCK_PHYS_MAPbool
这是一个架构级选择配置(bool,用户不可见)。启用的架构可以在 memblock 中维护 physmem 类型,用于记录实际物理内存(不受 mem= 命令行参数限制)。仅在 CONFIG_HAVE_MEMBLOCK_PHYS_MAP 为 y 时,memblock_physmem_init_regions 和 physmem 结构才会被编译。
/* mm/memblock.c:127-130 */#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAPstatic struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS];#endif/* mm/memblock.c:143-148 */#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAPstruct memblock_type physmem = {.regions = memblock_physmem_init_regions,.max = INIT_PHYSMEM_REGIONS,.name = "physmem",};#endif
# mm/Kconfigconfig ARCH_KEEP_MEMBLOCKbool
启用后,memblock 的 region 数据在 memblock_discard() 后不会被释放。这允许在正常运行阶段继续查询 memblock 数据(例如用于内存热插拔的合法性检查)。默认不启用。
# mm/Kconfigconfig MEMBLOCK_KHO_SCRATCHbool
启用 memblock 对 kexec handover(KHO)临时内存的支持。被 kernel/liveupdate/Kconfig 的 CONFIG_LIVEUPDATE 自动 select。
CONFIG_HAVE_MEMBLOCK_PHYS_MAP | |||
CONFIG_ARCH_KEEP_MEMBLOCK | |||
CONFIG_MEMBLOCK_KHO_SCRATCH |
注意:memblock 核心功能(memory + reserved 两种类型)不依赖任何可选 Kconfig。所有架构默认启用 memblock,这是内核启动阶段内存管理的基础设施。
memblock 在启动阶段的调用链如下:
架构早期初始化 (arch/*)└─ memblock_add() / memblock_add_node() /* 告知内核物理内存布局 */└─ memblock_add_range() /* 合并相邻区域,必要时扩容 */内核解压/初始化 (init/main.c)├─ memblock_alloc_or_panic() /* 分配 saved_command_line 等 */├─ memblock_reserve_kern() /* 保留内核镜像占用区域 */└─ memblock_reserve() /* 保留 initrd/设备树/ACPI 表 */伙伴系统初始化 (mm/mm_init.c)├─ memblock_free_all() /* 释放低内存给伙伴系统 */├─ mem_init() /* 架构特定的 mm 初始化(__weak) */└─ memblock_discard() /* 在 memblock_free_all 内部调用 */

上图说明:
左侧链路:架构代码通过 memblock_add() 注册物理内存,最终调用 memblock_add_range() 合并区域
中间链路:init/main.c 初始化期间,memblock_alloc() → memblock_alloc_internal() → memblock_alloc_range_nid() → memblock_find_in_range_node() 根据 bottom_up 标志选择搜索方向
右侧链路:mm_core_init() 调用 memblock_free_all() 释放低内存给伙伴系统,再根据 CONFIG_ARCH_KEEP_MEMBLOCK 决定是否释放 memblock 自身数据
/* mm/memblock.c:752 */int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size){phys_addr_t end = base + size - 1;memblock_dbg("%s: [%pa-%pa] %pS\n", __func__,&base, &end, (void *)_RET_IP);return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);}
memblock_add() 是架构代码告知内核物理内存布局的入口函数。它调用核心函数 memblock_add_range(),实际实现比表面看起来更复杂:
/* mm/memblock.c:611 */static int __init_memblock memblock_add_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size,int nid, enum memblock_flags flags){bool insert = false;phys_addr_t obase = base;phys_addr_t end = base + memblock_cap_size(base, &size);int idx, nr_new, start_rgn = -1, end_rgn;struct memblock_region *rgn;if (!size)return 0;/* 特殊处理:空数组(第一个 region) */if (type->regions[0].size == 0) {WARN_ON(type->cnt != 0 || type->total_size);type->regions[0].base = base;type->regions[0].size = size;type->regions[0].flags = flags;memblock_set_region_node(&type->regions[0], nid);type->total_size = size;type->cnt = 1;return 0;}/* 判断是否需要扩容:最坏情况下需要 type->cnt + 1 个空位 */if (type->cnt * 2 + 1 <= type->max)insert = true;repeat:base = obase;nr_new = 0;/* 第一遍:计算需要插入多少个新 region */for_each_memblock_type(idx, type, rgn) {phys_addr_t rbase = rgn->base;phys_addr_t rend = rbase + rgn->size;if (rbase >= end)break;if (rend <= base)continue;/* 新区域在现有 region 之前有部分未覆盖 */if (rbase > base) {nr_new++;if (insert)memblock_insert_region(type, idx++, base,rbase - base, nid, flags);}base = min(rend, end);}/* 插入剩余部分 */if (base < end) {nr_new++;if (insert)memblock_insert_region(type, idx, base, end - base,nid, flags);}if (!nr_new)return 0;/* 如果需要扩容且是第一遍,则扩容后重复 */if (!insert) {while (type->cnt + nr_new > type->max)if (memblock_double_array(type, obase, size) < 0)return -ENOMEM;insert = true;goto repeat;} else {/* 合并相邻区域 */memblock_merge_regions(type, start_rgn, end_rgn);return 0;}}
核心逻辑:
空数组特殊处理:如果数组为空(第一个 region),直接填入,不需要合并逻辑
两遍算法:第一遍计算需要插入多少个新 region,第二遍实际插入
重叠区域分割:新区域与现有区域重叠时,分割成多个部分分别插入
自动扩容:如果空间不足,调用 memblock_double_array() 翻倍数组容量
合并相邻区域:插入后调用 memblock_merge_regions() 合并相邻且属性相同的 region,保持列表紧凑
/* mm/memblock.c:1006 */int __init_memblock __memblock_reserve(phys_addr_t base, phys_addr_t size,int nid, enum memblock_flags flags){return memblock_add_range(&memblock.reserved, base, size, nid, flags);}
memblock_reserve() 将一段内存标记为已保留。它的实现完全复用 memblock_add_range(),只是操作的是 memblock.reserved 列表而非 memblock.memory。
保留的内存不会被后续分配使用。典型场景:
内核镜像自身占用的区域
initrd/initramfs 区域
设备树(DT)blob
ACPI 表
固件保留区域
所有通过 memblock 分配函数分配的内存都会自动带上 MEMBLOCK_RSRV_KERN 标志(内核保留)。
memblock 提供两类分配接口:物理地址返回 和 虚拟地址返回。
/* include/linux/memblock.h:407-411 */static __always_inline phys_addr_t memblock_phys_alloc(phys_addr_t size,phys_addr_t align){return memblock_phys_alloc_range(size, align, 0,MEMBLOCK_ALLOC_ACCESSIBLE);}
memblock_phys_alloc() 是一个 static __always_inline 函数,直接委托给 memblock_phys_alloc_range():
/* mm/memblock.c:1652 */phys_addr_t __init memblock_phys_alloc_range(phys_addr_t size,phys_addr_t align,phys_addr_t start,phys_addr_t end){memblock_dbg("%s: %llu bytes align=0x%llx from=%pa max_addr=%pa %pS\n",__func__, (u64)size, (u64)align, &start, &end,(void *)_RET_IP);return memblock_alloc_range_nid(size, align, start, end, NUMA_NO_NODE,false);}
这是所有 memblock 分配 API 的最终公共路径:
/* mm/memblock.c:1566 */phys_addr_t __init memblock_alloc_range_nid(phys_addr_t size,phys_addr_t align, phys_addr_t start,phys_addr_t end, int nid,bool exact_nid){enum memblock_flags flags = choose_memblock_flags();/* 安全保护:slab 就绪后不应再调用 memblock */if (WARN_ON_ONCE(slab_is_available())) {void *vaddr = kzalloc_node(size, GFP_NOWAIT, nid);return vaddr ? virt_to_phys(vaddr) : 0;}if (!align) {dump_stack();align = SMP_CACHE_BYTES;}again:found = memblock_find_in_range_node(size, align, start, end, nid,flags);if (found && !__memblock_reserve(found, size, nid, MEMBLOCK_RSRV_KERN))goto done;/* 如果指定了 NUMA 节点但非精确分配,尝试任意节点 */if (numa_valid_node(nid) && !exact_nid) {found = memblock_find_in_range_node(size, align, start,end, NUMA_NO_NODE, flags);if (found && !memblock_reserve_kern(found, size))goto done;}/* MEMBLOCK_MIRROR 标志失败后重试 */if (flags & MEMBLOCK_MIRROR) {flags &= ~MEMBLOCK_MIRROR;pr_warn_ratelimited("Could not allocate %pap bytes of mirrored memory\n",&size);goto again;}return 0;done:kmemleak_alloc_phys(found, size, 0);accept_memory(found, size);return found;}
关键设计点:
分配后自动保留:找到空闲区域后立即调用 __memblock_reserve() 标记为 reserved,防止重复分配
NUMA fallback:如果指定节点分配失败且非精确分配(exact_nid=false),自动降级到任意节点
MEMBLOCK_MIRROR 重试:镜像内存分配失败后,去掉 MIRROR 标志重试
安全保护:slab 就绪后调用会触发 WARN 并降级使用 kzalloc
内存接受:在 TDX/SEV-SNP 等机密计算环境中,分配后需要调用 accept_memory() 接受内存
memblock_find_in_range_node() 根据 memblock.bottom_up 标志选择不同的搜索函数:
/* mm/memblock.c:309 */static phys_addr_t __init_memblock memblock_find_in_range_node(phys_addr_t size,phys_addr_t align, phys_addr_t start,phys_addr_t end, int nid,enum memblock_flags flags){/* end = MEMBLOCK_ALLOC_ACCESSIBLE 时使用 current_limit */if (end == MEMBLOCK_ALLOC_ACCESSIBLE ||end == MEMBLOCK_ALLOC_NOLEAKTRACE)end = memblock.current_limit;/* 避免分配第 0 页(NULL 指针保护) */start = max_t(phys_addr_t, start, PAGE_SIZE);end = max(start, end);if (memblock_bottom_up())return __memblock_find_range_bottom_up(start, end, size, align,nid, flags);elsereturn __memblock_find_range_top_down(start, end, size, align,nid, flags);}
两个搜索函数的核心区别在于遍历方向:
bottom_up:使用 for_each_free_mem_range(),从低地址向高地址扫描,返回第一个满足条件的地址
top_down:使用 for_each_free_mem_range_reverse(),从高地址向低地址扫描,返回最后一个满足条件的地址
默认 top-down 策略保护低端内存供 DMA 等硬件使用。

上图说明:
物理内存布局:绿色为空闲区域,红色为已保留区域(内核镜像、DTB 等)
top-down(默认):使用 for_each_free_mem_range_reverse(),从高地址向低地址扫描,优先分配 0x40000-0x9FFFF,保护低端内存
bottom-up:使用 for_each_free_mem_range(),从低地址向高地址扫描,优先分配 0x10000-0x2FFFF
/* include/linux/memblock.h:424-427 */static __always_inline void *memblock_alloc(phys_addr_t size, phys_addr_t align){return memblock_alloc_try_nid(size, align, MEMBLOCK_LOW_LIMIT,MEMBLOCK_ALLOC_ACCESSIBLE, NUMA_NO_NODE);}
这是所有返回虚拟地址的 memblock 分配 API 的公共路径:
/* mm/memblock.c:1703 */static void * __init memblock_alloc_internal(phys_addr_t size, phys_addr_t align,phys_addr_t min_addr, phys_addr_t max_addr,int nid, bool exact_nid){phys_addr_t alloc;if (max_addr > memblock.current_limit)max_addr = memblock.current_limit;alloc = memblock_alloc_range_nid(size, align, min_addr, max_addr, nid,exact_nid);/* 如果分配失败,取消 min_addr 限制重试 */if (!alloc && min_addr)alloc = memblock_alloc_range_nid(size, align, 0, max_addr, nid,exact_nid);if (!alloc)return NULL;return phys_to_virt(alloc);}
关键设计:
取消下限重试:如果带 min_addr 限制分配失败,会取消下限限制再试一次
虚拟地址转换:使用 phys_to_virt() 将物理地址转换为虚拟地址(非 __va(),两者在部分架构上可能有差异)
/* mm/memblock.c:1837 */void *__init __memblock_alloc_or_panic(phys_addr_t size, phys_addr_t align,const char *func){void *addr = memblock_alloc(size, align);if (unlikely(!addr))panic("%s: Failed to allocate %pap bytes\n", func, &size);return addr;}
对于关键分配(如命令行缓冲区),使用 memblock_alloc_or_panic() 确保分配失败时内核直接 panic,而不是继续运行在不一致状态。
当 region 数量超过数组容量时,memblock 会自动扩容:
/* mm/memblock.c:430 */static int __init_memblock memblock_double_array(struct memblock_type *type,phys_addr_t new_area_start,phys_addr_t new_area_size){struct memblock_region *new_array, *old_array;phys_addr_t old_alloc_size, new_alloc_size;phys_addr_t old_size, new_size, addr, new_end;int use_slab = slab_is_available();int *in_slab;/* 检查扩容开关 */if (!memblock_can_resize)panic("memblock: cannot resize %s array\n", type->name);old_size = type->max * sizeof(struct memblock_region);new_size = old_size << 1;old_alloc_size = PAGE_ALIGN(old_size);new_alloc_size = PAGE_ALIGN(new_size);/* 判断当前数组是否在 slab 中分配 */if (type == &memblock.memory)in_slab = &memblock_memory_in_slab;elsein_slab = &memblock_reserved_in_slab;/* 尝试分配新数组 */if (use_slab) {new_array = kmalloc(new_size, GFP_KERNEL);addr = new_array ? __pa(new_array) : 0;} else {/* slab 不可用时,通过 memblock 自身查找空闲空间 */if (type != &memblock.reserved)new_area_start = new_area_size = 0;addr = memblock_find_in_range(new_area_start + new_area_size,memblock.current_limit,new_alloc_size, PAGE_SIZE);if (!addr && new_area_size)addr = memblock_find_in_range(0,min(new_area_start, memblock.current_limit),new_alloc_size, PAGE_SIZE);if (addr) {accept_memory(addr, new_alloc_size);new_array = __va(addr);} else {new_array = NULL;}}if (!addr) {pr_err("memblock: Failed to double %s array from %ld to %ld entries !\n",type->name, type->max, type->max * 2);return -1;}/* 复制数据 */memcpy(new_array, type->regions, old_size);memset(new_array + type->max, 0, old_size);old_array = type->regions;type->regions = new_array;type->max <<= 1;/* 释放旧数组 */if (*in_slab)kfree(old_array);else if (old_array != memblock_memory_init_regions &&old_array != memblock_reserved_init_regions)memblock_free(old_array, old_alloc_size);/* 新数组标记为 reserved */if (!use_slab)BUG_ON(memblock_reserve_kern(addr, new_alloc_size));*in_slab = use_slab;return 0;}
扩容策略:
容量翻倍:new_size = old_size << 1,对齐到 PAGE_SIZE
分配器选择:
slab 可用:使用 kmalloc(),设置 in_slab = true
slab 不可用:使用 memblock_find_in_range() 在 memblock 自身查找空间
旧数组释放:
slab 分配的:kfree()
memblock 分配的(且不是静态初始数组):memblock_free()
静态初始数组:不释放
扩容开关:必须调用 memblock_allow_resize() 设置 memblock_can_resize = true 后才允许扩容
/* mm/memblock.c:2173 */void __init memblock_allow_resize(void){memblock_can_resize = true;}void __init memblock_set_bottom_up(bool enable){memblock.bottom_up = enable;}
默认:top-down(自顶向下),保护低端内存
可选:bottom-up(自底向上),通过 memblock_set_bottom_up(true) 切换
扩容开关:必须调用 memblock_allow_resize() 后才允许动态扩容
memblock 不是线程安全的。它设计为单线程环境使用,仅在内核启动早期(SMP 尚未启动)被调用。因此:
无锁保护
无原子操作
依赖启动阶段的单线程执行模型
一旦 SMP 启动后,不应再调用 memblock API。代码中通过 WARN_ON_ONCE(slab_is_available()) 作为安全保护,如果 slab 就绪后调用分配函数会触发 WARN 并降级使用 kzalloc。
memblock 的生命周期分为三个阶段,状态转换如下:

上图说明:
静态初始化:编译时静态初始化,无需运行时分配
阶段 1(添加区域):架构代码注册物理内存布局
阶段 2(分配与保留):内核解压/初始化期间分配数据结构和保留关键区域,可能触发动态扩容
阶段 3(移交):通过 free_low_memory_core_early() 将空闲页释放给伙伴系统
CONFIG_ARCH_KEEP_MEMBLOCK 决策点:=n 则释放 memblock 自身内存(默认),=y 则保留供热插拔使用
memblock 的生命周期具体分为三个阶段:
阶段 1:初始化(架构早期)
全局 memblock 结构体编译时静态初始化
架构代码调用 memblock_add() / memblock_add_node() 注册物理内存布局
初始区域数组大小:INIT_MEMBLOCK_REGIONS = 128
阶段 2:分配与保留(内核解压/初始化)
init/main.c 中分配命令行缓冲区、未知选项缓冲区等
init/initramfs.c 中保留 initramfs 区域
各子系统保留自身使用的内存区域
阶段 3:移交(伙伴系统就绪)
移交过程发生在 mm/mm_init.c:mm_core_init() 中:
/* mm/mm_init.c:2717 */void __init mm_core_init(void){arch_mm_preinit();init_zero_page_pfn();/* ... *//* 将可用内存释放给伙伴系统 */memblock_free_all();/* 架构特定的 mm 初始化(默认为空函数) */mem_init();/* slab 分配器初始化(此后 memblock 不应再被调用) */kmem_cache_init();/* ... */}
核心移交函数 memblock_free_all():
/* mm/memblock.c:2431 */void __init memblock_free_all(void){unsigned long pages;free_unused_memmap();reset_all_zones_managed_pages();memblock_clear_kho_scratch_only();pages = free_low_memory_core_early();totalram_pages_add(pages);}
移交流程:
free_unused_memmap():释放稀疏内存模型中未使用的 memmap 页
reset_all_zones_managed_pages():重置所有内存区域的 managed_pages 计数
free_low_memory_core_early():遍历 for_each_free_mem_range(),将 memblock 中所有空闲区域释放给伙伴系统(调用 __free_pages_core())
totalram_pages_add(pages):更新总内存页数统计
memblock_discard():如果未启用 CONFIG_ARCH_KEEP_MEMBLOCK,释放 memblock 自身的 region 数组
memblock_discard() 的实现:
/* mm/memblock.c:384 */void __init memblock_discard(void){phys_addr_t size;void *addr;if (memblock.reserved.regions != memblock_reserved_init_regions) {addr = memblock.reserved.regions;size = PAGE_ALIGN(sizeof(struct memblock_region) *memblock.reserved.max);if (memblock_reserved_in_slab)kfree(addr);elsememblock_free(addr, size);}if (memblock.memory.regions != memblock_memory_init_regions) {addr = memblock.memory.regions;size = PAGE_ALIGN(sizeof(struct memblock_region) *memblock.memory.max);if (memblock_memory_in_slab)kfree(addr);elsememblock_free(addr, size);}memblock_memory = NULL;}#endif /* CONFIG_ARCH_KEEP_MEMBLOCK 的 #else 分支 */
如果启用了 CONFIG_ARCH_KEEP_MEMBLOCK,memblock_discard() 为空函数(被 #ifdef 排除),memblock 数据在移交后仍然可用。
CONFIG_DEBUG_FS=yCONFIG_FTRACE=y(ftrace 支持) | |
内核启动时可以通过 dmesg 查看 memblock 的内存布局信息:
# 在已启动的系统上执行dmesg | grep -i memblock
典型输出示例(x86_64 虚拟机,2GB 内存):
[ 0.000000] memblock: 128 memory regions[ 0.000000] memblock: 0: [0x0000001000-0x000009ffff] 639KB[ 0.000000] memblock: 1: [0x0000100000-0x0000ffffff] 15MB[ 0.000000] memblock: 2: [0x0001000000-0x7fffffff] 2047MB[ 0.000000] memblock: reserved regions:[ 0.000000] memblock: 0: [0x0000001000-0x0000001fff] 4KB (kernel)[ 0.000000] memblock: 1: [0x0001000000-0x00015fffff] 6MB (kernel)
注意:实际输出取决于内核日志级别和架构实现。部分架构可能不会在 dmesg 中打印 memblock 详细信息。
# 在内核根目录下执行(需要 root 权限)cat /sys/kernel/debug/memblock/memorycat /sys/kernel/debug/memblock/reserved
输出格式:每行一个 region,显示基地址和大小。
预期输出:
memory 文件:列出所有可用物理内存区域
reserved 文件:列出所有已保留区域(内核镜像、initrd 等)
# 在 sysfs 挂载点下执行(需要 root 权限)echo function > /sys/kernel/debug/tracing/current_tracerecho 'memblock_*' > /sys/kernel/debug/tracing/set_ftrace_filterecho 1 > /sys/kernel/debug/tracing/tracing_on# 运行一段时间查看结果cat /sys/kernel/debug/tracing/trace | head -50
预期输出:显示所有 memblock_* 函数的调用记录,包括调用者、参数和返回值。
在内核代码(非模块,因为 memblock 仅在 early boot 可用)或自定义启动代码中,可以添加以下验证代码:
/* 在 arch/*/mm/init.c 或类似位置添加 */phys_addr_t total = memblock_phys_mem_size();phys_addr_t reserved = memblock_reserved_size();pr_info("memblock: total=%pa, reserved=%pa, free=%pa\n",&total, &reserved, &(total - reserved));
注意:memblock API 仅在
memblock_free_all()调用之前可用。在常规内核模块中调用会触发WARN_ON_ONCE(slab_is_available())并返回错误值。
memblock 替代了旧的 bootmem 分配器,提供更高效、更灵活的早期物理内存管理:
基于区域而非位图:支持不连续内存,避免位图带来的内存浪费
支持区域属性标记:MEMBLOCK_NOMAP、MEMBLOCK_HOTPLUG 等标志实现精细化管理
自动合并相邻区域:减少碎片,保持 region 列表紧凑
动态扩容:支持复杂系统的大量内存区域
NUMA 感知:支持指定 NUMA 节点分配,提供 exact_nid 控制
memblock 是编译时静态初始化的全局分配器,memory 和 reserved 各有 128 个初始槽位,无需依赖 kmalloc/slab
核心数据结构是三级层次:memblock → memblock_type → memblock_region
分配策略默认自顶向下(top-down),保护低端内存;分配后自动标记为 reserved
核心分配公共路径是 memblock_alloc_range_nid(),支持 NUMA fallback 和 MEMBLOCK_MIRROR 重试
在 mm_core_init() 中通过 memblock_free_all() → free_low_memory_core_early() 移交内存管理权给伙伴系统,自身释放或保留
memblock 仅在启动早期单线程环境使用,SMP 启动后不应调用
分配失败时关键路径使用 memblock_alloc_or_panic() 直接 panic
启用 CONFIG_ARCH_KEEP_MEMBLOCK 可在移交后保留 memblock 数据(用于内存热插拔等场景)
region 数组初始容量 128,复杂系统可能需要调用 memblock_allow_resize() 启用动态扩容
memblock_phys_alloc() 返回物理地址,memblock_alloc() 返回虚拟地址,按需选择
所有配置项(CONFIG_HAVE_MEMBLOCK_PHYS_MAP、CONFIG_ARCH_KEEP_MEMBLOCK)均为 bool 不可见配置,由架构或上层配置自动决定
memblock 在 NUMA 系统上的节点分配策略(memblock_alloc_try_nid、exact_nid 参数)
不同架构(x86、ARM64、RISC-V)的 memblock 初始化差异
memblock 与设备树内存节点的集成方式
CONFIG_ARCH_KEEP_MEMBLOCK 开启后的内存热插拔支持
memblock 与 kexec handover(KHO)的交互
memblock 中 confidential computing(TDX/SEV-SNP)的 accept_memory 流程
Linux Kernel Documentation: Memory Management
Linux kernel source tree (v7.0): mm/memblock.c (2908 行), include/linux/memblock.h (623 行), mm/mm_init.c, mm/Kconfig
Mike Rapoport: memblock maintainer, git://git.kernel.org/pub/scm/linux/kernel/git/rppt/memblock.git
init/main.c, init/initramfs.c 中的 memblock 使用示例
本文主要基于 Linux 7.0 源码分析整理。