• 内存管理_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
    };
    
    • 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                                    |
    +----------------------------------------------------------------------------------------+
    |              memory              |           reserved       |         physmem          |
    +----------------------------------+--------------------------+--------------------------+
                        |    
                        |  
           -----------------------------------------------------------------------
           |                                   |                                 |
    +--------------+                     +--------------+                   +-----------+
    |    region    |                     |    region    |                   |  region   |
    +--------------+                     +--------------+                   +-----------+
             |                                   |                               |
    +---------------------------------------------------------------------------------------+
    |                                       real       memory                               |
    +---------------------------------------------------------------------------------------+
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    关于内存区域的划分,主要根据处理器架构而定。
    以 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,
    };
    
    • 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

    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;
    }
    
    • 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
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128

    随着处理器的性能提升,多处理器被广泛的使用。为了解决多处理器访问内存时发生冲突的问题,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);
    	}
    }
    
    • 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

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

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

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

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

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

  • 相关阅读:
    安全员c证考试科目有几门----唐山海德教育安全员报考
    mysql锁
    【CVPR 2022】NeRFReN: Neural Radiance Fields with Reflections
    园子的商业化努力-行行AI人才培养「常青藤计划」
    C# VisionPro二次开发在加载VPP时报错解决方法
    android 离线语言合成(文字转语音)
    【操作系统】进程管理(一)—— 进程
    FinalShell安装和使用(用来连接远程Linux)
    LeetCode刷题复盘笔记——47. 全排列 II(一文搞懂回溯解决全排列问题下篇)
    电子沙盘数字沙盘大数据人工智能开发教程第16课
  • 原文地址:https://blog.csdn.net/sinat_34467747/article/details/106061405