内存管理_memblock

内存管理是一个复杂的过程,操作系统对内存的管理方式在启动阶段与运行阶段是不同的。

在启动阶段,系统程序已被加载到内存并开始执行,但是,此时并不存在能过管理内存的模型。
因此,操作系统需要根据当前情况对内存区域进行划分,避免系统程序再执行过程中发生冲突。

内存区域的划分主要围绕两方面,分别是:

  1. 操作系统需要明确内核代码所处的位置,从而避免在执行过程中对该空间的随意覆写;
  2. 操作系统所需的数据结构,操作系统需要为这些数据结构预留出一定的地址空间。

内存区域的划分与管理,Linux 采用 mem_block 结构来完成。
mem_block 将内存划分为三种不同的类型,分别是:memroy,reserver,physmem。

具体的组织结构如下:

//整块内存可以看作多个块状内存的组合,内核创建 memblock_region 来描述块状内存。
struct memblock_region {
	phys_addr_t base;	//基地址
	phys_addr_t size;	//大小
	enum memblock_flags flags;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
	int nid;	//所属节点号
#endif
};

//每个块状内存的作用是不同的,为了区分,内核创建 memblock_type 将作用相同的块状内存进行关联。
struct memblock_type {
	unsigned long cnt;	//区域个数
	unsigned long max;	//数组大小
	phys_addr_t total_size;	//所有区域的空间大小总和
	struct memblock_region *regions;
	char *name;
};

//内核创建 memblock 管理不同作用的 memblock_type,从而间接的管理memblock_region。
struct memblock {
	bool bottom_up;
	phys_addr_t current_limit;
	struct memblock_type memory;
	struct memblock_type reserved;
#ifdef CONFIG_HAVE_MEMBLOCK_PHY_MAP
	struct memblock_type physmem;
#endif
};

从上述结构体中可看出内存目前的层级结构如图所示:

+----------------------------------------------------------------------------------------+
|                                            memblock                                    |
+----------------------------------------------------------------------------------------+
|              memory              |           reserved       |         physmem          |
+----------------------------------+--------------------------+--------------------------+
                    |    
                    |  
       -----------------------------------------------------------------------
       |                                   |                                 |
+--------------+                     +--------------+                   +-----------+
|    region    |                     |    region    |                   |  region   |
+--------------+                     +--------------+                   +-----------+
         |                                   |                               |
+---------------------------------------------------------------------------------------+
|                                       real       memory                               |
+---------------------------------------------------------------------------------------+

关于内存区域的划分,主要根据处理器架构而定。
以 mips 为例,内核对 mem_block 结构的定义如下:

static struct memblock_region memblock_memory_init_regions[INIT_MEMBLOCK_REGIONS] __initdata_memblock;
//memory:128
static struct memblock_region memblock_reserved_init_regions[INIT_MEMBLOCK_RESERVED_REGIONS] __initdata_memblock;
//reserved:128
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
static struct memblock_region memblock_physmem_init_regions[INIT_PHYSMEM_REGIONS] __initdata_memblock;
//physmem:4
#endif

#define __initdata_memblock __meminitdata
#define __meminitdata    __section(".meminit.data")

struct memblock memblock __initdata_memblock = {
	.memory.regions		= memblock_memory_init_regions,
	.memory.cnt		= 1,	
	.memory.max		= INIT_MEMBLOCK_REGIONS,
	.memory.name		= "memory",

	.reserved.regions	= memblock_reserved_init_regions,
	.reserved.cnt		= 1,	
	.reserved.max		= INIT_MEMBLOCK_RESERVED_REGIONS,
	.reserved.name		= "reserved",

#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
	.physmem.regions	= memblock_physmem_init_regions,
	.physmem.cnt		= 1,	
	.physmem.max		= INIT_PHYSMEM_REGIONS,
	.physmem.name		= "physmem",
#endif

	.bottom_up		= false,
	.current_limit		= MEMBLOCK_ALLOC_ANYWHERE,
};

mem_block 创建成功后,内核便可通过 memblock 对内存进行管理。
以向 memory 类型的 mem_block 添加对象为例,分析 mem_block 结构的应用。

//内核提供了相关的mem_block接口来对内存进行相关的操作,比如memblock_add()函数。
int __init_memblock  memblock_add(phys_addr_t base, phys_addr_t size)
{
	return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}

//该函数向类型为memory的mem_block中添加新的空间区域
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;
	struct memblock_region *rgn;

	if (!size)
		return 0;

	//当type->regions[0].size == 0时,意味着此时内存还没有进行区域划分,所以执行该分支代码
	//直接将这段内存区域存入struct memblock_region memory结构体数组中的第一个元素。
	if (type->regions[0].size == 0) {
		WARN_ON(type->cnt != 1 || type->total_size);
		type->regions[0].base = base;
		type->regions[0].size = size;
		type->regions[0].flags = flags;

		//该函数为该区域设置相对应的NUMA节点。type->regions[0].nid = 64。
		memblock_set_region_node(&type->regions[0], nid);
		type->total_size = size;
		return 0;
	}
