[TOC]

mm/memblock.c: Linux内核的“拓荒时代”内存管理器

mm/memblock.c 实现了一种极其早期的、简单的物理内存分配器,它在内核启动的“拓荒时代”——即在页分配器(伙伴系统)初始化之前——扮演着至关重要的角色。

可以将其想象成一个在建造正式仓库(伙伴系统)之前,用来管理建筑材料(物理内存)的临时账本和场地规划师。它的唯一使命是在最原始的环境下,为内核自身的初始化提供最基本的内存分配服务,并在完成使命后,将所有管理权平稳地移交给更高级的内存管理系统。


一、 核心问题:为什么需要 memblock

在内核启动的极早期(start_kernel 函数刚开始执行时),真正的内存管理子系统(如伙伴系统、Slab 分配器)还完全不存在。这些高级系统本身就需要分配内存来存放它们复杂的数据结构(如 mem_map 数组、kmem_cache 结构等)。这就产生了一个“先有鸡还是先有蛋”的问题:

  • 为了初始化内存管理器,你需要分配内存。
  • 但为了分配内存,你需要一个已初始化的内存管理器。

memblock 就是为了打破这个循环而存在的。它是一个极其简单的、无须复杂数据结构的分配器,可以在最简陋的环境下工作。


二、 核心原理与设计

memblock 的设计思想是极简主义。它不使用链表、树等复杂结构,而是只用了几个静态数组来管理整个系统的物理内存布局。

memblock 将系统的物理内存分为两大类:

  1. 内存区域 (Memory Regions): memblock.memory

    • 这是一个 struct memblock_region 类型的数组,记录了系统中所有可用的物理内存块。
    • 这些信息通常由 Bootloader(通过设备树、E820 表等)传递给内核。例如,[0x100000 - 0x80000000] (1MB 到 2GB)。
  2. 保留区域 (Reserved Regions): memblock.reserved

    • 这也是一个 struct memblock_region 类型的数组,记录了那些不可用的物理内存块。
    • 这些区域包括:内核自身的代码和数据段(.text, .data, .bss)、ACPI 数据、设备树二进制文件等。

struct memblock_region 的定义:

1
2
3
4
5
struct memblock_region {
phys_addr_t base; // 区域的物理基地址
phys_addr_t size; // 区域的大小
// ... 其他标志
};

内存分配的工作方式:
当内核需要调用 memblock_alloc() 来分配一块内存时,memblock 的工作方式就像一个反向的橡皮擦

  1. 扫描可用内存: 它会遍历 memblock.memory 数组,寻找一个足够大的、尚未被保留的可用内存区域。
  2. “保留”新区域: 一旦找到合适的空间,它不会memblock.memory 中“切”一块下来,而是简单地调用 memblock_reserve(),将刚刚分配出去的这块内存区域的信息,添加memblock.reserved 数组中。
  3. 返回物理地址: 最后,它返回这块内存的物理基地址。

内存释放: memblock 也提供了 memblock_free(),其工作方式与分配相反,即从 memblock.reserved 数组中移除一个区域。

核心优势:

  • 实现简单: 只需要几个数组和一些循环遍历逻辑。
  • 无动态分配: memblock 自身的数据结构(memblock_region 数组)是在编译时静态分配的(或在 .bss 段),它在工作时不需要为自己动态分配任何内存。
  • 功能足够: 它提供了启动阶段所需的所有基本功能:添加内存区域、保留内存区域、分配内存、查找可用内存等。

三、 在内核启动流程中的角色

