国外html5模板网站,长沙网页,2021年房价下跌已成定局,idc网站模版前一篇#xff1a;Linux内存管理 (1)#xff1a;内核镜像映射临时页表的建立 文章目录 1. 前言2. 分析背景3. memblock 简介3.1 memblock 数据结构3.2 memblock 接口 4. memblock 的构建过程 1. 前言
限于作者能力水平#xff0c;本文可能存在谬误#xff0c;因此而给读者…前一篇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, /* dont 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_MAPint 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) 用 memory0 标记 (可以没有 device_type 属性)* /{* memory0 {* 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, reg); /* 当前内存区域物理基址 */size dt_mem_next_cell(dt_root_size_cells, reg); /* 当前内存区域大小 */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, basesize-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_MAPWARN_ON(nid ! memblock_get_region_node(rgn));
#endifWARN_ON(flags ! rgn-flags);nr_new; /* 需新插入一个内存区域: [base, rbase - 1] */if (insert) /* 第1编执行不做插入操作(insertfalse), 第2编执行插入操作(inserttrue) */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,basesize) 和 已有内存区域重叠部分, * 从 start_rgn,end_rgn 分别返回 第1个 和 最后1个(1) 包含* 区间 [base,basesize) 的 内存区域索引。*/ret memblock_isolate_range(type, base, size, start_rgn, end_rgn);if (ret)return ret;/* 设置内存区间 [base,basesize) 所有内存区域 的 flags (MEMBLOCK_HOTPLUG) */for (i start_rgn; i end_rgn; i)if (set) /* 本文的场景 */memblock_set_region_flags(type-regions[i], flag);elsememblock_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 返回 1end_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;/* well 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--;}
}