repeat:

	base = obase;
	nr_new = 0;
	
	//遍历struct memblock_regions memblock_memory_init_regions[]数组。
	//遍历每一个区域的目的是为了避免区域之间的冲突。关于已添加区域与待添加区域,它们之间存在的情况有5种,如下:
	//           	     rbase----------------------------rend
	// 1: base---end
	//         2: base------------end
	//								3: base--------end
	//											4: base------------end
	//															5: base-------end
	for_each_memblock_type(idx, type, rgn) {
		phys_addr_t rbase = rgn->base;
		phys_addr_t rend = rbase + rgn->size;

		//当前区域的基地址大于添加区域的结束地址时,第一种情况,直接退出循环,因为此时添加的区域不与任何其他区域相冲突。
		//由此可知,struct memblock_region结构体的关联顺序是按照地址从小到大的顺序来连接。
		if (rbase >= end)
			break;

		//当前区域的结束地址小于添加区域的基地址时,第五种情况,即当前区域不会与添加区域发生冲突,因此可以直接遍历下一个区域。
		if (rend <= base)
			continue;
		
		//当前区域的基地址大于待添加区域的基地址时,第二种情况,如果insert为真,此时添加前半部分。
		if (rbase > base) {
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
			WARN_ON(nid != memblock_get_region_node(rgn));
#endif
			WARN_ON(flags != rgn->flags);
			nr_new++;
			if (insert)
				memblock_insert_region(type, idx++, base,
						       rbase - base, nid,
						       flags);
		}
		
		//第三种和第四种情况,修改起始地址,第三种可认为其后半部分为0,不再做任何处理。
		base = min(rend, end);
	}

	//第四种情况,base发生改变,只添加待添加区域的后半部分。
	if (base < end) {
		nr_new++;
		if (insert)
			memblock_insert_region(type, idx, base, end - base,
					       nid, flags);
	}
	
	if (!nr_new)
		return 0;
	
	//根据变量insert,可以将该过程看作两轮执行。第一轮是对memblock数组的插入与重新排序,第二轮是将首尾相接的memblock_region进行合并。
	if (!insert) {
	
		//当前类型的memblock个数与新增个数的和大于最大值时,执行该循环条件
		while (type->cnt + nr_new > type->max)
			if (memblock_double_array(type, obase, size) < 0)
				return -ENOMEM;
		insert = true;
		goto repeat;
	} else {
		//合并该类型的memblock_region。即当同样类型的两个区域出现首尾相接的情况时,合并这两个region区域。
		memblock_merge_regions(type);
		return 0;
	}
}

//memblock_insert_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);
	
	//struct memblock_region 结构体按照地址大小来排序,因此当插入新区域时,需要将插入位置及后边的区域推后。
	//memblock_region 是数组结构,所以 &type->regions[idx + 1] - &type->regions[idx] = rgn + 1 - rng。
	//memmove 将 rgn 地址开始的 memblock_region 复制到 rgn+1 地址开始的内存空间。
	memmove(rgn + 1, rgn, (type->cnt - idx) * sizeof(*rgn));

	//对原有的 rgn 结构体以及 type 结构体重新进行赋值。 
	rgn->base = base;
	rgn->size = size;
	rgn->flags = flags;
	memblock_set_region_node(rgn, nid);
	type->cnt++;
	type->total_size += size;
}

随着处理器的性能提升,多处理器被广泛的使用。为了解决多处理器访问内存时发生冲突的问题,Linux 提供了 NUMA(非一致性访问)机制。使每一个处理器对应一段独立的内存空间,这样的一个组织结构称为NUMA节点。因此,利用 mem_block 对内存区域的划分此时变为了对每个 numa 节点的区域划分,即从(CPU<>MEM)变为了(CPU1<>MEM1; CPU2<==>MEM2 …)的形式。

以 mips 架构的龙芯处理器为例:

static __init void prom_meminit(void)
{
	unsigned int node, cpu, active_cpu = 0;
	
	cpu_node_probe();
	init_topplogy_matrix();	//初始NUMA节点拓扑矩阵

	for (node = 0; node < loongson_sysconf.nr_nodes; node++) {
	//遍历NUMA节点,并初始化NUMA节点
		if (node_online(node)) {
			szmem(node);
			node_mem_init(node);
			cpumask_clear(&__node_cpumask[node]);
		}
	}
	memblocks_present();
	max_low_pfn = PHYS_PFN();

	for (cpu = 0; cpu < loongson_sysconf.nr_cpus; cpu++) {
		node = cpu / loongson_sysconf.cores_per_node;
		if (node >= num_online_nodes())
			node = 0;
		if (loongson_sysconf.reserved_cpus_mask & (1 << cpu))
			continue;
		cpumask_set_cpu(active_cpu, &__node_cpumask[node]);
		pr_info("NUMA: set cpumask cpu %d on node %d\n", active_cpu, node);
		active_cpu++;
	}
}