memblock 的生命周期非常短暂,但作用贯穿了整个启动过程的前半段。

  1. 信息收集阶段 (Arch-specific code):

    • start_kernel 之前,特定于体系结构的代码(如 arch/arm64/kernel/setup.c)会解析 Bootloader 传递的内存布局信息。
    • 它会调用 memblock_add(base, size) 将所有发现的可用物理 RAM 段添加到 memblock.memory 中。
  2. 早期保留阶段:

    • 内核会立即调用 memblock_reserve() 来“保护”那些已经被占用的关键区域,例如:
      • 内核镜像本身占用的空间。
      • 传递进来的设备树二进制文件 (initrd/dtb) 占用的空间。
  3. 早期分配阶段 (start_kernel 内部):

    • start_kernel 函数中,许多早期的子系统初始化都需要内存。它们会调用 memblock_alloc() 来获取。
    • 最重要的分配: 为伙伴系统的 mem_map 数组分配空间是 memblock 最关键的任务之一。mem_map 可能非常巨大(几百MB),只有 memblock 才能在伙伴系统上线前完成这个艰巨的任务。
  4. “交接仪式” (mm_init() 内部):

    • 当内核执行到 mm_init() -> mem_init() 时,标志着 memblock 的历史使命即将结束。
    • mem_init() 函数会遍历 memblock.memory 中记录的所有可用内存区域。
    • 对于每个区域,它会逐页地调用 __free_pages_bootmem() (或类似函数),将这些物理页**“释放”到刚刚初始化完成的伙伴系统中**。
    • 这个过程就像是临时账本的管理员,将所有账目和剩余物资,都交接给了新上任的正式仓库管理员。
  5. 退役: 交接完成后,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 内核如何从最原始的硬件状态,一步步建立起复杂而强大的内存管理大厦的第一步。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/**
* 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
#endif

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;
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开始分配
1
2
3
static inline void *memblock_alloc_from(phys_addr_t size,
phys_addr_t align,
phys_addr_t min_addr)

memblock_reserve 预留内存块

1
2
3
4
5
6
7
8
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 区域

1
2
3
4
#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 区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* 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 区域

  1. 没有添加过区域,直接添加
  2. 添加过区域,需要判断是否需要扩展区域数组
  3. 需要扩展区域数组,需要判断是否有足够的空间
  4. 如果没有足够的空间,扩展区域数组
  5. 如果有足够的空间,直接添加
  6. 添加区域后,合并区域
  7. 如果没有添加区域,直接返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/**
* 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_NUMA
WARN_ON(nid != memblock_get_region_node(rgn));
#endif
WARN_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 判断区域是否是内存区域

1
2
3
4
5
6
7
8
9
10
11
12
13
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 判断区域是否是保留区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
/*
* 地址比较实用程序
*/
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 查找空闲区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* __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 分配启动内存块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
/**
* 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 物理地址分配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 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 内存区域属性的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* 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 设置或清除内存区域的标志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/**
* 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;
else
r->flags &= ~flag;
}

memblock_merge_regions(type, start_rgn, end_rgn);
return 0;
}

memblock_mark_nomap 使用标志 MEMBLOCK_NOMAP 标记内存区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 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 打印所有内存块信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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_NUMA
if (numa_valid_node(memblock_get_region_node(rgn)))
snprintf(nid_buf, sizeof(nid_buf), " on node %d",
memblock_get_region_node(rgn));
#endif
pr_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_MAP
memblock_dump(&physmem);
#endif
}

void __init_memblock memblock_dump_all(void)
{
if (memblock_debug)
__memblock_dump_all();
}

memblock_alloc_try_nid 尝试分配内存块

1
memblock_alloc_try_nid: 4096 bytes align=0x1000 nid=-1 from=0x00000000 max_addr=0x00000000 __memblock_alloc_or_panic+0x15/0x28
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 允许调整大小

1
2
3
4
5
6
7
8
9
10
11
12
13
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 插入新的内存区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 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 隔离内存区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* 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 删除内存区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 释放物理内存块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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 释放内存映射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 是内核用于管理物理内存页面的核心数据结构,但在某些情况下,部分内存映射可能未被使用。通过释放这些未使用的区域,可以减少内存浪费,提高系统的可用内存量。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
/*
* 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 重置所有区域的管理页面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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)的内存区域的相关数据结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
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_NOMAP
if (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 释放页面内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
//释放从 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));
else
order = MAX_PAGE_ORDER;
//如果当前块大小超出范围(start + (1UL << order) > end),则递减 order,缩小块大小,直到块完全适配当前范围。
while (start + (1UL << order) > end)
order--;
//释放页面块 pfn_to_page(start) 将页面帧号(PFN)转换为 struct page
memblock_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 释放低内存区域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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 释放所有内存块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/**
* 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 角度返回估计的免费页面数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/*
* 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());
}