setup_arch:bootmem_init : sparse_init

kernel: 5.10
arch: arm64

首先简单说明一下背景知识,启动阶段的内存模型一般有4种:FLATMEM,经典sparse和sparse vmemmap,以及zone devie

  1. FLATMEM
    这种模型适用于具有连续物理内存或大部分连续物理内存的非NUMA系统。 在FLATMEM内存模型中,有一个全局mem_map数组映射整个物理内存。对于即使是空洞的物理内存,也需要分配struct page结构体,只不过不会被初始化,映射到具体的物理内存。因此使用的时候要通过pfn_valid来进行检测,可以看到这样实际是浪费了一些struct page的。
  2. SPARSEMEM
    SPARSEMEM是Linux中最通用的内存模型,也是唯一支持多种高级功能的内存模型。SPARSEMEM模型将物理内存表示为section的集合。一个section用struct mem_section表示,结构体成员section_mem_map指向struct page数组的指针。它还存储了一些有助于section管理的魔数。由体系结构定义的SECTION_SIZE_BITS和MAX_PHYSMEM_BITS来指定section大小和section的最大数量。MAX_PHYSMEM_BITS是体系结构支持的物理地址的实际宽度,而SECTION_SIZE_BITS是任意值,最大数目的section个数为:NR_MEM_SECTIONS = 2 ^ (MAX_PHYSMEM_BITS - SECTION_SIZE_BITS)
    mem_section对象排列在一个名为mem_sections的二维数组中。
    (1) 当CONFIG_SPARSEMEM_EXTREME被禁用时,mem_sections数组是静态定义的,有NR_MEM_SECTIONS行。
    (2) 当CONFIG_SPARSEMEM_EXTREME启用时,mem_sections数组是动态分配的。每行包含相当于PAGE_SIZE大小的mem_section个数
    架构设置代码应该调用sparse_init()来初始化内存sections和内存映射。
    使用SPARSEMEM,有两种可能的方法将PFN转换为相应的结构页——经典sparse和sparse vmemmap,由CONFIG_SPARSEMEM_VMEMMAP的值决定。
    (1) 经典稀疏编码在page->flags中对页面的section编号进行编码,并使用PFN的高位来访问映射该页帧的section。在一个section中,PFN是page数组的索引。 会有多次访存行为,因此出现了稀疏vmemmap
    (2) 稀疏vmemmap使用虚拟内存映射来优化pfn_to_page和page_to_pfn操作。有一个指向全局struct page数组的vmemmap指针。PFN是该数组的索引,且struct page距vmemmap的偏移量是该page的PFN。要使用vmemmap,必须保留一个虚拟地址范围,该范围将映射物理页面,并确保vmemmap指向该范围。架构应该实现vmemmap_populate()方法为struct page分配物理内存并为虚拟内存映射创建页表。如果一个架构对vmemmap映射没有任何特殊要求,它可以使用通用内存管理提供的默认vmemmap_populate_basepages()。
  3. ZONE_DEVICE(TODO)
    虚拟映射存储器映射允许将持久存储器设备的struct page对象存储在这些设备预先分配的存储器中。这个存储用结构vmem_altmap表示,它最终通过一长串函数调用传递给vmemmap_populate()。vmemmap_populate()实现可以使用vmem_altmap以及vmemmap_alloc_block_buf()来在持久性存储器设备上分配存储器映射。

此处我们主要是分析的稀疏vmemmap的内存模型
在这里插入图片描述

sparse_init是在bootmem_init中被调用,它初始化在线的每个mem_section,为这些mem_section创建struct page结构体,并为这些struct page结构体创建页表,映射到vmemmap虚拟映射区

/*
 * Allocate the accumulated non-linear sections, allocate a mem_map
 * for each and record the physical to section mapping.
 */
void __init sparse_init(void)
{
        unsigned long pnum_end, pnum_begin, map_count = 1;
        int nid_begin;

        memblocks_present();

        pnum_begin = first_present_section_nr();
        nid_begin = sparse_early_nid(__nr_to_section(pnum_begin));

        /* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */
        set_pageblock_order();

        for_each_present_section_nr(pnum_begin + 1, pnum_end) {
                int nid = sparse_early_nid(__nr_to_section(pnum_end));

                if (nid == nid_begin) {
                        map_count++;
                        continue;
                }
                /* Init node with sections in range [pnum_begin, pnum_end) */
                sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
                nid_begin = nid;
                pnum_begin = pnum_end;
                map_count = 1;
        }
        /* cover the last node */
        sparse_init_nid(nid_begin, pnum_begin, pnum_end, map_count);
        vmemmap_populate_print_last();
}
  1. memblocks_present: memblocks_present实际就是遍历memblock.memory下的所有memblock_region区域,将每个memblock_region区域按section划分,创建对应的mem_section结构体,且通过mem_section的section_mem_map标记为在线状态。关于memblock.memory的初始化可以参考early_init_dt_scan_memorystruct memblock

一般一个node对应一个memblock_region?

  1. first_present_section_nr:一个node由多个root组成,一个root由多个mem_section组成,此处获取首个root的首个mem_section编号
  2. sparse_early_nid(__nr_to_section(pnum_begin)):根据mem_section编号获取早期设置的nid
    在早期阶段mem_section->section_mem_map的布局如下:
    bit0:present, bit1:has memmap; bit2:online; bit3:eary; bit4-:node id
  3. set_pageblock_order:如果没有定义CONFIG_HUGETLB_PAGE_SIZE_VARIABLE则为空
  4. for_each_present_section_nr:
#define for_each_present_section_nr(start, section_nr)          \
        for (section_nr = next_present_section_nr(start-1);     \
             ((section_nr != -1) &&                             \
              (section_nr <= __highest_present_section_nr));    \
             section_nr = next_present_section_nr(section_nr))

从section num为start的section开始,对每个present的section进行遍历

  1. sparse_init_nid
    (1)sparse_early_usemaps_alloc_pgdat_section分配mem_section_usage结构体空间?
    (2)sparse_buffer_init:为一个node在线的所有section分配struct page空间
    (3)for_each_present_section_nr:遍历node下所有在线的mem_section执行__populate_section_memmap和sparse_init_one_section
    (3)__populate_section_memmap:为mem_sectoin对应的struct page本身创建页表
    (4)sparse_init_one_section:初始化mem_section,其中sparse_encode_mem_map返回mem_section的首个page的虚拟地址
static void __meminit sparse_init_one_section(struct mem_section *ms,
                unsigned long pnum, struct page *mem_map,
                struct mem_section_usage *usage, unsigned long flags)
{
        ms->section_mem_map &= ~SECTION_MAP_MASK;
        ms->section_mem_map |= sparse_encode_mem_map(mem_map, pnum)
                | SECTION_HAS_MEM_MAP | flags;
        ms->usage = usage;
}

参考文档

Physical Memory Model
https://lwn.net/Articles/789304/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值