static void __init szmem(unsigned int node)
{
	u32 i, mem_type;
	static unsigned long num_physpages;
	u64 node_id, node_psize, start_pfn, end_pfn, mem_start, mem_size;

	for (i = 0; i < loongson_memmap->nr_map; i++) {
		node_id = loongson_memmap->map[i].node_id;
		if (node_id != node)
			continue;
		mem_type = loongson_memmap->map[i].mem_type;	//获取NUMA节点的内存类型
		mem_size = loongson_memmap->map[i].mem_size;	//获取NUMA节点的内存大小
		mem_start = loongson_memmap->map[i].mem_start;	//获取NUMA节点的内存起始地址
		switch (mem_type) {
			case SYSTEM_RAM_LOW:	//当前内存如果为低端内存
				start_pfn = ((node_id << 44) + mem_start) >> PAGE_SHIFT;	//	起始页号
				node_psize = (mem_size << 20) >> PAGE_SHIFT;
				end_pfn = start_pfn + node_psize;
				num_physpages += node_psize;	//物理页个数
				pr_info("Node%d: mem_type:%d, mem_start:0x%llx, mem_size:0x%llx MB\n", (u32)node_id, mem_type, mem_start, mem_size);
				pr_info("       start_pfn:0x%llx, end_pfn:0x%llx, num_physpages:0x%lx\n", start_pfn, end_pfn, num_physpages);
				memblock_add_node(PFN_PHYS(start_pfn), PFN_PHYS(end_pfn - start_pfn), node);	//添加内存区域到mem_block中,并关联所对应的NUMA节点。
				break;
			case SYSTEM_RAM_HIGH:
				start_pfn = ((node_id << 44) + mem_start) >> PAGE_SHIFT;
				node_psize = (mem_size << 20) >> PAGE_SHIFT;
				end_pfn = start_pfn + node_psize;
				num_physpages += node_psize;
				pr_info("Node%d: mem_type:%d, mem_start:0x%llx, mem_size:0x%llx MB\n", (u32)node_id, mem_type, mem_start, mem_size);
				pr_info("       start_pfn:0x%llx, end_pfn:0x%llx, num_physpages:0x%lx\n", start_pfn, end_pfn, num_physpages);
				memblock_add_node(PFN_PHYS(start_pfn), PFN_PHYS(end_pfn - start_pfn), node);
				break;
			case SYSTEM_RAM_RESERVED:
				pr_info("Node%d: mem_type:%d, mem_start:0x%llx, mem_size:0x%llx MB\n", (u32)node_id, mem_type, mem_start, mem_size);
				memblock_reserve(((node_id << 44) + mem_start), mem_size << 20);
				break;
		}
	}
}

int __init_memblock memblock_add_node(phys_addr_t base, phys_addr_t size, int nid)
{
	return memblock_add_range(&memblock.memory, base, size, nid, 0);
	//添加的内存区域类型为memory,但绑定了 cpu 节点编号
}

static void __init node_mem_init(unsigned int node)
{
	unsigned long node_addrspace offset;
	unsigned long start_pfn, end_pfn;
	
	node_addrspace_offset = nid_to_addroffset(node);	//获取节点偏移量
	pr_info("Node%d's addrspace_offset is 0x%lx\n", node, node_addrspace_offset);
	
	get_pfn_range_for_nid(node, &start_pfn, &end_pfn);	//获取起始页号,与末尾页号
	pr_info("Node%d: start_pfn=0x%lx, end_pfn=0x%lx\n", node, start_pfn, end_pfn);
	
	__node_data[node] = prealloc__node_data + node;

	NODE_DATA(node)->node_start_pfn = start_pfn;
	NODE_DATA(node)->node_spanned_pages = end_pfn - start_pfn;	//页号个数
	
	if (node == 0) {
		unsigned long kernel_end_pfn = PFN_UP(__pa_symbol(&_end));	//内核代码的末尾页号
		
		max_low_pfn = end_pfn;	//节点的末尾页号
		
		memblock_reserve(start_pfn << PAGE_SHIFT, ((kernel_end_pfn - start_pfn) << PAGE_SHIFT));
		//创建预留空间,该部分空间主要用来存放内核代码

		if (node_end_pfn(0) >= (0xffffffff >> PAGE_SHIFT))
			memblock_reserve((node_addrspace_offset | 0xfe000000), 32 << 20);
	}
}

当 numa 节点的内存区域划分,以及初始化完成后,便可以通过 mem_block 添加或申请内存区域。关于NUMA节点地址空间的起始与结尾,是由固件参数传递给内核的。

根据上边的分析,可推测内核在启动阶段会对 reserve 类型的内存进行保护,期间内存的申请与释放可能主要在 memory 类型的内存空间中完成。

至此,操作系统已大致了解了内存的使用情况以及区域的划分,接下来,操作系统将逐步的将内存管理递交给“页式管理”。

void __init setup_arch(char **cmdline_p)
{
	arch_mem_init(cmdline_p);
	//根据cpu的架构来对内存进行初始化。其内部调用函数plat_mem_setup(),该函数的主要目的是处理与平台相关的内存。
	paging_init();
}

上述分析,如有错误请指出。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值