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 中,

调用关系如下:

start_kernel() ---> setup_arch() ---> setup_machine_fdt() ---> early_init_dt_scan_nodes() ---> early_init_dt_scan_memory() ---> early_init_dt_add_memory_arch() ---> memblock_add()

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 设置可用物理内存地址最大值

adjust_lowmem_bounds() ---> memblock_set_current_limit() 设置了 memblock.current_limit 为 high_memory,即使用 memblock 内存分配器,分配到的物理内存最大值不会大于 high_memory,分配的内存只能在处于低端内存的线性映射区

memblock 设置保留内存

arm_memblock_init() 函数把除了设备树外的其他不能被申请的内存设置到保留内存,比如 image 所在内存、设备树设置的保留内存等

memblock 内存申请

adjust_lowmem_bounds() 设置完能够申请的最大物理地址后,在 map_lowmem() 开始进行页表的初始化,一级页表所占内存依旧是swapper_page_table,不需要申请,二级页表所占内存通过 early_alloc 申请。疑问:通过 early_alloc 申请的内存位于线性映射区,还未进行创建页表,按理说不能使用虚拟地址,但是申请到内存后就通过虚拟地址往内存写二级页表项的值(cpu_v7_set_pte_ext,能用虚拟地址对内存进行读写的前提是已经建立好映射

 上图注释提示:从此可以使用 early_alloc(),但是在 map_lowmem() 也用了 early_alloc(),map_lowmem() 是对通过 early_alloc() 申请的物理内存和虚拟内存建立页表映射。

memblock 释放和移交管理权流程

 https://tinylab.org/riscv-memblock/

 

posted @ 2023-06-24 22:25  流水灯  阅读(317)  评论(0编辑  收藏  举报