一、简介
Linux中的物理内存被按页框划分,每个页框都会对应一个 struct page 结构体存放元数据,也就是说每块页框大小的内存都要花费 sizeof(struct page) 个字节进行管理。
因此系统会有大量的 struct page,在linux的历史上出现过三种内存模型去管理它们。依次是平坦内存模型(flat memory model)、不连续内存模型 (discontiguous memory model)和稀疏内存模型(sparse memory model)。新的内存模型被提出是因为老的内存模型已不适应计算机硬件的新技术(例如:NUMA技术、内存热插拔等)。
内存模型的设计主要是权衡以下两点(空间与时间):
(1) 消耗尽量少的内存去管理众多的 struct page 数据结构。
(2) pfn_to_page() 和 page_to_pfn() 的转换效率要尽量高。
二、三种内存模型
1. FLATMEM (flat memory model)
FLATMEM 内存模型是Linux最早使用的内存模型,那时计算机的内存通常不大。Linux会使用一个 struct page mem_map[x] 的数组,以PFN为下标去依次存放所有的 strcut page 结构,且 mem_map 也位于内核空间的线性映射区,所以根据PFN(页帧号)即可轻松的找到页帧对应的 strcut page 结构。
配置选项 CONFIG_FLATMEM,没有.c实现文件。
对于物理地址空间不存在空洞(holes)的计算机来说,FLATMEM 模型无疑是最优解。可物理地址中若是存在空洞的话,FLATMEM 就显得格外的浪费内存,#########因为 FLATMEM 不得不在 mem_map[] 数组中为所有的物理地址都创建一个 struct page,即使大块的物理地址是空洞,即不存在物理内存,也要创建。
2. DISCONTIGMEM (discontiguous memory model)
为了解决不连续物理内存带来的空洞问题,Linux社区提出了 DISCONTIGMEM 模型。
DISCONTIGMEM 模型引入了内存节点的概念,这仍然是 NUMA 内存管理的基础。每个节点都带有一个独立的内存管理子系统,它有自己的空闲页面列表、正在使用的页面列表、最近最少使用 (LRU) 信息和使用情况统计信息。在所有这些好东西中,由 struct pglist_data 表示的节点数据包含特定于节点的内存映射。假设每个节点都有连续的物理内存,每个节点都有一个页面结构数组可以解决平面内存映射中存在大漏洞的问题。
配置选项 CONFIG_DISCONTIGMEM,没有.c实现文件。
然而,DISCONTIGMEM 有一个弱点:内存热插拔和热移除。实际的 NUMA 节点粒度太粗,无法正确支持热插拔,而拆分节点会产生大量不必要的碎片和开销。请记住,每个节点都有一个独立的内存管理结构以及所有相关成本,进一步拆分节点会大大增加这些成本。
DISCONTIGMEM 稍纵即逝,倍后起之秀 SPARSEMEM 完全替代。
3. SPARSEMEM (sparse memory model)
SPARSEMEM 的引入解决了这一限制。该模型将内存映射抽象为由架构定义的任意大小的部分集合。每个部分由 struct mem_section 表示,里面包含指向 struct page 数组的指针。这些部分的数组表示一段物理内存,可以高效地以 SECTION_SIZE 粒度进行切割。为了在 PFN 和 struct page 之间进行高效地效转换,PFN 的几个高位用于索引部分数组。对于另一个方向,部分编号被编码在页面标志中。
SPARSEMEM 将 PFN 拆分成了三个level,每个level分别对应:ROOT编号(二维数组下标)、ROOT内的section偏移(一维数组下标)、section内的page偏移。(可以类比多级页表来理解)
在 SPARSEMEM 引入 Linux 内核几个月后,被扩展为 SPARSEMEM_EXTREME,它适用于具有特别稀疏的物理地址空间的系统。SPARSEMEM_EXTREME 为 sections 数组添加了第二个维度(默认使能),变成一个二维数组。使用 SPARSEMEM_EXTREME 后,第一级变成指向 mem_section 结构数组的指针,并且根据实际物理内存动态分配。
2007 年,SPARSEMEM 又增加了一项增强功能,称为 "generic virtual memmap support for SPARSEMEM"(对SPARSEMEM 增加虚拟内存映射),配置宏为 SPARSEMEM_VMEMMAP。SPARSEMEM_VMEMMAP 背后的理念是,整个内存映射被映射到一个几乎连续的区域,但只有active部分才有物理页面支持。此模型不适用于 32 位系统,因为 32 位系统的物理内存大小可能接近甚至超过虚拟地址空间。但是,对于 64 位系统,SPARSEMEM_VMEMMAP 显然是一个优势。以额外的页表项为代价,page_to_pfn() 和 pfn_to_page() 变得与平面模型一样简单。
#if defined(CONFIG_FLATMEM) //没使能 #define __pfn_to_page(pfn) ... #elif defined(CONFIG_DISCONTIGMEM) //没使能 #define __pfn_to_page(pfn) ... #elif defined(CONFIG_SPARSEMEM_VMEMMAP) //默认使能,且优先级高 #define __pfn_to_page(pfn) ... #elif defined(CONFIG_SPARSEMEM) //默认使能 #define __page_to_pfn(pg) ... #endif
SPARSEMEM 内存模型的最后一个扩展是较新的(2016 年);它是由持久内存设备的使用增加所推动的。为了支持将内存映射直接存储在这些设备上而不是主内存中,虚拟内存映射可以使用 struct vmem_altmap,####### 它将在持久内存中提供页面结构。
2008 年,SPARSEMEM_VMEMMAP 成为 x86-64 唯一支持的内存模型,因为它仅比 FLATMEM 稍微重一点,但比 DISCONTIGMEM 更高效。最近的内存管理开发(例如内存热插拔、持久内存支持和各种性能优化)都针对 SPARSEMEM 模型。SPARSEMEM 模式也是较新Linux内核默认支持的一种内存模型。
SPARSEMEM 需要使能的配置宏和.c实现有:
CONFIG_SPARSEMEM_VMEMMAP CONFIG_SPARSEMEM_VMEMMAP_ENABLE CONFIG_SPARSEMEM CONFIG_SPARSEMEM_MANUAL CONFIG_SPARSEMEM_EXTREME CONFIG_ARCH_SPARSEMEM_DEFAULT msm-5.4/mm$ find ./ -name Makefile | xargs grep sparse ./Makefile:obj-$(CONFIG_SPARSEMEM) += sparse.o ./Makefile:obj-$(CONFIG_SPARSEMEM_VMEMMAP) += sparse-vmemmap.o
三、数据结构
1. struct mem_section
SPARSEMEM 内存模型引入了section的概念,可以简单将它理解为 struct page 的集合(数组)。内核使用 struct mem_section 去描述section,定义如下:
//include/linux/mmzone.h struct mem_section { unsigned long section_mem_map; struct mem_section_usage *usage; #ifdef CONFIG_PAGE_EXTENSION struct page_ext *page_ext; unsigned long pad; #endif };
sparse_init_one_section() 中对其进行初始化。
section_mem_map: 存放的是 struct page 数组的地址,每个section可容纳 1<<PFN_SECTION_SHIFT 个 struct page. Arm64物理地址位宽为48bit时定义了每个section可囊括的地址范围是1GB(2^18个页帧,共1GB内存)。
usage:
page_ext:
pad:
2. struct mem_section_usage
struct mem_section_usage { DECLARE_BITMAP(subsection_map, SUBSECTIONS_PER_SECTION); //1<<9 /* See declaration of similar field in struct zone */ unsigned long pageblock_flags[0]; };
3. struct page_ext
struct page_ext { unsigned long flags; };
四、全局变量
1. mem_section
内核中用 struct mem_section **mem_section 这个二级指针去管理section,我们可以简单理解为一个动态的二维数组。所谓二维即内核又将 SECTIONS_PER_ROOT 个section划分为一个ROOT,ROOT的个数不是固定的,根据系统实际的物理地址大小来分配。
2. section_to_node_table
static u8 section_to_node_table[NR_MEM_SECTIONS]; //1<<18
五、vmemmap 区域
vmemmap 区域是一块起始地址是 VMEMMAP_START,范围是4GB的虚拟地址空间,位于kernel space。以section为单位来存放 strcut page 结构的虚拟地址空间,然后线性映射到物理内存。
//arch/arm64/include/asm/memory.h #define VMEMMAP_START (-VMEMMAP_SIZE - SZ_2M) //0xfffffffeffe00000 //(0xffffffc000000000 - 0xffffff8000000000) >> (12 - 6) = 0x100000000 = 4G #define VMEMMAP_SIZE ((_PAGE_END(VA_BITS_MIN) - PAGE_OFFSET) >> (PAGE_SHIFT - STRUCT_PAGE_MAX_SHIFT)) //启动log打印: vmemmap : 0xfffffffeffe00000 - 0xffffffffffe00000 ( 4 GB maximum)
若是有16GB的物理内存,则需要 2^34 / 2^12 * 2^6 = 256MB 的空间存储page数据结构。
参考:
Memory: the flat, the discontiguous, and the sparse:https://lwn.net/Articles/789304/
标签:struct,mem,18,section,SPARSEMEM,sparsemem,内存,page From: https://www.cnblogs.com/hellokitty2/p/18299512