内存映射过程之memblock
导读
本文主要整理如下问题:
- 在mem系统还未建立起来之前,kernel如何管理使用内存?
- 经过memblock初始化完成后,提供怎样的功能?
涉及到的目录文件:
目录 | 描述 |
---|---|
./kernel-4.9/drivers/of/fdt.c | 关于设备树相关函数的定义实现位置 |
./kernel-4.9/Documentation/kernel-parameters.txt | kernel中的命令参数 |
./kernel-4.9/include/linux/memblock.h | memblock相关定义位置 |
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 关系图示
简单图示如下:
- memblock的基本单位是region;
- 将memblock划分为reserved部分和memory部分:
- reserve部分其实是已经使用了的,早期分配时先将FDT的搞出来,如果配置了no-map则后续系统不可见(paging_init会过滤);
- 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();
}
该函数功能:
- 将内存空间纳入memblock的管理范畴;
- 将kernel text部分添加到memblock.memory中;
- 将DTS中配置的reserved mem添加到memblock.reserve中;
- 将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 功能:
- 支持更多的打印信息;
- 添加节点可以查看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_DRAM | memory 中第一个region的起始地址 |
memblock_end_of_DRAM | memory 中最后一个region的起始地址 |
memblock_add | memory region添加 |
memblock_remove | memory region删除 |
memblock_reserve | reserved region 添加 |
memblock_free | reserved region 删除 |
memblock_allow_resize | 在memblock_double_array中使用,即对当前的layout情况做处理 |
memblock_search | 在某个类型地址中找某个地址 |
memblock_find_in_range | 在所提供的range中找到空闲的空间 |
- 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);
}
- 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
- 将所有需要被管理的memory部分添加到memblock中;
- 其中已经被分配的,添加到reserve中,如果后续被alloc的部分也是添加到reserve中
- 这里需要注意一点是,这里处理的都是物理地址,即alloc的话也是物理地址,分配到了但是无法使用哦;
- 提供分配释放、查找使用、debug等相关接口使用;
- add逻辑这里需要特别说明下,目前从code中来看最多支持128个region:
- add时需要检查是否出现重叠;
- 插入新添加的地址范围;
- 对相邻的region合并;
关于映射部分还有两个宏会改变kernel image映射的线性地址:
- CONFIG_ARM64_RANDOMIZE_TEXT_OFFSET 这个宏会在编译的时候随机配置TEXT_OFFSET,使得每次kernel image的偏移不同,默认为0x80000
- CONFIG_RANDOMIZE_BASE=y 这个是启用kaslr功能,即在bootloader中通过kaslr-seed产生一个随机值,可以在搬运kernel image的时候添加一个偏移;
- 可以使得编译值与运行值不同,且每次开机都不同,注意这种情况下的addr2line分析;
- 需要在chosen中配置kaslr-seed且不能配置nokaslr;