文章目录
- mm/memblock.c: Linux内核的“拓荒时代”内存管理器
- 一、 核心问题:为什么需要 `memblock`?
- 二、 核心原理与设计
- 三、 在内核启动流程中的角色
- 四、 关键 API
- 五、 总结
- include/linux/memblock.h
- mm/memblock.c
- memblock_reserve 预留内存块
- for_each_memblock_type 遍历 memblock 区域
- memblock_insert_region 插入新的 memblock 区域
- memblock_add_range 添加新的 memblock 区域
- memblock_is_region_memory 判断区域是否是内存区域
- memblock_is_region_reserved 判断区域是否是保留区域
- __memblock_find_range_bottom_up __memblock_find_range_top_down 查找空闲区域
- memblock_alloc_range_nid 分配启动内存块
- memblock_phys_alloc_range 物理地址分配
- memblock_flags 内存区域属性的定义
- memblock_setclr_flag 设置或清除内存区域的标志
- memblock_mark_nomap 使用标志 MEMBLOCK_NOMAP 标记内存区域
- memblock_dump_all 打印所有内存块信息
- memblock_alloc_try_nid 尝试分配内存块
- __memblock_alloc_or_panic 分配内存并在失败时出现 panic
- memblock_allow_resize Memblock 允许调整大小
- memblock_insert_region 插入新的内存区域
- memblock_isolate_range 隔离内存区域
- memblock_remove_region 删除内存区域
- memblock_phys_free 释放物理内存块
- free_memmap 释放内存映射
- free_unused_memmap 释放未使用的内存映射
- reset_all_zones_managed_pages 重置所有区域的管理页面
- memmap_init_reserved_pages 初始化保留页面
- __free_memory_core 释放页面内存
- free_low_memory_core_early 释放低内存区域
- memblock_free_all 释放所有内存块
- memblock_estimated_nr_free_pages 从 memblock 角度返回估计的免费页面数

