系統中的物理頁框在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的。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM