内存管理之memblock探寻

内存映射过程之memblock

导读

本文主要整理如下问题:

  1. 在mem系统还未建立起来之前,kernel如何管理使用内存?
  2. 经过memblock初始化完成后,提供怎样的功能?

涉及到的目录文件:

目录描述
./kernel-4.9/drivers/of/fdt.c关于设备树相关函数的定义实现位置
./kernel-4.9/Documentation/kernel-parameters.txtkernel中的命令参数
./kernel-4.9/include/linux/memblock.hmemblock相关定义位置

1. memblock 是什么?

memblock即linux 启动后kernel管理dram空间抽象出来的结构,此时buddy系统,slab分配器等并没有被初始化好,当需要执行一些内存管理、内存分配的任务,则先使用memblock的机制;
当buddy系统和slab分配器初始化好后,在mem_init()中对memblock分配器进行释放,内存管理与分配由buddy系统,slab分配器等进行接管。

注意memblock是bootmem的升级版本,在config中有配置:CONFIG_NO_BOOTMEM=y

1.1 结构体关系

1.1.1 code

如下为几个结构体的定义
memblock结构

#define INIT_MEMBLOCK_REGIONS	128
#define INIT_PHYSMEM_REGIONS	4

struct memblock {
	bool bottom_up;  //分配内存自小向上
	phys_addr_t current_limit;
	struct memblock_type memory;//可使用内存
	struct memblock_type reserved;//已经被使用的内存
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
	struct memblock_type physmem;//物理内存,这个宏没打开
#endif
};

struct memblock_type {
	unsigned long cnt;	/* number of regions */
	unsigned long max;	/* size of the allocated array */
	phys_addr_t total_size;	/* size of all regions */
	struct memblock_region *regions;
};

struct memblock_region {
	phys_addr_t base;
	phys_addr_t size;
	unsigned long flags;
#ifdef CONFIG_HAVE_MEMBLOCK_NODE_MAP
	int nid;
#endif
};

/* Definition of memblock flags. */
enum {//region 的type
	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 */
};

1.1.2 关系图示

简单图示如下:
在这里插入图片描述

  1. memblock的基本单位是region;
  2. 将memblock划分为reserved部分和memory部分:
    1. reserve部分其实是已经使用了的,早期分配时先将FDT的搞出来,如果配置了no-map则后续系统不可见(paging_init会过滤);
    2. memory为实际系统可使用内存大小;

上述图示使用graphviz实现,如下为源码记录:

digraph memblock {

graph[nodesep="1.50"]
node [fontsize = "10", color = "skyblue",  shape = "record"]; 


memblock [label = "{
            <head> memblock |
            bool bottom_up |
			phys_addr_t current_limit |
			<memory> memory |
 			<reserved> reserved |
			<physmem> physmem
        }"]; 
memblock_type [label = "{
            <head> memory |
			unsigned long cnt /* number of regions */ |	
			unsigned long max /* size of the allocated array */ |	
			phys_addr_t total_size /* size of all regions */|	
			<memory> regions;
        }"];   		
memblock_type1 [label = "{
            <head> reserved |
			unsigned long cnt /* number of regions */ |	
			unsigned long max /* size of the allocated array */ |	
			phys_addr_t total_size /* size of all regions */|	
			<reserved> regions;
        }"];   	
memblock_region [label = "{
            <head> memblock_region |
			phys_addr_t base |
			phys_addr_t size |
			unsigned long flags 
        }"];  
memblock_region1 [label = "{
            <head> memblock_region |
			phys_addr_t base |
			phys_addr_t size |
			unsigned long flags 
        }"]; 	

memblock : memory : w -> memblock_type : head;
memblock : reserved : w -> memblock_type1 : head;	
memblock_type : memory  -> memblock_region : head;
memblock_type1 : reserved  -> memblock_region1 : head;
}

1.2 结构体初始化

memblock节点初始化

struct memblock memblock __initdata_memblock = {
	.memory.regions		= memblock_memory_init_regions,
	.memory.cnt		= 1,	/* empty dummy entry */
	.memory.max		= INIT_MEMBLOCK_REGIONS,//128

	.reserved.regions	= memblock_reserved_init_regions,
	.reserved.cnt		= 1,	/* empty dummy entry */
	.reserved.max		= INIT_MEMBLOCK_REGIONS,//128

#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAP
	.physmem.regions	= memblock_physmem_init_regions,
	.physmem.cnt		= 1,	/* empty dummy entry */
	.physmem.max		= INIT_PHYSMEM_REGIONS,//4
#endif

