linux memblock 介绍
memblock 作用
内核初始化阶段是用内存分配器 memblock 进行管理,因为内核后期使用的内存分配器初始化过程中有很多内存结构体,不可能在静态编译阶段就静态初始化所有的这些内存结构体,如伙伴分配器,那么伙伴分配器如何获取内存来初始化自己呢 ?为了达到这个目标,我们先实现一个满足要求的但是可能效率不高的内存分配器。用它来负责系统初始化初期的内存管理,最重要的,用它来初始化我们内存的数据结构,直到我们真正的内存管理器被初始化完成并能投入使用, 我们将旧的内存管理器丢掉。
memblock 获取物理内存信息
在内核开启 MMU 之后,整个内存世界实际上还处于一片黑暗之中,毕竟这时候内核并不知道当前系统中物理内存的信息,只是为内核 image、dtb 建立了页表,内核只有访问这两部分才是安全的。
紧接着,memblock 内存分配器就开始工作了,这是内核中静态定义的一个内存管理器,其首要工作就是将所有物理内存纳入管理,首先从通过扫描 dtb 获取物理内存的相关信息,看清楚整个物理内存世界,然后将那些已经被使用的内存设为保留,比如内核镜像所在内存、dtb 内存、页表内存。至于其它的内存都是空闲可支配的。
从上图可以看出 MEMBLOCK 采用了 3 层逻辑单元。第一层逻辑单元用于管理整个物理内存, 第二层逻辑单元管理不同类型的内存区,第三层逻辑单元管理独立的内存区块。 每层逻辑单元采用不同的数据结构进行维护,每种数据结构的相互配合,共同作为 MEMBLOCK 内存分配器的基础框架。
设备启动时,引导程序把设备树二进制文件从存储设备读到内存中,引导内核的时候把设备树二进制文件的起始地址传给内核,内核解析设备树二进制文件后得到硬件信息。
memory@80000000 { device_type = "memory"; reg = <0x80000000 0x20000000>; }; reserved-memory { #address-cells = <1>; #size-cells = <1>; ranges; linux,cma { compatible = "shared-dma-pool"; reusable; size = <0xa000000>; linux,cma-default; }; };
上面设备树描述了一个以 0x80000000 为首地址,长度为 0x20000000 的内存,会被添加到 memblock 中,
调用关系如下:
struct memblock_type memory 存储着可用内存的内存块信息,struct memblock_type reserved 存储着保留内存块信息(被占用,不可申请)
struct memblock_region 实例存储着每块内存的物理首地址和大小
if (atags_vaddr) { mdesc = setup_machine_fdt(atags_vaddr); if (mdesc) memblock_reserve(__atags_pointer, fdt_totalsize(atags_vaddr)); }
__atags_pointer 是设备树的物理地址,如果匹配到设备树,则会把设备树所在内存块加入到保留内存 reserved 中,后面申请内存就会避开这块内存
memblock 设置可用物理内存地址最大值
memblock 设置保留内存
memblock 内存申请
上图注释提示:从此可以使用 early_alloc(),但是在 map_lowmem() 也用了 early_alloc(),map_lowmem() 是对通过 early_alloc() 申请的物理内存和虚拟内存建立页表映射。
memblock 释放和移交管理权流程
https://tinylab.org/riscv-memblock/