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