温暖的电波  

本文参考代码: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的。

 

posted on 2021-03-31 21:19  温暖的电波  阅读(1180)  评论(0编辑  收藏  举报