前一篇:Linux内存管理 (1):内核镜像映射临时页表的建立
文章目录
- 1. 前言
- 2. 分析背景
- 3. memblock 简介
- 3.1 memblock 数据结构
- 3.2 memblock 接口
- 4. memblock 的构建过程
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 分析背景
本文基于 ARMv7
架构,Linux 4.14
内核进行分析。
3. memblock 简介
memblock
是内核在启动初期用来管理系统中内存的子系统,经由配置项 CONFIG_HAVE_MEMBLOCK
开启,用于替代更早期内核版本中的 bootmem
。bootmem
经由配置项 CONFIG_NO_BOOTMEM
关闭。在启用了 memblock
的情形下,bootmem
会被禁用,为保持了对 bootmem
接口的兼容,引入 mm/nobootmem.c
模块,将 bootmem
的 __alloc_bootmem() 等系列接口
封装为对 memblock
接口的调用。
3.1 memblock 数据结构
/* include/linux/memblock.h */
#define INIT_MEMBLOCK_REGIONS 128 /* "memory", "reserved" 类型内存区域最大个数 */
/* Definition of memblock flags. */
enum {
MEMBLOCK_NONE = 0x0, /* No special request */
MEMBLOCK_HOTPLUG = 0x1, /* hotpluggable region */
MEMBLOCK_MIRROR = 0x2, /* mirrored region */
MEMBLOCK_NOMAP = 0x4, /* don't add to kernel direct mapping */
};
/* 内存区域 */
struct memblock_region {
phys_addr_t base; /* 内存区域起始物理地址 */
phys_addr_t size; /* 内存区域大小 */
unsigned long flags; /* 内存区域特征标记: HOTPLUG, MIRROR, NOMAP */
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
int nid; /* 内存区域所在的 NUMA 节点 ID */
#endif
};
/* @name 类型内存区域列表 */
struct memblock_type {
unsigned long cnt; /* number of regions: 当前类型内存区域列表 @regions 的已用长度 */
unsigned long max; /* size of the allocated array: 当前类型内存区域列表 @regions 的最大长度 */
phys_addr_t total_size; /* size of all regions: 已添加到当前类型内存区域列表 @regions 的所有区域的总大小 */
struct memblock_region *regions; /* 当前类型内存区域列表 */
char *name; /* 内存区域类型名称: "memory", "reserved" */
};
/* memblock 子系统管理数据结构 */
struct memblock {
bool bottom_up; /* is bottom up direction? */
phys_addr_t current_limit; /* 所管理内存的最大地址 */
struct memblock_type memory; /* 各种类型的内存区域 */
struct memblock_type reserved; /* 系统保留的内存区域(如 initrd, DTB, 内核 crash dump 等内存区域) */
...
};
定义 memblock
子系统管理数据:
/* mm/memblock.c */
/* "memory" 内存区域列表 */
static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
/* "reserved" 内存区域列表 */
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
struct memblock memblock __initdata_memblock = {
/* "memory" 内存区域 */
.memory.regions = memblock_memory_init_regions,
.memory.cnt = 1, /* empty dummy entry */
.memory.max = INIT_MEMBLOCK_REGIONS,
.memory.name = "memory",
/* "reserved" 内存区域 */
.reserved.regions = memblock_reserved_init_regions,
.reserved.cnt = 1, /* empty dummy entry */
.reserved.max = INIT_MEMBLOCK_REGIONS,
.reserved.name = "reserved",
...
.bottom_up = false, /* 地址增长方向 */
.current_limit = MEMBLOCK_ALLOC_ANYWHERE, /* 初始为最大物理地址,后续会修正为 lowmem 的最大地址 */
};
3.2 memblock 接口
memblock
子系统提供了一系列 添加移除内存区域
、分配释放内存
等功能的接口,下面重点挑选几个典型的接口进行下简单说明。
/* include/linux/memblock.h */
/* 添加 内存区域(region) 到 "memory" 内存区列表 */
int memblock_add_node(phys_addr_t base, phys_addr_t size, int nid);
int memblock_add(phys_addr_t base, phys_addr_t size);
/* 添加 内存区域(region) 到指定类型 @type 的内存区列表 */
int memblock_add_range(struct memblock_type *type,
phys_addr_t base, phys_addr_t size,
int nid, unsigned long flags);
/* 从 "memory" 内存区列表移除 内存区域(region) */
int memblock_remove(phys_addr_t base, phys_addr_t size);
/* 添加 内存区域(region) 到 "reserved" 内存区列表 */
int memblock_reserve(phys_addr_t base, phys_addr_t size);
/*
* 管理 "memory" 内存区中 可热插拔(hot pluggable) 内存区域(region).
* 可能引起 内存区域(region) 的拆分和合并。
*/
int memblock_mark_hotplug(phys_addr_t base, phys_addr_t size);
int memblock_clear_hotplug(phys_addr_t base, phys_addr_t size);
/* 从 "memory" 内存区列表内存区域分配内存 */
phys_addr_t memblock_alloc_nid(phys_addr_t size, phys_addr_t align, int nid);
phys_addr_t memblock_alloc_try_nid(phys_addr_t size, phys_addr_t align, int nid);
phys_addr_t memblock_alloc(phys_addr_t size, phys_addr_t align);
/*
* 释放内存区域:
* 将内存区域从 "memory" 内存区列表移到 "reserved" 内存区列表,
* 这些释放的内存区域将不纳入 伙伴管理系统 进行管理。
*/
int memblock_free(phys_addr_t base, phys_addr_t size);
void __memblock_free_early(phys_addr_t base, phys_addr_t size);
以上接口都是基于物理地址
进行操作,以下是基于虚拟地址
进行操作的 memblock
接口:
/* include/linux/bootmem.h */
#if defined(CONFIG_HAVE_MEMBLOCK) && defined(CONFIG_NO_BOOTMEM)
void *memblock_virt_alloc_try_nid_nopanic(phys_addr_t size,
phys_addr_t align, phys_addr_t min_addr,
phys_addr_t max_addr, int nid);
void *memblock_virt_alloc_try_nid(phys_addr_t size, phys_addr_t align,
phys_addr_t min_addr, phys_addr_t max_addr, int nid);
void __memblock_free_early(phys_addr_t base, phys_addr_t size);
void __memblock_free_late(phys_addr_t base, phys_addr_t size);
#else
...
#endif
4. memblock 的构建过程
start_kernel() /* init/main.c */
setup_arch() /* arch/arm/kernel/setup.c */
mdesc = setup_machine_fdt(__atags_pointer); /* arch/arm/kernel/devtree.c */
early_init_dt_scan_nodes(); /* drivers/of/fdt.c */
/* 扫描 DTS 配置的内存区域,添加到 memblock */
of_scan_flat_dt(early_init_dt_scan_memory, NULL);
of_scan_flat_dt() /* drivers/of/fdt.c */
for (offset = fdt_next_node(blob, -1, &depth);
offset >= 0 && depth >= 0 && !rc;
offset = fdt_next_node(blob, offset, &depth)) {
pathp = fdt_get_name(blob, offset, NULL);
if (*pathp == '/')
pathp = kbasename(pathp);
rc = it(offset, pathp, depth, data);
early_init_dt_scan_memory(offset, pathp, depth, data)
}
/*
* DTS 配置的内存区域几种形式:
* (1) 用 memory@0 标记 (可以没有 device_type 属性)
* /{
* memory@0 {
* device_type = "memory"; // 可选
* reg = <0x00000000 0x40000000>; // 1 GB
* };
* };
* (2) device_type = "memory" 标记
* /{
* memory {
* device_type = "memory";
* reg = <0x00000000 0x20000000>;
* };
* };
*/
early_init_dt_scan_memory(offset, pathp, depth, data) /* drivers/of/fdt.c */
...
/* 节点可以用 linux,usable-memory 属性指定内存区域 */
reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
if (reg == NULL) /* 节点没有 linux,usable-memory 属性 */
reg = of_get_flat_dt_prop(node, "reg", &l); /* 节点可以用 reg 属性指定内存区域范围 */
if (reg == NULL)
return 0;
endp = reg + (l / sizeof(__be32));
/* 节点 hotpluggable 属性指定内存区域是否是 可热插拔 的 */
hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);
/* 提取内存区域信息: base, size[, base, size [...]] */
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
u64 base, size;
/*
* 节点的 linux,usable-memory 或 reg 属性中,每两个数据对定义一个内存区域:
* . 第1个数据定义内存区域物理基址
* . 第2个数据定义内存区域大小
* 即 (base size), 如 0x00000000 0x20000000
*/
base = dt_mem_next_cell(dt_root_addr_cells, ®); /* 当前内存区域物理基址 */
size = dt_mem_next_cell(dt_root_size_cells, ®); /* 当前内存区域大小 */
if (size == 0) /* 内存区域大小为 0 */
continue;
/* 添加 新内存区域 到 memblock */
early_init_dt_add_memory_arch(base, size);
if (!hotpluggable) /* 非 热插拔内存区域 */
continue;
/*
* 同一内存区域,不能同时包含 非可热插拔 和 可热插拔 内存,
* 必须独立管理可热插拔(hot pluggable)内存区域.
* 考虑这样一种情形:
* memblock 发现当前可热插拔内存区域 和 一块 memblock 里已有
* 的非可热插拔内存区域相邻, 于是将它们合并了进行管理; 然后
* 在某个时间点, 可热插拔内存区域拔出, 但是内存管理却因为将
* 它和相邻非可插拔内存区域合并了,所以没有了可热插拔内存区
* 域的区间信息(起始地址和大小), 于是就没法单独只移除可热插
* 拔内存区域了。这显然是有问题的: 如果我们选择和可热插拔内
* 存区域相邻的非可热插拔内存区域一起移除,浪费了本可以使用
* 的空间,极端情形下,如果这个和可热插拔内存区域相邻的非可
* 热插拔内存区域是系统中剩下的唯一内存,移除这块内存将变得
* 不可能; 但如果不移除可热插拔内存区域,允许继续访问,也会
* 出问题。
* 基于上述原因, 在系统插入可热插拔内存区域时, 不能将它们和
* 其它 memblock 里面已存在的非可热插拔内存区域合并, 必须对
* 所有的可热插拔内存区域进行独立管理.
*/
if (early_init_dt_mark_hotplug_memory_arch(base, size))
pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
base, base + size);
}
early_init_dt_add_memory_arch(base, size) /* drivers/of/fdt.c */
// 做内存区域对齐、边界检查:
// . 不合规的部分或全部丢弃
// . 小于 PHYS_OFFSET 内存部分全部丢弃
...
memblock_add(base, size); /* 添加内存区域到 memblock */
在插入新内存区域
时,新内存区域
和要插入类型的内存区内的已有区域
,在位置上
可能存在以下6种
关系:
其中,[base, end)
表示新内存区域
物理地址范围,[rbase, rend)
表示已有区域
的物理地址范围。上图中没有给出 新内存区域 和 已有区域 完全重叠
的情形,因为它可以视为情形 (5) 或 (6) 的一种特殊情形
。参照上图,我们继续来看新内存区域
的插入过程:
memblock_add(base, size); /* mm/memblock.c */
/* 添加 新内存区域 [base, base+size-1] 到 "memory" 类型内存区域列表 */
return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
bool insert = false;
phys_addr_t obase = base; /* 保存 新内存区域 基地址 */
phys_addr_t end = base + memblock_cap_size(base, &size); /* 新内存区域 结束地址 + 1 */
/*
* @idx : 要插入的 新内存区域 在 @type 内存区域列表中索引
* @nr_new: 要插入的 新内存区域 计数: 可能会对 新内存区域 进行拆分
*/
int idx, nr_new;
struct memblock_region *rgn;
if (!size)
return 0;
/* special case for empty array */
if (type->regions[0].size == 0) { /* 第1个添加到 @type 类型内存区间 的 内存块 */
WARN_ON(type->cnt != 1 || 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;
return 0;
}
repeat:
/*
* The following is executed twice. Once with %false @insert and
* then with %true. The first counts the number of regions needed
* to accommodate the new area. The second actually inserts them.
*/
/*
* 下面的代码会被执行两遍:
* . 第1遍用来确定需新插入的内存区域数目 (@nr_nw), 不做实际
* 内存区域的插入操作 (@insert == false). 在 @type 类型的
* 内存区域不够存放要新插入的 @nr_nw 个内存区域时, 扩展
* @type 类型内存区域列表长度.
* . 第2遍执行新增的 @nr_nw 个内存区域的插入操作 (@insert == true).
*/
base = obase;
nr_new = 0;
for_each_memblock_type(type, rgn) { /* 遍历 @type 类型内存区 当前所有的 内存区间(region) */
phys_addr_t rbase = rgn->base;
phys_addr_t rend = rbase + rgn->size;
/*
* 新内存区域 在 已有区域 之前 (见前面的图片情形 (1))
* 表示已经为 新内存区域 找到合适的插入位置,且整块
* 新内存区域 已经处理完毕,则可进入第2编的 新内存区域
* 的插入过程。
*/
if (rbase >= end)
break;
/*
* 新内存区域 在 已有区域 之后 (见前面的图片情形 (2))
* 继续比较 新内存区域 和后面 已有内存区域 位置, 插入
* 新内存区域 后, 要保证内存区域按地址从低到高排列, 这
* 是后续的内存区域 插入 和 分配 操作所要求的。
*/
if (rend <= base)
continue;
/*
* @rgn overlaps. If it separates the lower part of new
* area, insert that portion.
*/
/* 新内存区域 和 已有内存区域 存在重叠,彼此之间如前面图中 (3) 或 (4) 的关系 */
if (rbase > base) { /* 新内存区域底部 和 已有区域顶部 部分重叠 (见前面的图片情形 (3)) */
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
WARN_ON(nid != memblock_get_region_node(rgn));
#endif
WARN_ON(flags != rgn->flags);
nr_new++; /* 需新插入一个内存区域: [base, rbase - 1] */
if (insert) /* 第1编执行不做插入操作(@insert==false), 第2编执行插入操作(@insert==true) */
memblock_insert_region(type, idx++, base,
rbase - base, nid,
flags); /* 插入内存区域 */
}
/* area below @rend is dealt with, forget about it */
/*
* 新内存区域 和 已有区域 部分重叠:
* . 如果是 新内存区域 和 已有区域 位置关系是情形 (3),
* 在第1编执行时, 已经在 if (rbase > base) 代码分支中,
* 将需插入的 新内存区域 和 @rgn 非重叠部分 纳入到需新
* 插入计数内存区域计数 @nr_new 中; 在第2编执行已经在
* if (rbase > base) 代码分支中, 对需插入的 新内存区域
* 和 @rgn 非重叠部分 执行了插入操作. 无论是第1编还是第
* 2编执行, 新内存区域都已经处理完毕, 这种情形下在这里将
* base 和 end 置成相同的位置(base == end).
* . 如果 新内存区域 和 已有区域 位置关系是情形 (4), 这里
* 让 base 指向 新内存区域 非重叠部分的开始位置, 然后
* [base, end) 覆盖新内存区域 非重叠部分, 继续和下一个
* 已有内存区域进行位置比较, 最终找到合适的插入位置并
* 进行处理.
*/
base = min(rend, end);
}
/* insert the remaining portion */
/* 新内存区域 仍有未处理部分, 插入这一部分 */
if (base < end) {
nr_new++;
if (insert)
memblock_insert_region(type, idx, base, end - base,
nid, flags); /* 插入内存区域 */
}
/* 没有内存区域需要插入: 新内存区域可能完全包含于已有内存区域 */
if (!nr_new)
return 0;
/*
* If this was the first round, resize array and repeat for actual
* insertions; otherwise, merge and return.
*/
if (!insert) { /* 第1遍 */
/*
* 如果 @type 类型内存区域列表无法容纳新增的 @nr_new 个
* 内存区域, 动态扩展 @type 类型内存区域列表长度, 直到
* 能容纳新增内存区域为止.
*/
while (type->cnt + nr_new > type->max)
if (memblock_double_array(type, obase, size) < 0)
return -ENOMEM;
/* 进入第2遍的实际插入操作阶段 */
insert = true;
goto repeat;
} else {
memblock_merge_regions(type);
return 0;
}
/* 插入 内存区域 */
static void __init_memblock memblock_insert_region(struct memblock_type *type,
int idx, phys_addr_t base,
phys_addr_t size,
int nid, unsigned long flags)
{
struct memblock_region *rgn = &type->regions[idx];
BUG_ON(type->cnt >= type->max);
/* 将新内存区信息记录在 @idx 位置上,首先需将 @idx 位置开始的所有已有旧区域的信息往后移动一个位置 */
memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));
rgn->base = base; /* 记录新内存区域物理基址 */
rgn->size = size; /* 记录新内存区域容量 */
rgn->flags = flags; /* 记录新内存区域特性标记: MEMBLOCK_NONE, MEMBLOCK_HOTPLUG, ... */
memblock_set_region_node(rgn, nid); /* 记录新内存区域所属的 NUMA 节点 ID */
type->cnt++; /* @type 类型内存区域个数加1 */
type->total_size += size; /* 更新 @type 类型所有内存区域总容量 */
}
到此,新增内存区域的插入过程已经分析完毕,还剩下的就是对于热插拔内存区域
隔离处理:
/* 接前面的分析代码 */
early_init_dt_scan_memory(offset, pathp, depth, data) /* drivers/of/fdt.c */
...
/* 节点 hotpluggable 属性指定内存区域是否是 可热插拔 的 */
hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);
...
while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
...
/* 添加 新内存区域 到 memblock */
early_init_dt_add_memory_arch(base, size);
if (!hotpluggable) /* 非 热插拔内存区域 */
continue;
/* 热插拔内存区域处理 */
if (early_init_dt_mark_hotplug_memory_arch(base, size))
pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
base, base + size);
}
early_init_dt_mark_hotplug_memory_arch(base, size) /* drivers/of/fdt.c */
return memblock_mark_hotplug(base, size);
memblock_mark_hotplug(base, size); /* mm/memblock.c */
return memblock_setclr_flag(base, size, 1, MEMBLOCK_HOTPLUG);
static int __init_memblock memblock_setclr_flag(phys_addr_t base,
phys_addr_t size, int set, int flag)
{
struct memblock_type *type = &memblock.memory;
int i, ret, start_rgn, end_rgn;
/*
* 分离区间 [base,base+size) 和 已有内存区域重叠部分,
* 从 start_rgn,end_rgn 分别返回 第1个 和 最后1个(+1) 包含
* 区间 [base,base+size) 的 内存区域索引。
*/
ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);
if (ret)
return ret;
/* 设置内存区间 [base,base+size) 所有内存区域 的 flags (MEMBLOCK_HOTPLUG) */
for (i = start_rgn; i < end_rgn; i++)
if (set) /* 本文的场景 */
memblock_set_region_flags(&type->regions[i], flag);
else
memblock_clear_region_flags(&type->regions[i], flag);
/*
* 可能因对内存区域 flags 的设置/清除 操作,
* 导致原本相邻、具有不同 flags 标记相邻内存区域,
* 现在具有具有了一样的 flags, 这时候需要合并。
*/
memblock_merge_regions(type);
return 0;
}
函数 memblock_isolate_range()
找到 指定内存区间
和 memblock
中已有内存区域重叠相交部分,将重叠相交部分从 memblock
中原有的内存区域分离出来,成为一个新的内存区域,添加插入 memblock
。 memblock_isolate_range()
的功能,举一个具体的例子就很容易理解了,假定已经有 [0,3], [4,8], [9,10]
这3个
内存区域,现在想将 [2,6]
内存区间分离出来,经过 memblock_isolate_range()
处理后,区间被重新分割成 [0,2], [2,3], [4,6], [6,8], [9,10]
这5个
内存区域,同时 start_rgn
返回 1
,end_rgn
返回 3
(内存区域索引从 0
开始)。来看 memblock_isolate_range()
实现细节:
/* mm/memblock.c */
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;
/* we'll create at most two more regions */
while (type->cnt + 2 > type->max)
if (memblock_double_array(type, base, size) < 0)
return -ENOMEM;
for_each_memblock_type(type, rgn) {
phys_addr_t rbase = rgn->base;
phys_addr_t rend = rbase + rgn->size;
/*
* 指定区间 在 已有区域 @rgn 之前 (见前面的图片情形 (1))
* 指定区间处理完毕: 不可能再和其它内存区域相交。
*/
if (rbase >= end)
break;
/*
* 指定区间 在 已有区域 @rgn 之后 (见前面的图片情形 (2))
* 继续处理更高地址内存区域。
*/
if (rend <= base)
continue;
if (rbase < base) { /* 指定区间顶部 和 已有区域 @rgn 底部 部分重叠 (见前面的图片情形 (4)) */
/*
* @rgn intersects from below. Split and continue
* to process the next region - the new top half.
*/
/*
* 将区域 @rgn 一分为二:
* . 非重叠、高地址 部分 [base, rend)
* . 重叠 低地址 部分 [rbase, base)
* 分别记录为两个不同的区域。
*/
/* 区域 @rgn 缩减为只记录 非重叠、高地址部分 [base, rend) */
rgn->base = base;
rgn->size -= base - rbase;
type->total_size -= base - rbase;
/* 在 @rgn 前插入一个新的内存区域,记录 重叠的 低地址 部分 [rbase, base) */
memblock_insert_region(type, idx, rbase, base - rbase,
memblock_get_region_node(rgn),
rgn->flags);
} else if (rend > end) { /* 指定区间底部 和 已有区域 @rgn 顶部 部分重叠 (见前面的图片情形 (3)) */
/*
* @rgn intersects from above. Split and redo the
* current region - the new bottom half.
*/
/*
* 将区域 @rgn 一分为二:
* . 非重叠、高地址 部分 [end, rend)
* . 重叠 低地址 部分 [rbase, end)
* 分别记录为两个不同的区域。
*/
/* 区域 @rgn 缩减为只记录 非重叠、高地址部分 [end, rend) */
rgn->base = end;
rgn->size -= end - rbase;
type->total_size -= end - rbase;
/* 在 @rgn 前插入一个新的内存区域,记录 重叠的 低地址 部分 [rbase, end) */
memblock_insert_region(type, idx--, rbase, end - rbase,
memblock_get_region_node(rgn),
rgn->flags);
} else { /* 已有区域 @rgn 完全包含于 指定内存区间 [base,end) (见前面的图片情形 (5)) */
/* @rgn is fully contained, record it */
/*
* 更新包含 指定内存区间 [base,end) 的内存区域起始索引:
* 只有当 @rgn 完全包含于区间 [base,end) 时才更新起始索引,
* 因为这代表区间 [base,end) 和 区域 @rgn 相关的部分已经完全处理完毕。
*/
if (!*end_rgn)
*start_rgn = idx;
*end_rgn = idx + 1;
}
}
return 0;
}
热插拔内存区间的分离操作完成后,继续看后续逻辑:
memblock_mark_hotplug(base, size)
return memblock_setclr_flag(base, size, 1, MEMBLOCK_HOTPLUG);
...
ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);
...
for (i = start_rgn; i < end_rgn; i++)
if (set) /* 本文的场景: 标记所有 可热插拔 内存区域 */
memblock_set_region_flags(&type->regions[i], flag);
else
...
/* 修改 内存区域 flags,可能需要进行内存区域的合并 */
memblock_merge_regions(type);
static void __init_memblock memblock_merge_regions(struct memblock_type *type)
{
int i = 0;
/* cnt never goes below 1 */
while (i < type->cnt - 1) {
struct memblock_region *this = &type->regions[i];
struct memblock_region *next = &type->regions[i + 1];
/*
* 相邻区域可以合并的必须 同时 满足下列条件:
* . 两区间空间相邻且没有空洞
* . 位于同一 NUMA 节点
* . 具有相同的特定标记 flags
*/
if (this->base + this->size != next->base ||
memblock_get_region_node(this) != memblock_get_region_node(next) ||
this->flags != next->flags) {
BUG_ON(this->base + this->size > next->base);
i++;
continue;
}
/* 相邻两内存区域合二为一,内存区域数减1 */
this->size += next->size;
/* move forward from next + 1, index of which is i + 2 */
memmove(next, next + 1, (type->cnt - (i + 2)) * sizeof(*next));
type->cnt--;
}
}