本文參考代碼: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。
- 如果CONFIG_SPARSEMEM_EXTREME=y,則分配SECTION_ROOTS;
- 遍歷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的。