Linux 内存管理:Sparse 内存模型简述

发布时间:2026/5/21 17:35:42

Linux 内存管理:Sparse 内存模型简述 文章目录1. 前言2. Sparse 内存模型1. 前言限于作者能力水平本文可能存在谬误因此而给读者带来的损失作者不做任何承诺。2. Sparse 内存模型笔者在 Linux 内存管理 (4)buddy 管理系统的建立 分析了不连续内存模型(Discontiguous Memory Model)下最终建立内存管理数据图如下本文分析Sparse 内存模型(CONFIG_SPARSEMEMy)是基于前面的博文只理出不连续内存模型(Discontiguous Memory Model)和Sparse 内存模型的差异部分两者相同的部分可参考前面提到的博文。不连续内存模型(Discontiguous Memory Model)和Sparse 内存模型的差异在于物理页面管理数据(即 memmap)结构不同。非Sparse 内存模型下也包括不连续内存模型(Discontiguous Memory Model)物理页面管理数据(即 memmap)不再来自pglist_data::node_mem_map// include/linux/mmzone.htypedefstructpglist_data{...#ifdefCONFIG_FLAT_NODE_MEM_MAP/* means !SPARSEMEM *//* * NUMA 节点 node_id 实际 起始物理页面 的 struct page : * NUMA 节点 的每物理页面管理数据 struct page, 是按将 * NUMA 节点 起始物理页面页框号 node_start_pfn 向下对齐到 * (1 MAX_ORDER) 后进行分配的, 也就是说, 为 NUMA 节点 分配 * 的每物理页面管理数据 struct page , 头部有一些是为对齐到 (1 MAX_ORDER) * 页面数 而分配的多余无用的 struct page , 这时候用 node_mem_map * 指向实际的 NUMA 节点 第1个起始物理页面 (node_start_pfn) 的 struct page . */structpage*node_mem_map;#ifdefCONFIG_PAGE_EXTENSIONstructpage_ext*node_page_ext;#endif#endif/* CONFIG_FLAT_NODE_MEM_MAP */...};而是来自mem_section// mm/sparse.cstructmem_section{/* * This is, logically, a pointer to an array of struct * pages. However, it is stored with some other magic. * (see sparse.c::sparse_init_one_section()) * * Additionally during early boot we encode node id of * the location of the section here to guide allocation. * (see sparse.c::memory_present()) * * Making it a UL at least makes someone do a cast * before using it wrong. *//* * 系统启动初期 (early boot) 用来记录 NUMA node id; * 启动结束后被赋予 mem_map 值指向页表管理数据 (struct page) 。 */unsignedlongsection_mem_map;/* See declaration of similar field in struct zone */unsignedlong*pageblock_flags;#ifdefCONFIG_PAGE_EXTENSION/* * If SPARSEMEM, pgdat doesnt have page_ext pointer. We use * section. (see page_ext.h about this.) */structpage_ext*page_ext;unsignedlongpad;#endif/* * WARNING: mem_section must be a power-of-2 in size for the * calculation and use of SECTION_ROOT_MASK to make sense. */};/* * Permanent SPARSEMEM data: * * 1) mem_section - memory sections, mem_maps for valid memory */#ifdefCONFIG_SPARSEMEM_EXTREMEstructmem_section**mem_section;#elsestructmem_sectionmem_section[NR_SECTION_ROOTS][SECTIONS_PER_ROOT]____cacheline_internodealigned_in_smp;#endifEXPORT_SYMBOL(mem_section);mem_section的构建实在系统初始化早期以ARM64 Linux 4.14.111为例start_kernel()setup_arch()/* * 初始化 内核空间页表 (swapper_pg_dir[]): * - 在 swapper_pg_dir[] PGD 页表中重建内核映射 * - 在 swapper_pg_dir[] PGD 页表中建立系统 RAM 映射 */paging_init()...bootmem_init()...arm64_numa_init()/* * Sparsemem tries to allocate bootmem in memory_present(), so must be * done after the fixed reservations. */arm64_memory_present()sparse_init()......// mm/sparse.c/* * Allocate the accumulated non-linear sections, allocate a mem_map * for each and record the physical to section mapping. *//* * Sparse Memory Model 初始化. * 为所有 NUMA 节点内的 present section 分配: * . pageblock flag 管理数据空间 (mem_section::pageblock_flags) */void__initsparse_init(void){unsignedlongpnum;structpage*map;unsignedlong*usemap;unsignedlong**usemap_map;intsize;#ifdefCONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHERintsize2;structpage**map_map;#endif/* see include/linux/mmzone.h struct mem_section definition */BUILD_BUG_ON(!is_power_of_2(sizeof(structmem_section)));/* Setup pageblock_order for HUGETLB_PAGE_SIZE_VARIABLE */set_pageblock_order();/* 设置 HUGE 页面的 page order *//* * map is using big page (aka 2M in x86 64 bit) * usemap is less one page (aka 24 bytes) * so alloc 2M (with 2M align) and 24 bytes in turn will * make next 2M slip to one more 2M later. * then in big system, the memory will have a lot of holes... * here try to allocate 2M pages continuously. * * powerpc need to call sparse_init_one_section right after each * sparse_early_mem_map_alloc, so allocate usemap_map at first. */sizesizeof(unsignedlong*)*NR_MEM_SECTIONS;usemap_mapmemblock_virt_alloc(size,0);if(!usemap_map)panic(can not allocate usemap_map\n);alloc_usemap_and_memmap(sparse_early_usemaps_alloc_node,(void*)usemap_map);/* * CONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER 试图将一个 * NUMA 节点内所有 mem_map 管理数据分配在连续的空间内. */#ifdefCONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHER/* * 分配每 section 的 memmap 指针数组 struct page *map_map[]: * 用来指向各自的 struct page[]. * * 注意, 这里没有分配每 section 的 struct page 数据, * 仅仅是每 section 的 struct page * 指针. */size2sizeof(structpage*)*NR_MEM_SECTIONS;map_mapmemblock_virt_alloc(size2,0);if(!map_map)panic(can not allocate map_map\n);/* 为所有 NUMA 节点内的 present section 分配 mem_map 管理数据(struct page) */alloc_usemap_and_memmap(sparse_early_mem_maps_alloc_node,(void*)map_map);#endif/* * 配置所有 NUMA 节点所有 present section : * . pageblock flag 管理数据空间; * . mem_map 管理数据空间(struct page). */for_each_present_section_nr(0,pnum){usemapusemap_map[pnum];if(!usemap)continue;#ifdefCONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHERmapmap_map[pnum];/* 使用前面提前分配好的 mem_map 管理数据(struct page) */#elsemapsparse_early_mem_map_alloc(pnum);/* 为 section pnum 分配 mem_map 管理数据(struct page) */#endifif(!map)continue;sparse_init_one_section(__nr_to_section(pnum),pnum,map,usemap);}vmemmap_populate_print_last();/* * 释放存放 pageblock flag 和 mem_map 指针的空间: * 这些信息已经记录到 section 的管理数据 struct mem_section 了. */#ifdefCONFIG_SPARSEMEM_ALLOC_MEM_MAP_TOGETHERmemblock_free_early(__pa(map_map),size2);#endifmemblock_free_early(__pa(usemap_map),size);}Sparse 内存模型下最终建立如下图所示的物理页面管理数据(即 memmap)对上图做一下简要解释ARM64 下使用 48-bit 的物理地址(PA)其中 PA[47:12] 划分为 3 部分这 3 部分也划分了 mem_section 对物理页面的 3 级管理section root #: 物理页面所在的 section root 编号section #: 物理页面所在的 section 在对应 section root X 内的编号page #: 物理页面在 section X 内的编号也可以这样来理解sparse 内存模型 memmapsection-root0,[section-root1,[...,[section-root X]]]section-root Xsection0,[section1,[...,[section X]]]section Xpage0,[page1,[...,[page X]]]可见在Sparse 内存模型下memmap 数据不再像非 Sparse 内存模型的其它模式呈现一维数据的“平坦”结构而是呈现了层级树状结构这很符合 Sparse 内存模型的特点毕竟 Sparse 内存模型设计用来管理物理内存物理地址不连续同时结构位置不单一的NUMA内存结构。我们知道memmap 数据的作用是物理页框号 PFN和 关联的struct page数据的相互转换即page_to_pfn()和pfn_to_page()操作看一下 Linux 支持的各种内存模型下这两个宏的定义就可以知道它们之间的差异// include/asm-generic/memory_model.h#ifdefined(CONFIG_FLATMEM)#ifndefARCH_PFN_OFFSET#defineARCH_PFN_OFFSET(0UL)#endif#elifdefined(CONFIG_DISCONTIGMEM)#ifndefarch_pfn_to_nid#definearch_pfn_to_nid(pfn)pfn_to_nid(pfn)#endif#ifndefarch_local_page_offset#definearch_local_page_offset(pfn,nid)\((pfn)-NODE_DATA(nid)-node_start_pfn)#endif#endif/* CONFIG_DISCONTIGMEM *//* * supports 3 memory models. */#ifdefined(CONFIG_FLATMEM)#define__pfn_to_page(pfn)(mem_map((pfn)-ARCH_PFN_OFFSET))/* 返回 页框号 pfn 对应的 struct page *//* 返回 struct page 对应的 物理页面页框号 */#define__page_to_pfn(page)((unsignedlong)((page)-mem_map)\ARCH_PFN_OFFSET)#elifdefined(CONFIG_DISCONTIGMEM)#define__pfn_to_page(pfn)\({unsignedlong__pfn(pfn);\unsignedlong__nidarch_pfn_to_nid(__pfn);\NODE_DATA(__nid)-node_mem_maparch_local_page_offset(__pfn,__nid);\})#define__page_to_pfn(pg)\({conststructpage*__pg(pg);\structpglist_data*__pgdatNODE_DATA(page_to_nid(__pg));\(unsignedlong)(__pg-__pgdat-node_mem_map)\__pgdat-node_start_pfn;\})#elifdefined(CONFIG_SPARSEMEM_VMEMMAP)/* memmap is virtually contiguous. */#define__pfn_to_page(pfn)(vmemmap(pfn))#define__page_to_pfn(page)(unsignedlong)((page)-vmemmap)#elifdefined(CONFIG_SPARSEMEM)/* * Note: sections mem_map is encoded to reflect its start_pfn. * section[i].section_mem_map mem_maps address - start_pfn; */#define__page_to_pfn(pg)\({conststructpage*__pg(pg);\int__secpage_to_section(__pg);\(unsignedlong)(__pg-__section_mem_map_addr(__nr_to_section(__sec)));\})#define__pfn_to_page(pfn)\({unsignedlong__pfn(pfn);\structmem_section*__sec__pfn_to_section(__pfn);\__section_mem_map_addr(__sec)__pfn;\})#endif/* CONFIG_FLATMEM/DISCONTIGMEM/SPARSEMEM *//* * Convert a physical address to a Page Frame Number and back */#define__phys_to_pfn(paddr)PHYS_PFN(paddr)#define__pfn_to_phys(pfn)PFN_PHYS(pfn)/* 页框号 物理地址 */#definepage_to_pfn__page_to_pfn/* struct page 页框号 PFN */#definepfn_to_page__pfn_to_page/* 页框号 PFN struct page */几种内存模型下page_to_pfn()和pfn_to_page()的实现一目了然这里展开下Sparse 内存模型下这两个宏它们处于CONFIG_SPARSEMEM配置分支下。先看下page_to_pfn() - __page_to_pfn()转换struct page 到 PFN的过程staticinlineunsignedlongpage_to_section(conststructpage*page){return(page-flagsSECTIONS_PGSHIFT)SECTIONS_MASK;}#ifdefCONFIG_SPARSEMEM_EXTREME#defineSECTIONS_PER_ROOT(PAGE_SIZE/sizeof(structmem_section))#else#defineSECTIONS_PER_ROOT1#endif#defineSECTION_NR_TO_ROOT(sec)((sec)/SECTIONS_PER_ROOT)/* 计算 section 所在的 ROOT 编号 */#defineNR_SECTION_ROOTSDIV_ROUND_UP(NR_MEM_SECTIONS,SECTIONS_PER_ROOT)#defineSECTION_ROOT_MASK(SECTIONS_PER_ROOT-1)/* 获取编号为 nr 的 section 对象指针. */staticinlinestructmem_section*__nr_to_section(unsignedlongnr){#ifdefCONFIG_SPARSEMEM_EXTREMEif(!mem_section)returnNULL;#endifif(!mem_section[SECTION_NR_TO_ROOT(nr)])returnNULL;returnmem_section[SECTION_NR_TO_ROOT(nr)][nrSECTION_ROOT_MASK];}staticinlinestructpage*__section_mem_map_addr(structmem_section*section){unsignedlongmapsection-section_mem_map;mapSECTION_MAP_MASK;return(structpage*)map;}#define__page_to_pfn(pg)\({conststructpage*__pg(pg);\int__secpage_to_section(__pg);\(unsignedlong)(__pg-__section_mem_map_addr(__nr_to_section(__sec)));\})再看下pfn_to_page() - __pfn_to_page()转换PFN 到 struct page的过程重复的代码部分见上面staticinlineunsignedlongpfn_to_section_nr(unsignedlongpfn){returnpfnPFN_SECTION_SHIFT;}staticinlinestructmem_section*__pfn_to_section(unsignedlongpfn){return__nr_to_section(pfn_to_section_nr(pfn));}#define__pfn_to_page(pfn)\({unsignedlong__pfn(pfn);\structmem_section*__sec__pfn_to_section(__pfn);\__section_mem_map_addr(__sec)__pfn;\})

相关新闻