Linux内存管理 (2):memblock 子系统的建立

news2024/12/23 23:19:26

前一篇: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 开启,用于替代更早期内核版本中的 bootmembootmem 经由配置项 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, &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, 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 中原有的内存区域分离出来,成为一个新的内存区域,添加插入 memblockmemblock_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;

	/* 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--;
	}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1085252.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【动态规划】121. 买卖股票的最佳时机、122. 买卖股票的最佳时机 II

提示&#xff1a;努力生活&#xff0c;开心、快乐的一天 文章目录 121. 买卖股票的最佳时机&#x1f4a1;解题思路&#x1f914;遇到的问题&#x1f4bb;代码实现&#x1f3af;题目总结 122. 买卖股票的最佳时机 II&#x1f4a1;解题思路&#x1f914;遇到的问题&#x1f4bb;代…

DC电源模块工作效率的特点

BOSHIDA DC电源模块工作效率的特点 DC电源模块是一种常见的电源供应装置&#xff0c;它在广泛应用于各种电子设备中。它是一种直流电源&#xff0c;通常用于提供低压、高电流的电源&#xff0c;如电子器件、LED灯、无线路由器、计算机硬件等。DC电源模块的工作效率是其中一个非…

关于如何进行ChatGPT模型微调的新手指南

微调是指在预训练的模型基础上&#xff0c;通过进一步的训练来调整模型以适应特定任务或领域。预训练的模型在大规模的文本数据上进行了广泛的学习&#xff0c;从中获得了一定的知识和语言理解能力。然而&#xff0c;由于预训练并不针对具体任务&#xff0c;因此需要微调来使模…

启山智软/微信小程序商城

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 启山智软一、微信小程序商城二、微信小程序商城的定义微信小程序商城特点总结 启山智软 想要了解代码规范&#xff0c;学习商城解决方案&#xff0c;点击下方官网链…

11.1.0- iDesktopX新特性之统计面内对象数

作者&#xff1a;Mei 文章目录 一、属性更新二、面内统计对象数 当我们在做数据处理时&#xff0c;可能会遇到需要统计面内包含其他对象数的需求&#xff0c;在以往的iDesktopX 11i版本中&#xff0c;一般是用属性更新功能。今年发布的iDesktopX 11.1.0版本&#xff0c;有一个新…

创新学习方式,电大搜题助您迈向成功之路

近年来&#xff0c;随着信息技术的发展&#xff0c;互联网在教育领域发挥的作用越来越显著。贵州开放大学作为国内首家电视大学&#xff0c;一直致力于创新教学模式&#xff0c;帮助学生更好地获取知识。在学习过程中&#xff0c;学生常常遇到疑难问题&#xff0c;而解决这些问…

Python作业【简单算法题】

总结&#xff1a; 这次题目当中考点是基础知识&#xff0c;还有一些简单的算法&#xff0c;比如说动态规划&#xff0c;插入排序这些&#xff0c;以及切片的知识 筛选法&#xff1a; 这里说一下我对他的理解&#xff0c;之前一直不能理解为什么开个根号就可以减少算法的复杂…

【数据结构复习之路】栈和队列(本站最全最详细讲解) 严蔚敏版

复习完上面一章【线性表】&#xff0c;我们接着复习栈和队列&#xff0c;这篇文章我写的非常详细且通俗易懂&#xff0c;看完保证会带给你不一样的收获。如果对你有帮助&#xff0c;看在我这么辛苦整理的份上&#xff0c;三连一下啦ε٩(๑> ₃ <)۶з 目录: ☆ 栈 &am…

SpringBoot篇之集成Jedis、Lettuce、Redisson

目录 前言一、详解Jedis、Lettuce 和 Redisson的区别二、SpringBoot集成2.1 集成Jedis2.2 集成Lettuce2.3 集成Redisson 总结 前言 大家好&#xff0c;我是AK&#xff0c;最近在做新项目&#xff0c;基于旧项目框架修改&#xff0c;正好最近也在整理springboot相关知识&#x…

C语言系统化精讲(四): 条件判断语句

文章目录 一、if语句二、if…else语句三、else if语句四、if语句的嵌套五、条件运算符六、switch语句的基本形式七、多路开关模式的switch语句八、if…else语句和switch语句的区别 当我们是儿童时&#xff0c;父母就告诉我们记住这句 红灯停&#xff0c;绿灯行&#xff0c;黄灯…

JTS:11 Overlaps 部分重叠

这里写目录标题 版本代码1 多点与多点2 线与线3 面与面 版本 org.locationtech.jts:jts-core:1.19.0 链接: github 代码 /*** 部分重叠*/ public class GeometryOverlaps {private final GeometryFactory geometryFactory new GeometryFactory();private static final Logg…

提升自动化测试效率的秘密武器——Allure Report

一.使用 Allure2 运行方式-Python # --alluredir 参数生成测试报告。 # 在测试执行期间收集结果 pytest [测试用例/模块/包] --alluredir./result/ (—alluredir这个选项 用于指定存储测试结果的路径)# 生成在线的测试报告 allure serve ./result二.使用 Allure2 运行方式-Ja…

TCP/IP(十四)流量控制

一 流量控制 说明&#xff1a; 本文只是原理铺垫,没有用tcpdumpwiresahrk鲜活的案例讲解,后续补充 ① 基本概念 流量控制: TCP 通过接受方实际能接收的数据量来控制发送方的窗口大小 ② 正常传输过程 背景:1、客户端是接收方,服务端是发送方 --> 下载2、假设接收窗…

Vue绑定样式

一、绑定class样式 语法格式&#xff1a; :class "属性名" &#xff08;一&#xff09;字符串写法 该写法适用于样式的类名不确定&#xff0c;需要动态指定的场景 我们用如下的CSS样式进行操作演示 我们要完成点击按钮改变CSS样式的操作&#xff0c;如下图代码所…

33.高等数学

一、函数与极限。 &#xff08;1&#xff09;函数。 1.平方根&#xff1a;有正负号。 2.算术平方根&#xff1a;算术平方根都是正数。 3.复数&#xff1a;是由实部和虚部组成的数&#xff0c;可以表示为abi 的形式&#xff0c;其中 a 是实部&#xff0c;b 是虚部。如果虚部…

检验科LIS系统源码,多家二甲医院实际使用,三年持续优化和运维,系统稳定可靠

检验科LIS系统源码&#xff0c;Client/Server架构SaaS服务模式的LIS系统全套源码&#xff0c;自主版权&#xff0c;有演示。 LIS系统&#xff0c;专为医院检验科设计的一套实验室信息系统。它是以数据库为核心&#xff0c;将实验仪器与电脑连接成网&#xff0c;基础功能包括病人…

C# 中大小端Endian

大小端可以找下资料很多&#xff0c;都是文字的。我每次遇到大小端问题就会搜资料&#xff0c;总是记不住。我自己用用图片记录一下&#xff0c;以备直观的从内存中看到。 在C#中可以用BitConverter.IsLittleEndian来查询。 几个数字在内存中 我们来观察一下&#xff0c;我的…

小程序中如何设置所服务地区的时区

在全球化的背景下&#xff0c;小程序除了在中国使用外&#xff0c;还为海外的华人地区提供服务。例如我们采云小程序为泰国、阿根廷、缅甸等国家的商家就提供过微信小程序。这些商家开通小程序&#xff0c;为本地的华人提供服务。但通常小程序的开发者/服务商位于中国&#xff…

Java多线程篇(10)——BlockingQueue(数组,链表,同步阻塞队列)

文章目录 1、ArrayBlockingQueue2、LinkedBlockingQueue3、SynchronousQueue3.1、transfer 公平实现&#xff08;队列&#xff09;3.2、transfer 非公平实现&#xff08;栈&#xff09; 1、ArrayBlockingQueue put public void put(E e) throws InterruptedException {Objects…

小程序:下拉刷新+上拉加载+自定义导航栏

下拉刷新 &#xff1a; <scroll-view scroll-y"true" 允许纵向滚动 refresher-enabled"true" 开启自定义下拉刷新 默认为false :refresher-triggered&quo…