	.bottom_up		= false,//内存分配的方向,为true则从低到高,false则从高到低
	.current_limit		= MEMBLOCK_ALLOC_ANYWHERE,//(~(phys_addr_t)0)
};

int memblock_debug __initdata_memblock;

在这里首先构建了结构体,然后在arm64_memblock_init中将这个结构补充完整;

2. arm64_memblock_init 函数解析

本函数即初始化引导阶段的内存管理结构:
在这里插入图片描述

2.1 code走读

void __init arm64_memblock_init(void)
{
	const s64 linear_region_size = -(s64)PAGE_OFFSET; 
	// 这里获取定义的线性地址的大小,这东西是经过CONFIG_ARM64_VA_BITS转换得到的,当前平台默认为39,然后转换kernel和user各自512GB 
	// PAGE_OFFSET即定义在kernel的中间位置,PAGE_OFFSET是0xFFFFFFC000000000,这里前边有个-,即线性地址大小为256G
	
	BUILD_BUG_ON(linear_region_size != BIT(VA_BITS - 1));//做个检测,确保kernel和user 的offset位置在中间;

	memstart_addr = round_down(memblock_start_of_DRAM(), ARM64_MEMSTART_ALIGN);//memstart_addr: 0x8 0000 0000 
	// round_down是去除小数,保留整数的操作,所谓小数,是以第二个参数作为标准的;
	// memblock_start_of_DRAM: 在这里是:0x8 0000 0000,后续在mem_init中大小为:0xffffffbf 0000 0000 (phys_to_pages),0xffffffc0 0000 0000(phys_to_virt)
	// ARM64_MEMSTART_ALIGN:这个宏转换很复杂,核心就是根据页表层级和页表大小进行计算,最终为1 << 30 = 0x4000 0000,就是以G为单位对齐的;

	memblock_remove(max_t(u64, memstart_addr + linear_region_size, __pa(_end)), ULLONG_MAX);
    //将超出范围的地址remove 0x4800000000以外的,  memblock_start_of_dram():  0x8 0000 0000, memblock_end_of_dram: 0x8 8000 0000 
	if (memstart_addr + linear_region_size < memblock_end_of_DRAM()) {//这个判断不会进去,应该也搞不到这么大的物理内存吧
		/* ensure that memstart_addr remains sufficiently aligned */
		memstart_addr = round_up(memblock_end_of_DRAM() - linear_region_size, ARM64_MEMSTART_ALIGN);
		memblock_remove(0, memstart_addr);
	}

	// memory limit的限制,这里是在early_mem中读取FDT的配置,对mem的大小做出限制,如果有配置mem字段的话,目前没有配置;
	if (memory_limit != (phys_addr_t)ULLONG_MAX) {
		memblock_mem_limit_remove_map(memory_limit);
		memblock_add(__pa(_text), (u64)(_end - _text));
	}

	if (IS_ENABLED(CONFIG_BLK_DEV_INITRD) && initrd_start) {//initrd宏有定义,但是initrd_start是0 ,所以这里没有进来
		u64 base = initrd_start & PAGE_MASK;
		u64 size = PAGE_ALIGN(initrd_end) - base;
		if (WARN(base < memblock_start_of_DRAM() || base + size > memblock_start_of_DRAM() + linear_region_size,
			"initrd not fully accessible via the linear mapping -- please check your bootloader ...\n")) {
			initrd_start = 0;
		} else {
			//将这块地址空间搞成reserved 即kernel不可见的黑洞
			memblock_remove(base, size); /* clear MEMBLOCK_ flags */
			memblock_add(base, size);
			memblock_reserve(base, size);
		}
	}

	if (IS_ENABLED(CONFIG_RANDOMIZE_BASE)) {//这个宏是有配置的
		extern u16 memstart_offset_seed;
		u64 range = linear_region_size - (memblock_end_of_DRAM() - memblock_start_of_DRAM()); 
        //这里是把linear的256G与实际物理地址的2G计算出来range,即254G,这里的range应该是线性地址中超出物理地址的范围;

		if (memstart_offset_seed > 0 && range >= ARM64_MEMSTART_ALIGN) {//这里seed为0 ,没有进来
			memstart_addr -= ARM64_MEMSTART_ALIGN * ((range * memstart_offset_seed) >> 16); 
		}
	}

	 //添加kernel code 到reserved mem中
	memblock_reserve(__pa(_text), _end - _text);
#ifdef CONFIG_BLK_DEV_INITRD
	if (initrd_start) {
		//添加initrd code到reserved mem中
		memblock_reserve(initrd_start, initrd_end - initrd_start);
		initrd_start = __phys_to_virt(initrd_start);
		initrd_end = __phys_to_virt(initrd_end);
	}
#endif

	// 扫描添加DTS配置的reserved mem
	early_init_fdt_scan_reserved_mem();//之前已经通过fixmap映射了fdt的位置,后续有一篇跟踪这个函数的整理

	/* 4GB maximum for 32-bit only capable devices */
	if (IS_ENABLED(CONFIG_ZONE_DMA)) //配置项中设置,这里就是物理地址最大值的位置;
		arm64_dma_phys_limit = max_zone_dma_phys();
	else
		arm64_dma_phys_limit = PHYS_MASK + 1;
	dma_contiguous_reserve(arm64_dma_phys_limit);

	//设置为后续可配置改动的
	memblock_allow_resize();
}