https://github.com/wdfk-prog/linux-study
文章目录
- mm/memblock.c: Linux内核的“拓荒时代”内存管理器
- 一、 核心问题:为什么需要 `memblock`?
- 二、 核心原理与设计
- 三、 在内核启动流程中的角色
- 四、 关键 API
- 五、 总结
- include/linux/memblock.h
- mm/memblock.c
- memblock_reserve 预留内存块
- for_each_memblock_type 遍历 memblock 区域
- memblock_insert_region 插入新的 memblock 区域
- memblock_add_range 添加新的 memblock 区域
- memblock_is_region_memory 判断区域是否是内存区域
- memblock_is_region_reserved 判断区域是否是保留区域
- __memblock_find_range_bottom_up __memblock_find_range_top_down 查找空闲区域
- memblock_alloc_range_nid 分配启动内存块
- memblock_phys_alloc_range 物理地址分配
- memblock_flags 内存区域属性的定义
- memblock_setclr_flag 设置或清除内存区域的标志
- memblock_mark_nomap 使用标志 MEMBLOCK_NOMAP 标记内存区域
- memblock_dump_all 打印所有内存块信息
- memblock_alloc_try_nid 尝试分配内存块
- __memblock_alloc_or_panic 分配内存并在失败时出现 panic
- memblock_allow_resize Memblock 允许调整大小
- memblock_insert_region 插入新的内存区域
- memblock_isolate_range 隔离内存区域
- memblock_remove_region 删除内存区域
- memblock_phys_free 释放物理内存块
- free_memmap 释放内存映射
- free_unused_memmap 释放未使用的内存映射
- reset_all_zones_managed_pages 重置所有区域的管理页面
- memmap_init_reserved_pages 初始化保留页面
- __free_memory_core 释放页面内存
- free_low_memory_core_early 释放低内存区域
- memblock_free_all 释放所有内存块
- memblock_estimated_nr_free_pages 从 memblock 角度返回估计的免费页面数
mm/memblock.c: Linux内核的“拓荒时代”内存管理器
mm/memblock.c
实现了一种极其早期的、简单的物理内存分配器,它在内核启动的“拓荒时代”——即在页分配器(伙伴系统)初始化之前——扮演着至关重要的角色。
可以将其想象成一个在建造正式仓库(伙伴系统)之前,用来管理建筑材料(物理内存)的临时账本和场地规划师。它的唯一使命是在最原始的环境下,为内核自身的初始化提供最基本的内存分配服务,并在完成使命后,将所有管理权平稳地移交给更高级的内存管理系统。
一、 核心问题:为什么需要 memblock
?
在内核启动的极早期(start_kernel
函数刚开始执行时),真正的内存管理子系统(如伙伴系统、Slab 分配器)还完全不存在。这些高级系统本身就需要分配内存来存放它们复杂的数据结构(如 mem_map
数组、kmem_cache
结构等)。这就产生了一个“先有鸡还是先有蛋”的问题:
- 为了初始化内存管理器,你需要分配内存。
- 但为了分配内存,你需要一个已初始化的内存管理器。
memblock
就是为了打破这个循环而存在的。它是一个极其简单的、无须复杂数据结构的分配器,可以在最简陋的环境下工作。
二、 核心原理与设计
memblock
的设计思想是极简主义。它不使用链表、树等复杂结构,而是只用了几个静态数组来管理整个系统的物理内存布局。
memblock
将系统的物理内存分为两大类:
-
内存区域 (Memory Regions):
memblock.memory
- 这是一个
struct memblock_region
类型的数组,记录了系统中所有可用的物理内存块。 - 这些信息通常由 Bootloader(通过设备树、E820 表等)传递给内核。例如,
[0x100000 - 0x80000000]
(1MB 到 2GB)。
- 这是一个
-
保留区域 (Reserved Regions):
memblock.reserved
- 这也是一个
struct memblock_region
类型的数组,记录了那些不可用的物理内存块。 - 这些区域包括:内核自身的代码和数据段(
.text
,.data
,.bss
)、ACPI 数据、设备树二进制文件等。
- 这也是一个
struct memblock_region
的定义:
struct memblock_region {phys_addr_t base; // 区域的物理基地址phys_addr_t size; // 区域的大小// ... 其他标志
};
内存分配的工作方式:
当内核需要调用 memblock_alloc()
来分配一块内存时,memblock
的工作方式就像一个反向的橡皮擦:
- 扫描可用内存: 它会遍历
memblock.memory
数组,寻找一个足够大的、尚未被保留的可用内存区域。 - “保留”新区域: 一旦找到合适的空间,它不会从
memblock.memory
中“切”一块下来,而是简单地调用memblock_reserve()
,将刚刚分配出去的这块内存区域的信息,添加到memblock.reserved
数组中。 - 返回物理地址: 最后,它返回这块内存的物理基地址。
内存释放: memblock
也提供了 memblock_free()
,其工作方式与分配相反,即从 memblock.reserved
数组中移除一个区域。
核心优势:
- 实现简单: 只需要几个数组和一些循环遍历逻辑。
- 无动态分配:
memblock
自身的数据结构(memblock_region
数组)是在编译时静态分配的(或在.bss
段),它在工作时不需要为自己动态分配任何内存。 - 功能足够: 它提供了启动阶段所需的所有基本功能:添加内存区域、保留内存区域、分配内存、查找可用内存等。
三、 在内核启动流程中的角色
memblock
的生命周期非常短暂,但作用贯穿了整个启动过程的前半段。
-
信息收集阶段 (Arch-specific code):
- 在
start_kernel
之前,特定于体系结构的代码(如arch/arm64/kernel/setup.c
)会解析 Bootloader 传递的内存布局信息。 - 它会调用
memblock_add(base, size)
将所有发现的可用物理 RAM 段添加到memblock.memory
中。
- 在
-
早期保留阶段:
- 内核会立即调用
memblock_reserve()
来“保护”那些已经被占用的关键区域,例如:- 内核镜像本身占用的空间。
- 传递进来的设备树二进制文件 (initrd/dtb) 占用的空间。
- 内核会立即调用
-
早期分配阶段 (
start_kernel
内部):- 在
start_kernel
函数中,许多早期的子系统初始化都需要内存。它们会调用memblock_alloc()
来获取。 - 最重要的分配: 为伙伴系统的
mem_map
数组分配空间是memblock
最关键的任务之一。mem_map
可能非常巨大(几百MB),只有memblock
才能在伙伴系统上线前完成这个艰巨的任务。
- 在
-
“交接仪式” (
mm_init()
内部):- 当内核执行到
mm_init()
->mem_init()
时,标志着memblock
的历史使命即将结束。 mem_init()
函数会遍历memblock.memory
中记录的所有可用内存区域。- 对于每个区域,它会逐页地调用
__free_pages_bootmem()
(或类似函数),将这些物理页**“释放”到刚刚初始化完成的伙伴系统中**。 - 这个过程就像是临时账本的管理员,将所有账目和剩余物资,都交接给了新上任的正式仓库管理员。
- 当内核执行到
-
退役: 交接完成后,
memblock
的数据结构本身所占用的内存也会被回收,它从此在内核的运行中销声匿迹。
四、 关键 API
memblock_add(phys_addr_t base, phys_addr_t size)
: 添加一段物理内存到memblock.memory
。memblock_reserve(phys_addr_t base, phys_addr_t size)
: 将一段内存标记为已保留,添加到memblock.reserved
。memblock_alloc(phys_addr_t size, phys_addr_t align)
: 从可用内存中分配一块指定大小和对齐的内存,并将其标记为保留。memblock_free(phys_addr_t base, phys_addr_t size)
: 释放一块之前保留的内存。memblock_find_in_range(phys_addr_t start, phys_addr_t end, ...)
: 在指定范围内查找一块符合条件的空闲内存,常用于为mem_map
寻找空间。
五、 总结
memblock
是 Linux 内核启动过程的奠基石。它是一个专门为解决“鸡生蛋,蛋生鸡”问题而设计的引导期物理内存管理器。
核心特点:
- 简单高效: 基于静态数组,操作逻辑直观。
- 生命周期短暂: 仅在伙伴系统初始化之前活跃。
- 任务关键: 负责为内核早期初始化(特别是为伙伴系统自身)提供内存。
- 承上启下: 它的角色是收集硬件信息,服务早期分配,并最终将整个物理内存的管理权无缝地移交给伙伴系统。
理解 memblock
的工作原理,是理解 Linux 内核如何从最原始的硬件状态,一步步建立起复杂而强大的内存管理大厦的第一步。
/*** DOC: memblock 概述** Memblock 是一种在早期启动阶段管理内存区域的方法,此时通常的内核内存分配器尚未启动。** Memblock 将系统内存视为连续区域的集合。这些集合有几种类型:** * ``memory`` - 描述可供内核使用的物理内存;这可能与系统中实际安装的物理内存不同,例如当使用 ``mem=`` 命令行参数限制内存时。* * ``reserved`` - 描述已分配的区域。* * ``physmem`` - 描述启动期间可用的实际物理内存,无论可能的限制和内存热插拔;``physmem`` 类型仅在某些架构上可用。** 每个区域由 struct memblock_region 表示,该结构定义了区域范围、其属性以及 NUMA 系统上的 NUMA 节点 ID。每种内存类型由 struct memblock_type 描述,* 其中包含内存区域的数组以及分配器的元数据。"memory" 和 "reserved" 类型通过 struct memblock 进行了良好的封装。* 该结构在构建时静态初始化。"memory" 和 "reserved" 类型的区域数组初始大小分别为 %INIT_MEMBLOCK_MEMORY_REGIONS 和 %INIT_MEMBLOCK_RESERVED_REGIONS。* "physmem" 的区域数组初始大小为 %INIT_PHYSMEM_REGIONS。* memblock_allow_resize() 函数允许在添加新区域时自动调整区域数组的大小。此功能应谨慎使用,以确保为区域数组分配的内存不会与应保留的区域(例如 initrd)重叠。** 早期的架构设置应通过使用 memblock_add() 或 memblock_add_node() 函数告知 memblock 物理内存布局。第一个函数不会将区域分配给 NUMA 节点,* 它适用于 UMA 系统。然而,也可以在 NUMA 系统上使用它,并在设置过程中稍后使用 memblock_set_node() 将区域分配给 NUMA 节点。* memblock_add_node() 函数直接执行此类分配。** 一旦设置了 memblock,就可以使用以下 API 变体之一分配内存:** * memblock_phys_alloc*() - 这些函数返回分配内存的 **物理** 地址。* * memblock_alloc*() - 这些函数返回分配内存的 **虚拟** 地址。** 注意,这两种 API 变体都使用了关于允许内存范围和回退方法的隐式假设。有关更详细的描述,请参阅 memblock_alloc_internal() 和 memblock_alloc_range_nid() 函数的文档。** 随着系统启动的进行,架构特定的 mem_init() 函数会将所有内存释放到伙伴页分配器。** 除非架构启用了 %CONFIG_ARCH_KEEP_MEMBLOCK,否则系统初始化完成后,memblock 数据结构(除了 "physmem")将被丢弃。*/
include/linux/memblock.h
/*** struct memblock - memblock allocator metadata* @bottom_up: is bottom up direction?* @current_limit: physical address of the current allocation limit* @memory: usable memory regions* @reserved: reserved memory regions*/
struct memblock {bool bottom_up; /* is bottom up direction? */phys_addr_t current_limit;struct memblock_type memory;struct memblock_type reserved;
};#define INIT_MEMBLOCK_REGIONS 128
#define INIT_PHYSMEM_REGIONS 4#ifndef INIT_MEMBLOCK_RESERVED_REGIONS
# define INIT_MEMBLOCK_RESERVED_REGIONS INIT_MEMBLOCK_REGIONS
#endif#ifndef INIT_MEMBLOCK_MEMORY_REGIONS
#define INIT_MEMBLOCK_MEMORY_REGIONS INIT_MEMBLOCK_REGIONS
#endifstatic 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;
struct memblock memblock __initdata_memblock = {.memory.regions = memblock_memory_init_regions,.memory.max = INIT_MEMBLOCK_MEMORY_REGIONS,.memory.name = "memory",.reserved.regions = memblock_reserved_init_regions,.reserved.max = INIT_MEMBLOCK_RESERVED_REGIONS,.reserved.name = "reserved",.bottom_up = false,.current_limit = MEMBLOCK_ALLOC_ANYWHERE,
};
mm/memblock.c
- memblock_alloc_from: 需要写入可以从最小的地址开始的内存块.如果没有找到,则从0开始分配
static inline void *memblock_alloc_from(phys_addr_t size,phys_addr_t align,phys_addr_t min_addr)
memblock_reserve 预留内存块
int __init_memblock memblock_reserve(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.reserved, base, size, MAX_NUMNODES, 0);
}
for_each_memblock_type 遍历 memblock 区域
#define for_each_memblock_type(i, memblock_type, rgn) \for (i = 0, rgn = &memblock_type->regions[0]; \i < memblock_type->cnt; \i++, rgn = &memblock_type->regions[i])
memblock_insert_region 插入新的 memblock 区域
/*** memblock_insert_region - insert new memblock region* @type: memblock type to insert into* @idx: index for the insertion point* @base: base address of the new region* @size: size of the new region* @nid: node id of the new region* @flags: flags of the new region** Insert new memblock region [@base, @base + @size) into @type at @idx.* @type must already have extra room to accommodate the new region.*/
static void __init_memblock memblock_insert_region(struct memblock_type *type,int idx, phys_addr_t base,phys_addr_t size,int nid,enum memblock_flags flags)
{struct memblock_region *rgn = &type->regions[idx];BUG_ON(type->cnt >= type->max);//将当前区域赋值给新的区域memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));rgn->base = base;rgn->size = size;rgn->flags = flags;memblock_set_region_node(rgn, nid);type->cnt++;type->total_size += size;
}
memblock_add_range 添加新的 memblock 区域
- 没有添加过区域,直接添加
- 添加过区域,需要判断是否需要扩展区域数组
- 需要扩展区域数组,需要判断是否有足够的空间
- 如果没有足够的空间,扩展区域数组
- 如果有足够的空间,直接添加
- 添加区域后,合并区域
- 如果没有添加区域,直接返回
/*** memblock_add_range - 添加新的 memblock 区域* @type:用于添加新区域的 memblock 类型* @base:新区域的基址* @size:新区域的大小* @nid:新区域的 NID* @flags:新区域的标志** 将新的 memblock 区域 [@base, @base @size) 添加到 @type 中。 * 允许新区域与现有区域重叠 - 重叠不会影响已存在的区域。* @type保证在添加后最小(所有相邻的兼容区域都合并)。** Return:* 0 on success, -errno on failure.*/
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;/* 空数组的特殊情况 */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); //NUMA使用type->total_size = size;type->cnt = 1;return 0;}/** 最坏的情况是,当新范围与所有现有区域重叠时,我们需要 type->cnt 1 in @type 空区域。* 所以如果 type->cnt * 2 1 小于等于 type->max,我们就知道 @type 中有足够的空白区域,我们可以直接插入区域。*/if (type->cnt * 2 + 1 <= type->max)insert = true;repeat:/** 以下将执行两次。 一次使用 lse @insert,然后使用 %true。* 第一个计算容纳新区域所需的区域数。 第二个实际上插入了它们。*/base = obase;nr_new = 0;for_each_memblock_type(idx, type, rgn) {//rgn = &memblock_type->regions[i]phys_addr_t rbase = rgn->base;phys_addr_t rend = rbase + rgn->size;if (rbase >= end)break;if (rend <= base)continue;/** @rgn重叠。 如果它分隔了新区域的下部,请插入该部分。*/if (rbase > base) { //已经添加区域 > 需要添加区域
#ifdef CONFIG_NUMAWARN_ON(nid != memblock_get_region_node(rgn));
#endifWARN_ON(flags != rgn->flags);nr_new++;if (insert) {if (start_rgn == -1)start_rgn = idx;end_rgn = idx + 1;//插入新区域memblock_insert_region(type, idx++, base,rbase - base, nid,flags);}}/*区域低于 @rend 被处理,忘记它 */base = min(rend, end);}/* 插入剩余部分*/if (base < end) {nr_new++;if (insert) {if (start_rgn == -1)start_rgn = idx;end_rgn = idx + 1;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;}
}
memblock_is_region_memory 判断区域是否是内存区域
bool __init_memblock memblock_is_region_memory(phys_addr_t base, phys_addr_t size)
{int idx = memblock_search(&memblock.memory, base); //二分查找/* *size = min(*size, PHYS_ADDR_MAX - base);* 返回需要的大小和可用大小用更小的一个*/phys_addr_t end = base + memblock_cap_size(base, &size);if (idx == -1)return false;return (memblock.memory.regions[idx].base +memblock.memory.regions[idx].size) >= end;
}
memblock_is_region_reserved 判断区域是否是保留区域
/** 地址比较实用程序*/
unsigned long __init_memblock
memblock_addrs_overlap(phys_addr_t base1, phys_addr_t size1, phys_addr_t base2,phys_addr_t size2)
{return ((base1 < (base2 + size2)) && (base2 < (base1 + size1)));
}
bool __init_memblock memblock_overlaps_region(struct memblock_type *type,phys_addr_t base, phys_addr_t size)
{unsigned long i;memblock_cap_size(base, &size);for (i = 0; i < type->cnt; i++)if (memblock_addrs_overlap(base, size, type->regions[i].base,type->regions[i].size))return true;return false;
}bool __init_memblock memblock_is_region_reserved(phys_addr_t base, phys_addr_t size)
{return memblock_overlaps_region(&memblock.reserved, base, size);
}
__memblock_find_range_bottom_up __memblock_find_range_top_down 查找空闲区域
/*** __memblock_find_range_bottom_up - 在自下而上中找到 Free Area 实用程序* @start:候选范围的开始* @end:候选范围的结尾,可以是 %MEMBLOCK_ALLOC_ANYWHERE 或* %MEMBLOCK_ALLOC_ACCESSIBLE* @size:可寻的自由区域大小* @align:对齐 free area to find* @nid:要查找的空闲区域的 NID,任何节点的 %NUMA_NO_NODE* @flags:根据内存属性从块中选择** 从 memblock_find_in_range_node() 调用的实用程序,自下而上查找空闲区域。**返回:* 成功时找到地址,失败时找到 0。*/
static phys_addr_t __init_memblock
__memblock_find_range_bottom_up(phys_addr_t start, phys_addr_t end,phys_addr_t size, phys_addr_t align, int nid,enum memblock_flags flags)
{phys_addr_t this_start, this_end, cand;u64 i;for_each_free_mem_range(i, nid, flags, &this_start, &this_end, NULL) {this_start = clamp(this_start, start, end);this_end = clamp(this_end, start, end);cand = round_up(this_start, align);if (cand < this_end && this_end - cand >= size)return cand;}return 0;
}/*** __memblock_find_range_top_down - 自上而下查找自由区域实用程序* @start:候选范围的开始* @end:候选范围的结尾,可以是 %MEMBLOCK_ALLOC_ANYWHERE 或* %MEMBLOCK_ALLOC_ACCESSIBLE* @size:可寻的自由区域大小* @align:对齐 free area to find* @nid:要查找的空闲区域的 NID,任何节点的 %NUMA_NO_NODE* @flags:根据内存属性从块中选择** 从 memblock_find_in_range_node() 调用的实用程序,自上而下查找空闲区域。**返回:* 成功时找到地址,失败时找到 0。*/
static phys_addr_t __init_memblock
__memblock_find_range_top_down(phys_addr_t start, phys_addr_t end,phys_addr_t size, phys_addr_t align, int nid,enum memblock_flags flags)
{phys_addr_t this_start, this_end, cand;u64 i;for_each_free_mem_range_reverse(i, nid, flags, &this_start, &this_end,NULL) {this_start = clamp(this_start, start, end);this_end = clamp(this_end, start, end);if (this_end < size)continue;cand = round_down(this_end - size, align);if (cand >= this_start)return cand;}return 0;
}
memblock_alloc_range_nid 分配启动内存块
/*** memblock_alloc_range_nid - 分配启动内存块* @size:要分配的内存块大小(以字节为单位)* @align:区域和块大小的对齐方式* @start:要分配的内存区域的下限 (phys address)* @end:要分配的内存区域的上限 (phys address)* @nid:要查找的空闲区域的 nid,任何节点的 %NUMA_NO_NODE* @exact_nid:控制分配回退到其他节点** 如果 @end == %MEMBLOCK_ALLOC_ACCESSIBLE,则从受 memblock.current_limit 限制的内存区域执行分配。** 如果指定的节点无法容纳请求的内存和 @exact_nid为 false,则分配将回退到系统中的任何节点。** 对于具有内存镜像的系统,首先从启用了镜像的区域尝试分配,然后从任何内存区域重试。** 此外,使用 kmemleak_alloc_phys 分配的启动内存块的函数,永远不会报告泄漏。** 返回:成功时已分配内存块的物理地址,失败时为 %0。*/
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();phys_addr_t found;/** Detect any accidental use of these APIs after slab is ready, as at* this moment memblock may be deinitialized already and its* internal data may be destroyed (after execution of memblock_free_all)*/if (WARN_ON_ONCE(slab_is_available())) {void *vaddr = kzalloc_node(size, GFP_NOWAIT, nid);return vaddr ? virt_to_phys(vaddr) : 0;}if (!align) {/* Can't use WARNs this early in boot on powerpc */dump_stack();align = SMP_CACHE_BYTES;}again://Memblock 在 range 节点中查找found = memblock_find_in_range_node(size, align, start, end, nid,flags);if (found && !memblock_reserve(found, size))goto done;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(found, size))goto done;}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:/** 由于高容量,请跳过 kasan_init() 和 early_pgtable_alloc() 等地方的 kmemleak。*/if (end != MEMBLOCK_ALLOC_NOLEAKTRACE)/** Memblock 分配的块永远不会报告为泄漏。这是因为这些块中的许多只是通过物理地址引用的,而 kmemleak 不会查找该地址。*/kmemleak_alloc_phys(found, size, 0);/** Some Virtual Machine platforms, such as Intel TDX or AMD SEV-SNP,* require memory to be accepted before it can be used by the* guest.** Accept the memory of the allocated buffer.*/accept_memory(found, size);return found;
}
memblock_phys_alloc_range 物理地址分配
/*** memblock_phys_alloc_range - 在指定范围内分配内存块* @size:要分配的内存块大小(以字节为单位)* @align:区域和块大小的对齐方式* @start:要分配的内存区域的下限(物理地址)* @end:要分配的内存区域的上限(物理地址)** 在 @start 和 @end 之间分配 @size 字节。** 返回:成功时分配的内存块的物理地址,失败时返回 %0。*/
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_flags 内存区域属性的定义
/*** enum memblock_flags - 内存区域属性的定义* @MEMBLOCK_NONE:无特殊要求* @MEMBLOCK_HOTPLUG:在早期启动期间,固件提供的内存映射中将内存区域指示为热(不可)插拔系统 RAM(例如,以后可能会热拔出的内存范围)。在内核命令行上设置 “movable_node” 后,尝试保持此内存区域可热拔。不适用于在早期启动后添加(“热插拔”)的内存块。* @MEMBLOCK_MIRROR:镜像区域* @MEMBLOCK_NOMAP:不添加到内核直接映射中,并在内存映射中视为保留;有关更多详细信息,请参阅 memblock_mark_nomap() 描述* @MEMBLOCK_DRIVER_MANAGED:始终通过驱动程序检测和添加的内存区域,并且从未在固件提供的内存映射中指示为系统 RAM。这对应于内核资源树中的 IORESOURCE_SYSRAM_DRIVER_MANAGED。
* @MEMBLOCK_RSRV_NOINIT:未初始化结构页的内存区域(仅适用于保留区域)。*/
enum memblock_flags {MEMBLOCK_NONE = 0x0, /* 无特殊要求 */MEMBLOCK_HOTPLUG = 0x1, /* 可热插拔区域 */MEMBLOCK_MIRROR = 0x2, /* 镜像区域 */MEMBLOCK_NOMAP = 0x4, /* 不添加到内核直接映射 */MEMBLOCK_DRIVER_MANAGED = 0x8, /* 始终通过驱动程序检测到 */MEMBLOCK_RSRV_NOINIT = 0x10, /* 不初始化结构页 */
};
memblock_setclr_flag 设置或清除内存区域的标志
/*** memblock_setclr_flag - 设置或清除内存区域的标志* @type:要设置/清除标志的 memblock 类型* @base:区域的基址* @size:区域大小* @set:设置或清除标志* @flag:要更新的标志** 此功能隔离区域 [@base, @base @size),并设置/清除标志** 返回:成功时为 0,失败时为 -errno。*/
static int __init_memblock memblock_setclr_flag(struct memblock_type *type,phys_addr_t base, phys_addr_t size, int set, int flag)
{int i, ret, start_rgn, end_rgn;ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);if (ret)return ret;for (i = start_rgn; i < end_rgn; i++) {struct memblock_region *r = &type->regions[i];if (set)r->flags |= flag;elser->flags &= ~flag;}memblock_merge_regions(type, start_rgn, end_rgn);return 0;
}
memblock_mark_nomap 使用标志 MEMBLOCK_NOMAP 标记内存区域
/*** memblock_mark_nomap - 使用标志 MEMBLOCK_NOMAP 标记内存区域。* @base:区域的基本 phys addr* @size:区域的大小** 标有 %MEMBLOCK_NOMAP 的内存区域不会被添加到物理内存的直接映射中。这些区域仍将被内存映射覆盖。表示内存映射中 NOMAP 内存帧的结构页将是 PageReserved()** 注意:如果标记为 %MEMBLOCK_NOMAP 的内存是从 memblock 分配的,调用者必须通知 kmemleak 忽略该内存** 返回:成功时为 0,失败时为 -errno。*/
int __init_memblock memblock_mark_nomap(phys_addr_t base, phys_addr_t size)
{return memblock_setclr_flag(&memblock.memory, base, size, 1, MEMBLOCK_NOMAP);
}
memblock_dump_all 打印所有内存块信息
static int memblock_debug __initdata_memblock = 1;static void __init_memblock memblock_dump(struct memblock_type *type)
{phys_addr_t base, end, size;enum memblock_flags flags;int idx;struct memblock_region *rgn;pr_info(" %s.cnt = 0x%lx\n", type->name, type->cnt);for_each_memblock_type(idx, type, rgn) {char nid_buf[32] = "";base = rgn->base;size = rgn->size;end = base + size - 1;flags = rgn->flags;
#ifdef CONFIG_NUMAif (numa_valid_node(memblock_get_region_node(rgn)))snprintf(nid_buf, sizeof(nid_buf), " on node %d",memblock_get_region_node(rgn));
#endifpr_info(" %s[%#x]\t[%pa-%pa], %pa bytes%s flags: %#x\n",type->name, idx, &base, &end, &size, nid_buf, flags);}
}static void __init_memblock __memblock_dump_all(void)
{pr_info("MEMBLOCK configuration:\n");pr_info(" memory size = %pa reserved size = %pa\n",&memblock.memory.total_size,&memblock.reserved.total_size);memblock_dump(&memblock.memory);memblock_dump(&memblock.reserved);
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAPmemblock_dump(&physmem);
#endif
}void __init_memblock memblock_dump_all(void)
{if (memblock_debug)__memblock_dump_all();
}
memblock_alloc_try_nid 尝试分配内存块
memblock_alloc_try_nid: 4096 bytes align=0x1000 nid=-1 from=0x00000000 max_addr=0x00000000 __memblock_alloc_or_panic+0x15/0x28
/*** memblock_alloc_internal - 分配引导内存块* @size:要分配的内存块大小(以字节为单位)* @align:区域和块大小的对齐方式* @min_addr:要分配的内存区域的下限 (phys address)* @max_addr:要分配的内存区域的上限 (phys address)* @nid:要查找的空闲区域的 nid,任何节点的 %NUMA_NO_NODE* @exact_nid:控制分配回退到其他节点** 使用 memblock_alloc_range_nid() 分配内存块,并将返回的物理地址转换为 virtual。** 如果无法满足 @min_addr 限制,则会删除该限制,并且分配将回退到低于 @min_addr 的内存。其他约束(例如节点和镜像内存)将在 memblock_alloc_range_nid() 中再次处理。**返回:* 成功时已分配内存块的虚拟地址,失败时为 NULL。*/
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);/* retry allocation without lower limit */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);
}
/*** memblock_alloc_try_nid - 分配引导内存块* @size:要分配的内存块大小(以字节为单位)* @align:区域和块大小的对齐方式* @min_addr:首选分配的内存区域的下限 (phys address)* @max_addr:首选分配的内存区域的上限 (phys address),或 %MEMBLOCK_ALLOC_ACCESSIBLE 仅从受 memblock.current_limit 值限制的内存进行分配* @nid:要查找的空闲区域的 nid,任何节点的 %NUMA_NO_NODE** 公共函数,如果启用,则提供额外的调试信息(包括调用方信息)。此函数将分配的内存归零。**返回:* 成功时已分配内存块的虚拟地址,失败时为 NULL。*/
void * __init memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align,phys_addr_t min_addr, phys_addr_t max_addr,int nid)
{void *ptr;memblock_dbg("%s: %llu bytes align=0x%llx nid=%d from=%pa max_addr=%pa %pS\n",__func__, (u64)size, (u64)align, nid, &min_addr,&max_addr, (void *)_RET_IP_);ptr = memblock_alloc_internal(size, align,min_addr, max_addr, nid, false);if (ptr)memset(ptr, 0, size);return ptr;
}
__memblock_alloc_or_panic 分配内存并在失败时出现 panic
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_alloc_or_panic - 尝试分配内存并在失败时出现 panic* @size:要分配的内存块大小(以字节为单位)* @align:区域和块大小的对齐方式* @func:调用方函数名称** 此函数尝试使用 memblock_alloc 分配内存,如果失败,它会使用格式化的消息调用 panic。此功能不能直接使用,请使用 宏 memblock_alloc_or_panic。*/
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_allow_resize Memblock 允许调整大小
static int __init_memblock memblock_double_array(struct memblock_type *type,phys_addr_t new_area_start,phys_addr_t new_area_size)
{/* 在我们知道不适合分配的内存保留区域之前,我们不允许调整大小*/if (!memblock_can_resize)panic("memblock: cannot resize %s array\n", type->name);
}
void __init memblock_allow_resize(void)
{memblock_can_resize = 1;
}
memblock_insert_region 插入新的内存区域
/*** memblock_insert_region - 插入新的 memblock 区域* @type:要插入的 memblock 类型* @idx:插入点的索引* @base:新区域的基址* @size:新区域的大小* @nid:新区域的节点 ID* @flags:新区域的标志** 在 @idx 处将新的 memblock 区域 [@base, @base @size) 插入 @type。* @type必须已经有额外的空间来容纳新的区域。*/
static void __init_memblock memblock_insert_region(struct memblock_type *type,int idx, phys_addr_t base,phys_addr_t size,int nid,enum memblock_flags flags)
{struct memblock_region *rgn = &type->regions[idx];BUG_ON(type->cnt >= type->max);memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));rgn->base = base;rgn->size = size;rgn->flags = flags;memblock_set_region_node(rgn, nid);type->cnt++;type->total_size += size;
}
memblock_isolate_range 隔离内存区域
/*** memblock_isolate_range - 将给定范围隔离到不相交的内存块中* @type:用于隔离范围的 memblock 类型* @base:要隔离的范围基数* @size:隔离范围的大小* @start_rgn:隔离区域开始的 out 参数* @end_rgn:隔离区域结束的 out 参数** @type 并确保区域不会跨越 [@base, @base @size) 定义的边界。* 交叉区域在边界处被分割,这最多可能会再创建两个区域。 * 范围内第一个区域的索引以 *@start_rgn 返回,范围后的第一个区域的索引以 *@end_rgn 返回。**返回:* 成功时为 0,失败时为 -errno。*/
static int __init_memblock memblock_isolate_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size,int *start_rgn, int *end_rgn)
{phys_addr_t end = base + memblock_cap_size(base, &size);int idx;struct memblock_region *rgn;*start_rgn = *end_rgn = 0;if (!size)return 0;/* 我们最多再创建两个区域*/while (type->cnt + 2 > type->max)if (memblock_double_array(type, base, size) < 0)return -ENOMEM;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;if (rbase < base) {/** @RGN 从下方相交。 拆分并继续处理下一个区域 - 新的上半部分。*/rgn->base = base;rgn->size -= base - rbase;type->total_size -= base - rbase;memblock_insert_region(type, idx, rbase, base - rbase,memblock_get_region_node(rgn),rgn->flags);} else if (rend > end) {/** @RGN 从上方相交。 拆分并重做当前区域 - 新的下半部分。*/rgn->base = end;rgn->size -= end - rbase;type->total_size -= end - rbase;memblock_insert_region(type, idx--, rbase, end - rbase,memblock_get_region_node(rgn),rgn->flags);} else {/*@rgn 已完全包含,请记录下来*/if (!*end_rgn)*start_rgn = idx;*end_rgn = idx + 1;}}return 0;
}
memblock_remove_region 删除内存区域
static void __init_memblock memblock_remove_region(struct memblock_type *type, unsigned long r)
{type->total_size -= type->regions[r].size;memmove(&type->regions[r], &type->regions[r + 1],(type->cnt - (r + 1)) * sizeof(type->regions[r]));type->cnt--;/* 空数组的特殊情况 */if (type->cnt == 0) {WARN_ON(type->total_size != 0);type->regions[0].base = 0;type->regions[0].size = 0;type->regions[0].flags = 0;memblock_set_region_node(&type->regions[0], MAX_NUMNODES);}
}
memblock_phys_free 释放物理内存块
static int __init_memblock memblock_remove_range(struct memblock_type *type,phys_addr_t base, phys_addr_t size)
{int start_rgn, end_rgn;int i, ret;ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);if (ret)return ret;for (i = end_rgn - 1; i >= start_rgn; i--)memblock_remove_region(type, i);return 0;
}/*** memblock_phys_free - Free Boot 内存块* @base:引导内存块的 phys 起始地址* @size:引导内存块的大小(以字节为单位)** 释放先前由 memblock_phys_alloc_xx() API 分配的启动内存块。* 释放内存不会释放给好友分配器。*/
int __init_memblock memblock_phys_free(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_);kmemleak_free_part_phys(base, size);return memblock_remove_range(&memblock.reserved, base, size);
}
free_memmap 释放内存映射
static void __init free_memmap(unsigned long start_pfn, unsigned long end_pfn)
{struct page *start_pg, *end_pg;phys_addr_t pg, pgend;/** 将 start_pfn/end_pfn 转换为结构体页面指针。*/start_pg = pfn_to_page(start_pfn - 1) + 1;end_pg = pfn_to_page(end_pfn - 1) + 1;/** 转换为物理地址,并将 start 向上舍入,向下舍入 end。*/pg = PAGE_ALIGN(__pa(start_pg));pgend = PAGE_ALIGN_DOWN(__pa(end_pg));/** 如果这些之间有空闲页面,则释放 memmap 数组的部分。*/if (pg < pgend)memblock_phys_free(pg, pgend - pg);
}
free_unused_memmap 释放未使用的内存映射
- mem_map 是内核用于管理物理内存页面的核心数据结构,但在某些情况下,部分内存映射可能未被使用。通过释放这些未使用的区域,可以减少内存浪费,提高系统的可用内存量。
/** mem_map 数组可能会变得非常大。 释放内存映射的未使用区域。*/
static void __init free_unused_memmap(void)
{unsigned long start, end, prev_end = 0;int i;if (!IS_ENABLED(CONFIG_HAVE_ARCH_PFN_VALID) ||IS_ENABLED(CONFIG_SPARSEMEM_VMEMMAP))return;/** 这取决于每个 bank 的地址顺序。SoundBank 之前在 bootmem_init() 中排序。*///遍历系统中的每个内存范围(bank),获取每个范围的起始地址(start)和结束地址(end)for_each_mem_pfn_range(i, MAX_NUMNODES, &start, &end, NULL) {/** 表示将地址向下对齐到页块的起始地址。这是为了满足内存管理子系统的对齐要求。* 内核的虚拟内存子系统假设页块内的内存是连续的,没有空洞* 如果地址未对齐,可能会导致内存管理逻辑出现问题,例如错误的内存分配或回收。*/start = pageblock_start_pfn(start); //ALIGN_DOWN((pfn), pageblock_nr_pages)/** 如果我们有一个前一个 SoundBank,并且当前 SoundBank 和前一个 SoundBank 之间有一个空格,请释放它。*/if (prev_end && prev_end < start)free_memmap(prev_end, start);/* 在此处对齐,因为 VM 子系统中的许多作都假定页面块内的内存映射中没有漏洞*/prev_end = pageblock_align(end); //ALIGN((pfn), pageblock_nr_pages)}
}
reset_all_zones_managed_pages 重置所有区域的管理页面
static int reset_managed_pages_done __initdata;static void __init reset_node_managed_pages(pg_data_t *pgdat)
{struct zone *z;for (z = pgdat->node_zones; z < pgdat->node_zones + MAX_NR_ZONES; z++)atomic_long_set(&z->managed_pages, 0);
}void __init reset_all_zones_managed_pages(void)
{struct pglist_data *pgdat;if (reset_managed_pages_done)return;for_each_online_pgdat(pgdat)reset_node_managed_pages(pgdat);reset_managed_pages_done = 1;
}
memmap_init_reserved_pages 初始化保留页面
- 用于初始化内核中标记为“保留”(reserved)的内存区域的相关数据结构
static void __init memmap_init_reserved_pages(void)
{struct memblock_region *region;phys_addr_t start, end;int nid;/** 在所有保留页上设置 nid,并将 NOMAP 区域的 struct 页视为 PageReserved*/for_each_mem_region(region) {nid = memblock_get_region_node(region);start = region->base;end = start + region->size;//如果内存区域被标记为 NOMAP(不可映射)//FDT中添加"no-map"属性,在early_init_dt_alloc_reserved_memory_arch时会设置为 MEMBLOCK_NOMAPif (memblock_is_nomap(region))reserve_bootmem_region(start, end, nid); //将其标记为保留区域//将内存区域的节点信息设置到 memblock.reserved 中,用于后续管理memblock_set_node(start, end, &memblock.reserved, nid);}/** 遍历所有标记为“保留”的内存区域。*/for_each_reserved_mem_region(region) {//如果内存区域没有设置 MEMBLOCK_RSRV_NOINIT 标志,则需要初始化。if (!memblock_is_reserved_noinit(region)) {nid = memblock_get_region_node(region);start = region->base;end = start + region->size;if (!numa_valid_node(nid))nid = early_pfn_to_nid(PFN_DOWN(start));reserve_bootmem_region(start, end, nid); ////将其标记为保留区域}}
}
__free_memory_core 释放页面内存
//释放从 start 到 end 范围内的页面
static void __init __free_pages_memory(unsigned long start, unsigned long end)
{int order; //每次释放的块大小while (start < end) {/** 释放对齐方式允许的最大块中的页面。** __ffs(start) 计算 start 的最低有效位(即最低的 1 位),确定当前地址对齐的最大块大小* 如果 start 为 0,则默认使用 MAX_PAGE_ORDER(最大页面块大小)*/if (start)order = min_t(int, MAX_PAGE_ORDER, __ffs(start));elseorder = MAX_PAGE_ORDER;//如果当前块大小超出范围(start + (1UL << order) > end),则递减 order,缩小块大小,直到块完全适配当前范围。while (start + (1UL << order) > end)order--;//释放页面块 pfn_to_page(start) 将页面帧号(PFN)转换为 struct pagememblock_free_pages(pfn_to_page(start), start, order);start += (1UL << order);}
}static unsigned long __init __free_memory_core(phys_addr_t start,phys_addr_t end)
{unsigned long start_pfn = PFN_UP(start);unsigned long end_pfn = PFN_DOWN(end);if (!IS_ENABLED(CONFIG_HIGHMEM) && end_pfn > max_low_pfn)end_pfn = max_low_pfn;if (start_pfn >= end_pfn)return 0;__free_pages_memory(start_pfn, end_pfn);return end_pfn - start_pfn;
}
free_low_memory_core_early 释放低内存区域
static unsigned long __init free_low_memory_core_early(void)
{unsigned long count = 0;phys_addr_t start, end;u64 i;//清理整个地址范围的热插拔标记。memblock_clear_hotplug(0, -1);//始化内存映射中标记为保留的页面。这是为了确保保留页面不会被错误地释放或分配。memmap_init_reserved_pages();/**我们需要使用 NUMA_NO_NODE 而不是 NODE_DATA(0)->node_id因为在某些情况下,例如 Node0 没有安装 RAM,Node1 上的内存会很低*/for_each_free_mem_range(i, NUMA_NO_NODE, //表示不指定特定的 NUMA 节点。MEMBLOCK_NONE, //表示不限制内存块类型。&start, &end, NULL)//释放每个内存块,并将释放的内存块数量累加到 count 中count += __free_memory_core(start, end);return count;
}
memblock_free_all 释放所有内存块
/*** memblock_free_all - release free pages to the buddy allocator*/
void __init memblock_free_all(void)
{unsigned long pages;//放未使用的内存映射(memmap)结构。这些结构通常用于描述物理内存页面,但在初始化过程中可能有部分未被使用。通过释放这些未使用的内存,可以减少内存浪费。free_unused_memmap();//置所有内存区域(zone)的管理页面计数reset_all_zones_managed_pages();//释放低地址内存中的空闲页面,并返回释放的页面数量。低地址内存通常是系统启动时优先使用的内存区域,释放这些页面可以将它们交给伙伴分配器进行统一管理。pages = free_low_memory_core_early();//将释放的页面数量添加到系统的总内存页面计数中。totalram_pages 是一个全局变量,表示系统中可用的总内存页面数量。更新该计数可以让内核准确地了解当前的内存状态。totalram_pages_add(pages);
}
memblock_estimated_nr_free_pages 从 memblock 角度返回估计的免费页面数
/** Remaining API functions*/phys_addr_t __init_memblock memblock_phys_mem_size(void)
{return memblock.memory.total_size;
}phys_addr_t __init_memblock memblock_reserved_size(void)
{return memblock.reserved.total_size;
}#define PHYS_PFN(x) ((unsigned long)((x) >> PAGE_SHIFT))/*** memblock_estimated_nr_free_pages - 从 memblock 角度返回估计的免费页面数** 在启动过程中,子系统可能需要粗略估计整个系统中的空闲页面数量,然后才能从伙伴那里获得准确的数字。* 尤其是对于CONFIG_DEFERRED_STRUCT_PAGE_INIT,在启动过程中从好友那里获得的数字可能非常不准确。** 返回:* 从 memblock 的角度来看,免费页面的估计数量。*/
unsigned long __init memblock_estimated_nr_free_pages(void)
{return PHYS_PFN(memblock_phys_mem_size() - memblock_reserved_size());
}