• 系统中的物理页框在Linux内核中都有struct page与之对应么?


    本文参考代码:Linux-5.10

          要回答这个问题, 根源还是要搞清楚struct page结构是在哪里,如何分配的。

          就当前的Linux而言,几乎都采用的是SPARSEMEM内存模型进行管理。直接一点,struct page的分配就是在sparse_init()这个函数中完成的。

    /*
     * 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();    //【1】
    
            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);   //【2】
                    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()函数,做的事情就是发现系统中所有的有效mem sections。

    1. 如果CONFIG_SPARSEMEM_EXTREME=y,则分配SECTION_ROOTS;
    2. 遍历memblock.memory中各个PFN,找到PFN所对应的mem_section
    •       如果SPARSEMEM_EXTREME=y, 要为没有初始化的mem_section分配内存struct mem_section结构, 并放到SECTION ROOTS数组合适index位置
    •       如果NODE_NOT_IN_PAGE_FLAGS=1,即, 将nodeID放到section_to_node_table[section_nr]中,section_nr是该mem section的id.
    •       将nid、SECTION_IS_ONLINE以及SECTION_MARKED_PRESENT编码到mem_section->section_mem_map中。 

          对于函数memblocks_present()需要强调的一点就是对于 包含有效物理页框(PFN)的section,要设置SECTION_MARKED_PRESENT标志。

          什么是有效物理页框呢?这个名字实际上是我自己起的,直白一点就是所有memblock.memory中的内存。

          那memblock.reserve中的内存呢?对不起,memblock.reserve的物理页框不在memblocks_present()函数的统计范围内。

    【2】对所有设置了SECTION_MARKED_PRESENT标志的section进行初始化

          初始化操作是sparse_init_nid()函数来完成的。

    /*
     * Initialize sparse on a specific node. The node spans [pnum_begin, pnum_end)
     * And number of present sections in this node is map_count.
     */
    static void __init sparse_init_nid(int nid, unsigned long pnum_begin,
                                       unsigned long pnum_end,
                                       unsigned long map_count)
    {
            struct mem_section_usage *usage;
            unsigned long pnum;
            struct page *map;
    
            usage = sparse_early_usemaps_alloc_pgdat_section(NODE_DATA(nid),
                            mem_section_usage_size() * map_count); //【2.1】
            if (!usage) {
                    pr_err("%s: node[%d] usemap allocation failed", __func__, nid);
                    goto failed;
            }
            sparse_buffer_init(map_count * section_map_size(), nid); //【2.2】
            for_each_present_section_nr(pnum_begin, pnum) {
                    unsigned long pfn = section_nr_to_pfn(pnum);
    
                    if (pnum >= pnum_end)
                            break;
    
                    map = __populate_section_memmap(pfn, PAGES_PER_SECTION,    //【2.3】
                                    nid, NULL);
                    if (!map) {
                            pr_err("%s: node[%d] memory map backing failed. Some memory will not be available.",
                                   __func__, nid);
                            pnum_begin = pnum;
                            goto failed;
                    }
                    check_usemap_section_nr(nid, usage);
                    sparse_init_one_section(__nr_to_section(pnum), pnum, map, usage,
                                    SECTION_IS_EARLY);  //【2.4】
                    usage = (void *) usage + mem_section_usage_size();
            }
            sparse_buffer_fini();     //【2.5】
            return;
    failed:
            /* We failed to allocate, mark all the following pnums as not present */
            for_each_present_section_nr(pnum_begin, pnum) {
                    struct mem_section *ms;
    
                    if (pnum >= pnum_end)
                            break;
                    ms = __nr_to_section(pnum);
                    ms->section_mem_map = 0;
            }
    }

          先说这个函数的参数:nid是当前内存节点node id; pnum_begin和pnum_end表示这个内存节点中的起、始section num;map_count表示这个内存节点中有效(是present的)mem sections的个数。

    【2.1】为该内存节点中(map_count个sections)存放BLOCKFLAGS_BITS的标志分配内存,这部分内存放到struct mem_section_usage *usage中;

    【2.2】为该内存节点中(map_count个sections)分配struct page数据结构内存,一个sections分配(sizeof(struct page) * PAGES_PER_SECTION)内存。新分配内存的虚拟起始地址和结束地址分别放到sparsemap_buf和sparsemap_buf_end两个变量中。

          从这个分配算法来看,即使一个section中的物理页框PFN不是连续的,或者说一个section的物理地址是有空洞的,也会为section中的所有可能的PFN分配struct page结构。 

    紧接着遍历该内存节点中的所有presented section:

    【2.3】获取各个section中的”memmap“,也就是这个section中struct pages数组。

          对于经典的SPARSEMEM模型,直接通过取【2.2】中sparsemap_buf地址作为该section的memmap虚拟地址基地址,然后将sparsemap_buf向后推进PAGES_PER_SECTION大小;

          对于SPARSEMEM_VMEMMAP的实现,需要将【2.2】中为struct pages数组分配的物理内存与vmemmap这段虚拟区间建立虚实映射,新建立的映射虚拟地址作为该section的memmap虚拟地址基地址。取struct pages物理内存的过程和经典模型流程相似,也是通过sparsemap_buf获取到虚拟地址先,然后再将sparsemap_buf向后推进PAGES_PER_SECTION大小。

    【2.4】 将【2.3】获取到的memmap基地址编码到mem_sections->section_mem_map中,

          如何编码的呢?其中包含了两类元素:(1) memmap - PFN ,其中PFN是该section的第一个PFN;(2)标志:SECTION_IS_EARLY|SECTION_HAS_MEM_MAP。

    图1 经典SPARSEMEM与SPARSEMEM_VMEMMAP的struct pages数组情况

    总结

          好了,可能扯的有点远了。但是作为知识的补充还是很有必要的。我们再回过头来看看最初的问题:

          系统中的物理页框在Linux内核中都有struct page与之对应么? 我们通过上面的分析来做一个总结。

    •       一个系统根据物理地址位数(一般48bit)可分为若干个mem sections,但是只有包含了有效内存(memblock.memory)的mem sections才会标记为presented
    •       Linux初始化期间分配struct pages的数量是按照presented的mem sections数量来分配的,即只为presented的mem sections分配struct pages
    •       隐含的一个情况,对于一些mem sections可能只包含了memblock.reserve内存的情况是不会为该sections创建struct page的,但是对于既有memblock.memory又有memblock.reserve的情况是会创建的struct pages的。
  • 相关阅读:
    TestNG中DataProvider的用法
    性能调优过程发现的问题
    20170221——接口自动化测试代码提交流程
    svn忽略target文件
    springboot搭建dubbo+zookeeper简单案例
    docker上启动mysql镜像,mysq中记录乱码解决方法
    docker在linux上的安装
    使用jave1.0.2将amr文件转成其他格式报错解决方案
    使用fio命令查看磁盘iops
    解决使用maven clean项目的时候报错,删除target文件夹失败
  • 原文地址:https://www.cnblogs.com/liuhailong0112/p/14594880.html
Copyright © 2020-2023  润新知