内存映射过程之memblock

导读

本文主要整理如下问题:

  1. 在mem系统还未建立起来之前,kernel如何管理使用内存?
  2. 经过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    4struct memblock {bool bottom_up;  //分配内存自小向上phys_addr_t current_limit;struct memblock_type memory;//可使用内存struct memblock_type reserved;//已经被使用的内存
#ifdef CONFIG_HAVE_MEMBLOCK_PHYS_MAPstruct 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_MAPint nid;
#endif
};/* Definition of memblock flags. */
enum {//region 的typeMEMBLOCK_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,这里前边有个-,即线性地址大小为256GBUILD_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_INITRDif (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 memearly_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();elsearm64_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_debugreturn 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中找到空闲的空间
  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;

内存管理之memblock探寻相关推荐

  1. Linux内存管理:memblock(引导期间管理内存区域)

    目录 介绍 内存块 内存块初始化 Memblock API 获取有关内存区域的信息 Memblock调试 链接 相关阅读 看原文:<Linux内存管理:memblock> 介绍 内存管理是 ...

  2. 【Linux 内核 内存管理】memblock 分配器编程接口 ⑤ ( memblock_free 函数 | memblock_remove_range 函数 )

    文章目录 一.memblock_free 函数分析 二.memblock_remove_range 函数分析 memblock 分配器提供了如下编程接口 : ① 添加内存 : memblock_add ...

  3. Linux内存管理:memblock

    内核内存管理. 第一部分. 简介 内存管理是操作系统内核中最复杂的部分之一(我认为没有之一).在讲解内核进入点之前的准备工作时,我们在调用 start_kernel 函数前停止了讲解.start_ke ...

  4. linux早期内存管理:memblock完全介绍

    内核版本 4.19.114 背景 linux启动阶段,在伙伴系统初始化之前,也是需要动态内存分配的,比如dts.sparse_vmemmap.页表等,称早期内存管理,early mem manger. ...

  5. 【Linux 内核 内存管理】memblock 分配器 ③ ( memblock_region 内存块区域 | memblock_region 结构体成员分析 | memblock 分配器标志位 )

    文章目录 一.memblock_region 内存块区域 二.memblock_region 结构体成员分析 1.base 成员 2.size 成员 3.flags 成员 4.nid 成员 三.mem ...

  6. 【Linux 内核 内存管理】memblock 分配器编程接口 ④ ( memblock_alloc 函数 | memblock_alloc_base 函数 )

    文章目录 一.memblock_alloc 函数分析 二.memblock_alloc_base 函数分析 三.__memblock_alloc_base 函数分析 四.memblock_alloc_ ...

  7. 深入理解Linux内存管理--目录导航

    日期 内核版本 架构 作者 GitHub CSDN 2016-08-31 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 1 ...

  8. 一文掌握 Linux 内存管理

    作者:dengxuanshi,腾讯 IEG 后台开发工程师 以下源代码来自 linux-5.10.3 内核代码,主要以 x86-32 为例. Linux 内存管理是一个很复杂的"工程&quo ...

  9. Linux内存管理知识总结(一)

    以下源代码来自 linux-5.10.3 内核代码,主要以 x86-32 为例 Linux 内存管理是一个很复杂的"工程",它不仅仅是对物理内存的管理,也涉及到虚拟内存管理.内存交 ...

  10. Linux内核内存管理(1):内存块 - memblock

    Linux内核内存管理 内存块 - memblock rtoax 2021年3月 在英文原文基础上,针对中文译文增加5.10.13内核源码相关内容. 1. 简介 内存管理是操作系统内核中最复杂的部分之 ...

最新文章

  1. 关于Windows Message ID 以及应用【转】
  2. html页面button样式
  3. JNLP(jar包签名)
  4. 【Python】调用百度云API图像搜索服务
  5. 注解@NotNull/@NotEmpty/@NotBlank
  6. 简单的学习心得:网易云课堂Android开发第六章SQLite与ContentProvider
  7. CLR运行时细节 - Method Descriptor
  8. linux 画图 源码,Drawing:一款开源的类似微软画图的 Linux 桌面应用
  9. Java程序员春招三面蚂蚁金服,1-7中HashMap死循环分析
  10. java static关键字_Java基础:static关键字作用总结
  11. git pull命令报错
  12. extern作用详解
  13. python caffe框架_Windows下的caffe框架的配置
  14. 微信公众号python开发_基于Python的微信公众平台二次开发(Python常用框架、订阅号开发、公众号开发)...
  15. HTML meta标签使用介绍
  16. windows如何截屏
  17. 编辑SRT字幕,添加在视频中播放
  18. 学者该如何快速入门Python?内附十年Python程序员详细学习攻略
  19. 14- I、 剪绳子(cuttingRope)
  20. Spring Boot项目出现问题: Whitelabel Error Page

热门文章

  1. supervisord的安装
  2. tp框架-----Model模型层
  3. Android UI 调试常用工具(Dump view UI hierarchy for Automator)
  4. 学习笔记之TCP/IP协议分层与OSI參考模型
  5. Session持久化
  6. android平板电脑维修电路图,《图解windows10平板电脑电路原理和维修》大家可以读读看看...
  7. jupyter notebook怎么画决策树图_状态图怎么画?图文详解快速上手UML图
  8. docker搭建sonarqube做代码审计
  9. 阿里云成为云原生计算基金会金牌会员
  10. 【Dubbo篇】--Dubbo框架的使用