linux 内存管理(12) - 物理内存初始化

-了解linux物理内存初始化

1.系统是怎么知道物理内存的?

  memory节点,以arch/arm64/boot/dts/freescale/fsl-ls208xa.dtsi为例:

    memory@80000000 {
        device_type = "memory";
        reg = <0x00000000 0x80000000 0 0x80000000>;
              /* DRAM space - 1, size : 2 GB DRAM */
    };

  这个节点描述了内存的起始地址及大小,内核在解析dtb文件时会去读取该memory节点的内容,从而将检测到的内存注册进系统。

  Uboot会将kernel image和dtb拷贝到内存中,并且将dtb物理地址告知kernel,kernel需要从该物理地址上读取到dtb文件并解析,才能得到最终的内存信息,dtb的物理地址需要映射到虚拟地址上才能访问,但是这个时候paging_init还没有调用,也就是说物理地址的映射还没有完成,那该怎么办呢?Fixed map机制出现了参考:fixmap addresses原理

1.1.DTB映射

  在执行setup_arch中,会最先进行early_fixmap_init(),这个函数用来map dtb,但是它只会建立dtb对应的这段物理地址中间level的页表entry,而最后一个level的页表映射,则通过setup_machine_fdt函数里的fixmap_remap_fdt来创建。fixmap_remap_fdt主要是为fdt建立地址映射,在该函数的最后,调用memblock_reserve保留了该段内存。

  可以看出dtb的映射采用的是fixmap,所谓fixmap就是固定映射,它需要我们明确的知道想要映射的物理地址,并把这段地址映射到想要映射的虚拟地址上。当然这里固定映射还有些片面,因为在fixmap机制实现上,也有支持动态分配虚拟地址的功能,这个功能主要用于临时fixmap映射(这个临时映射就是用来执行early ioremap使用的。),而dtb的映射属于永久映射。

  • early_fixmap_init

  Fixed map指的是虚拟地址中的一段区域,在该区域中所有的线性地址是在编译阶段就确定好的,这些虚拟地址需要在boot阶段去映射到物理地址上。
在这里插入图片描述
  图中fixed: 0xffffffbefe7fd000 - 0xffffffbefec00000,描述的就是Fixed map的区域。详细布局如下所示:(arch/arm64/include/asm/fixmap.h中的enum fixed_addresses)
在这里插入图片描述
  从图中可以看出,如果要访问DTB所在的物理地址,那么需要将该物理地址映射到Fixed map中的区域,然后访问该区域中的虚拟地址即可。访问IO空间也是一样的道理。

1.2.early_fixmap_init函数关键代码:

void __init early_fixmap_init(void)
{
    pgd_t *pgd;
    pud_t *pud;
    pmd_t *pmd;
    unsigned long addr = FIXADDR_START;              /* (1) */

    pgd = pgd_offset_k(addr);           /* (2) */
    if (CONFIG_PGTABLE_LEVELS > 3 &&
        !(pgd_none(*pgd) || pgd_page_paddr(*pgd) == __pa_symbol(bm_pud))) {
        /*
         * We only end up here if the kernel mapping and the fixmap
         * share the top level pgd entry, which should only happen on
         * 16k/4 levels configurations.
         */
        BUG_ON(!IS_ENABLED(CONFIG_ARM64_16K_PAGES));
        pud = pud_offset_kimg(pgd, addr);
    } else {
        if (pgd_none(*pgd))
            __pgd_populate(pgd, __pa_symbol(bm_pud), PUD_TYPE_TABLE);          /* (3) */
        pud = fixmap_pud(addr);
    }
    if (pud_none(*pud))
        __pud_populate(pud, __pa_symbol(bm_pmd), PMD_TYPE_TABLE);    /* (4) */
    pmd = fixmap_pmd(addr);
    __pmd_populate(pmd, __pa_symbol(bm_pte), PMD_TYPE_TABLE);        /* (5) */
......
}
  • FIXADDR_START,定义了Fixed map区域的起始地址,位于arch/arm64/include/asm/fixmap.h中;
  • pgd_offset_k(addr),获取addr地址对应pgd全局页表中的entry,而这个pgd全局页表正是swapper_pg_dir全局页表;
  • 将bm_pud的物理地址写到pgd全局页目录表中;
  • 将bm_pmd的物理地址写到pud页目录表中;
  • 将bm_pte的物理地址写到pmd页表目录表中;

  bm_pud/bm_pmd/bm_pte是三个全局数组,相当于是中间的页表,存放各级页表的entry,定义如下:

static pte_t bm_pte[PTRS_PER_PTE] __page_aligned_bss;
static pmd_t bm_pmd[PTRS_PER_PMD] __page_aligned_bss __maybe_unused;
static pud_t bm_pud[PTRS_PER_PUD] __page_aligned_bss __maybe_unused;

  early_fixmap_init只是建立了一个映射的框架,具体的物理地址和虚拟地址的映射没有去填充,这个是由使用者具体在使用时再去填充对应的pte entry。比如像fixmap_remap_fdt()函数,就是典型的填充pte entry的过程,完成最后的一步映射,然后才能读取dtb文件。
在这里插入图片描述

1.3.early_ioremap_init

  如果在boot早期需要操作IO设备的话,那么ioremap就用上场了。对于一些硬件需要在内存管理系统起来之前就要工作的,我们就可以使用这种机制来映射内存给这些硬件driver使用。各个模块在使用完early ioremap的地址后,需要尽快把这段映射的虚拟地址释放掉,这样才能反复被其他模块继续申请使用。

注意:如果想要在伙伴系统初始化之前进行设备寄存器的访问,那么可以考虑early IO remap机制。

  dtb和early ioremap都是在fixmap区的,如下图:
在这里插入图片描述

1.4.DTB访问

  完成dtb的map之后,内核可以访问这一段的内存了,通过解析dtb中的内容,内核可以勾勒出整个内存布局的情况,为后续内存管理初始化奠定基础。这一步主要在setup_machine_fdt中完成。

2.在内存管理真正初始化之前,内核的代码执行需要分配内存该怎么处理?

  当所有物理内存添加进系统后,在mm_init之前,系统会使用memblock模块来对内存进行管理。
在这里插入图片描述

2.1.结构体
在这里插入图片描述
由三个数据结构来描述:

  • struct memblock定义了一个全局变量,用来维护所有的物理内存;
  • struct memblock_type代表系统中的内存类型,包括实际使用的内存和保留的内存;
  • struct memblock_region用来描述具体的内存区域,包含在struct memblock_type中的regions数组中,最多可以存放128个。

2.2.arm64_memblock_init

  当物理内存都添加进系统之后,arm64_memblock_init会对整个物理内存进行整理,主要的工作就是将一些特殊的区域添加进reserved内存中。函数执行完后,如下图所示:
在这里插入图片描述

refer to

  • https://www.lagou.com/lgeduarticle/23340.html
发布了161 篇原创文章 · 获赞 15 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/weixin_41028621/article/details/104587278