该函数功能:

  1. 将内存空间纳入memblock的管理范畴;
  2. 将kernel text部分添加到memblock.memory中;
  3. 将DTS中配置的reserved mem添加到memblock.reserve中;
  4. 将DMA部分内存添加到memblock.reserve中
2.1.1 小细节

memory_limit:如果在FDT中定义了mem字段,则会解析出来并配置相关限制项,默认是没有处理的;

static phys_addr_t memory_limit = (phys_addr_t)ULLONG_MAX;
static int __init early_mem(char *p)
{
	if (!p)
		return 1;

	memory_limit = memparse(p, &p) & PAGE_MASK;
	pr_notice("Memory limited to %lldMB\n", memory_limit >> 20);

	return 0;
}
early_param("mem", early_mem);

3. 调试过程记录

3.1 memblock初始化时相关地址打印

memblock中配置的一些关键的地址信息:

linear_region_size: 0x40 0000 0000, memstart_addr: 0x8 0000 0000 //线性地址大小
memblock_start_of_dram(): 0x8 0000 0000, memblock_end_of_dram: 0x8 8000 0000 //物理地址范围
ARM64_MEMSTART_ALIGN : 0x4000 0000 //对齐
memory_limit: 0xffffffffffffffff , (phys_addr_t)ULLONG_MAX: 0xffffffffffffffff
memstart_offset_seed: 0x0, range: 0x3f80000000
high_memory: 0xffffffc080000000
arm64_dma_phys_limit: 0x880000000

3.2 memblock debug功能

linux中提供了这部分的debug 功能:

  1. 支持更多的打印信息;
  2. 添加节点可以查看memblock的信息情况;

在DTS的chosen节点中添加memblock=debug,则在启动中会读取这里的支持:

 static int __init early_memblock(char *p)
 {
 	if (p && strstr(p, "debug")) memblock_debug = 1;//即这里配置为memblock_debug
 	return 0;
 }
 early_param("memblock", early_memblock);
 
 #define memblock_dbg(fmt, ...) if (memblock_debug) printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

从如上定义可以看到,添加memblockdebug之后,memblock_dbg则会输出信息,各个入口函数都会有:
在这里插入图片描述

ps:查看kernel支持的参数可以参考官方文档:kernel-4.9/Documentation/kernel-parameters.txt

实际打印:这里是meminit过程中扫描dts中相关reserved mem部分分配情况:

memblock_reserve: [0x00000800080000-0x000008014a4fff] flags 0x0 arm64_memblock_init+0x290/0x378
memblock_reserve: [0x00000816a60000-0x0000081aa5ffff] flags 0x0 early_init_dt_reserve_memory_arch+0x1c/0x24
memblock_reserve: [0x0000087ffff000-0x0000087fffffff] flags 0x0 early_pgtable_alloc+0x18/0xbc
memblock_reserve: [0x0000087fffe000-0x0000087fffefff] flags 0x0 early_pgtable_alloc+0x18/0xbc
memblock_reserve: [0x0000087fffd000-0x0000087fffdfff] flags 0x0 early_pgtable_alloc+0x18/0xbc
memblock_reserve: [0x0000087fffc000-0x0000087fffcfff] flags 0x0 early_pgtable_alloc+0x18/0xbc
memblock_reserve: [0x0000087ffffe00-0x0000087ffffe3f] flags 0x0 __alloc_memory_core_early+0xa0/0xe0
memblock_reserve: [0x0000087ffffd80-0x0000087ffffdbf] flags 0x0 __alloc_memory_core_early+0xa0/0xe0
memblock_reserve: [0x0000087ffffd00-0x0000087ffffd3f] flags 0x0 __alloc_memory_core_early+0xa0/0xe0
memblock_reserve: [0x0000087ffffc80-0x0000087ffffcbf] flags 0x0 __alloc_memory_core_early+0xa0/0xe0
memblock_reserve: [0x0000087ffffa80-0x0000087ffffc3a] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ffff880-0x0000087ffffa3a] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ffff680-0x0000087ffff83a] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ffbb080-0x0000087ffbc07f] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ffba080-0x0000087ffbb07f] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ff5e000-0x0000087ffb9fff] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087fffff80-0x0000087fffff87] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ffff600-0x0000087ffff607] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ffff580-0x0000087ffff58f] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ffff500-0x0000087ffff51f] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ffff400-0x0000087ffff4ff] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ffff380-0x0000087ffff3df] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8
memblock_reserve: [0x0000087ffff300-0x0000087ffff35f] flags 0x0 memblock_virt_alloc_internal+0x188/0x1e8

在sys/kernel/debug/memblock中会生成当前memory和reserved两种case的
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jUMVkZkI-1597759044855)(evernotecid://F527BF5A-8FCD-42B4-B85F-97CCC2082198/appyinxiangcom/15722577/ENResource/p372)]

4 memblock操作函数描述

相关函数有很多,这里只整理在init过程中遇到的:

函数功能描述
memblock_start_of_DRAMmemory 中第一个region的起始地址
memblock_end_of_DRAMmemory 中最后一个region的起始地址
memblock_addmemory region添加
memblock_removememory region删除
memblock_reservereserved region 添加
memblock_freereserved region 删除
memblock_allow_resize在memblock_double_array中使用,即对当前的layout情况做处理
memblock_search在某个类型地址中找某个地址
memblock_find_in_range在所提供的range中找到空闲的空间
  1. memblock_add:添加对应地址的region节点
int __init_memblock memblock_add(phys_addr_t base, phys_addr_t size)
{
	memblock_dbg("memblock_add: [%#016llx-%#016llx] flags %#02lx %pF\n",
		     (unsigned long long)base,
		     (unsigned long long)base + size - 1,
		     0UL, (void *)_RET_IP_);

	return memblock_add_range(&memblock.memory, base, size, MAX_NUMNODES, 0);
}
  1. memblock_remove:先把连接去掉,然后删除对应结构中的region
int __init_memblock memblock_remove(phys_addr_t base, phys_addr_t size)
{
	return memblock_remove_range(&memblock.memory, base, size);
}

static int __init_memblock memblock_remove_range(struct memblock_type *type,
					  phys_addr_t base, phys_addr_t size)
{
	int start_rgn, end_rgn;
	int i, ret;

	ret = memblock_isolate_range(type, base, size, &start_rgn, &end_rgn);
	if (ret)
		return ret;

	for (i = end_rgn - 1; i >= start_rgn; i--)
		memblock_remove_region(type, i);
	return 0;
}

相关函数这里不多描述,定义在:./kernel-4.9/include/linux/memblock.h
在这里插入图片描述

总结

简单来说,就会说定义了两种类型:memory & reserve

  1. 将所有需要被管理的memory部分添加到memblock中;
  2. 其中已经被分配的,添加到reserve中,如果后续被alloc的部分也是添加到reserve中
    1. 这里需要注意一点是,这里处理的都是物理地址,即alloc的话也是物理地址,分配到了但是无法使用哦;
  3. 提供分配释放、查找使用、debug等相关接口使用;
  4. add逻辑这里需要特别说明下,目前从code中来看最多支持128个region:
    1. add时需要检查是否出现重叠;
    2. 插入新添加的地址范围;
    3. 对相邻的region合并;

关于映射部分还有两个宏会改变kernel image映射的线性地址:

  1. CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET 这个宏会在编译的时候随机配置TEXT_OFFSET,使得每次kernel image的偏移不同,默认为0x80000
  2. CONFIG_RANDOMIZE_BASE=y 这个是启用kaslr功能,即在bootloader中通过kaslr-seed产生一个随机值,可以在搬运kernel image的时候添加一个偏移;
    1. 可以使得编译值与运行值不同,且每次开机都不同,注意这种情况下的addr2line分析;
    2. 需要在chosen中配置kaslr-seed且不能配置nokaslr;
  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值