Linux深入理解内存管理17(基于Linux6.6)---内存模型介绍
一、概述
前面已经分析把物理内存添加到memblock以及给物理内存建立页表映射,这里分析内存模型。在Linux内核中支持3种内存模型,分别为:
- flat memory model
- Discontiguous memory model
- sparse memory model
所谓memory model,其实就是从cpu的角度看,其物理内存的分布情况,在linux kernel中,使用什么的方式来管理这些物理内存。某些体系架构支持多种内存模型,但在内核编译构建时只能选择使用一种内存模型。
1. Flat Memory Model(平坦内存模型)
在平坦内存模型中,系统的物理内存被视为一个连续的内存空间,所有内存地址是线性排列的,没有内存分段或区间的划分。换句话说,操作系统的虚拟地址空间和物理地址空间之间的映射是直接的,整个内存区域是连续的。
特点:
- 简单:这种模型非常简单,内存管理的实现不复杂。
- 高效:由于物理内存的地址是线性连续的,系统可以直接访问内存,减少了复杂的地址转换。
- 适用于小型系统:通常应用于内存较小、硬件资源有限的架构。
适用场景:
- 较小的嵌入式系统或早期的架构(如 x86 中的一些较旧的硬件)。
2. Discontiguous Memory Model(不连续内存模型)
不连续内存模型下,系统的物理内存并不是一个连续的块,而是被分成多个离散的区域。这种内存模型能够更灵活地利用硬件中的内存资源,特别是对于具有多个内存区域的系统(如有不同类型内存的系统,或存在内存空洞的系统)非常有用。
特点:
- 内存区域不连续:物理内存不再是一个线性空间,可能会存在内存的空洞或多个不连续的内存区域。
- 更高的灵活性:可以支持具有多个不同内存类型或区域的系统(如 NUMA 架构、多级缓存系统等)。
- 复杂的内存管理:操作系统需要维护多个内存区域的映射,并在分配内存时处理不连续的内存区域。
适用场景:
- 适用于具有多个内存区域的硬件架构,比如某些高性能计算机、带有不同内存池(如快速内存和慢速内存)的系统。
3. Sparse Memory Model(稀疏内存模型)
稀疏内存模型是一种更复杂且更灵活的内存管理模型,它使用稀疏数组(sparse array)来映射物理内存。在稀疏内存模型中,物理内存的地址空间通常是非连续的,并且仅在必要时进行内存分配。稀疏内存模型的关键优势是可以显著减少内存管理的开销,尤其是在大规模的系统中。
特点:
- 内存映射是稀疏的:并非所有物理地址都对应实际的物理内存空间,只有实际使用的内存区域才会被映射。
- 灵活的内存分配:稀疏内存模型能够高效地处理大规模内存系统,特别适用于具有非常大的内存空间且不需要连续内存分配的系统。
- 高效的资源利用:通过稀疏分配,可以减少系统内存的浪费,避免对不连续的内存区域的复杂处理。
适用场景:
- 适用于大内存系统(如高性能计算机、大型服务器或具有大量内存的系统),特别是需要处理极其复杂的内存分配模式或稀疏分配需求时。
二、基本概念
2.1、page frame
从虚拟地址到物理地址的映射过程,系统对于内存管理是以页为单位进行管理的。在linux操作系统中,物理内存是按照page size来管理的,具体page size是多少是和硬件以及linux系统配置相关的,4k是最经典的设定。因此,对于物理内存,我们将其分成一个个按page size排列的page,每一个物理内存中的page size的内存区域我们称之page frame。page frame是系统内存的最小单位,对内存中的每个页都会创建struct page实例。
2.2、PFN
对于一个计算机系统,其整个物理地址空间应该是从0开始,到实际系统能支持的最大物理空间为止的一段地址空间。在ARM系统中,假设物理地址是32个bit,那么其物理地址空间就是4G,在ARM64系统中,如果支持的物理地址bit数目是48个,那么其物理地址空间就是256T。当然,实际上这么大的物理地址空间并不是都用于内存,有些也属于I/O空间(当然,有些cpu arch有自己独立的io address space)。因此,内存所占据的物理地址空间应该是一个有限的区间,不可能覆盖整个物理地址空间。
PFN是page frame number的缩写,所谓page frame,就是针对物理内存而言的,把物理内存分成一个个固定长度为page size的区域,并且给每一个page 编号,这个号码就是PFN。与page frame的转换关系如下图所示。
2.3、NUMA
在多核的系统设计中内存的架构有两种类型计算机,分别以不同的方式管理物理内存。
UMA计算机(一致内存访问,uniform memory access):将可用内存以连续方式组织起来,系统中所有的处理器共享一个统一的,一致的物理内存空间,无论从哪个处理器发起访问,对内存的访问时间都是一样快。其架构图如下图所示:
NUMA计算机(非一致内存访问,non-uniform memory access):每个 CPU 都有自己的本地内存,CPU 访问本地内存不用过总线,因而速度要快很多,每个 CPU 和内存在一起,称为一个 NUMA 节点。但是,在本地内存不足的情况下,每个 CPU 都可以去另外的 NUMA 节点申请内存,这个时候访问延时就会比较长。
从图中可以看出,每个CPU访问local memory,速度更快,延迟更小。当然,整体的内存构成一个内存池,CPU也能访问remote memory,相对来说速度更慢,延迟更大。目前对NUMA的了解仅限于此,在内核中会遇到相关的代码,大概知道属于什么范畴就可以了。
三、Linux内存模型
Linux提供了三种内存模型(include/asm-generic/memory_model.h),一般处理器架构支持一种或者多种内存模型,这个在编译阶段就已经确定,比如目前在ARM64中,使用的Sparse Memory Model。
3.1、FLAT memory model(平坦内存模型)
如果从系统中任意一个CPU的角度来看,当它访问物理内存的时候,物理地址空间是一个连续的,没有空洞的地址空间,那么这种计算机系统的内存模型就是Flat memory。
早期的系统物理内存不大,那个时候Linux使用平坦内存模型(flat memory model)来管理物理内存就足够有效了。一个page frame用一个struct page结构体表示,整个物理内存可以用一个由所有struct page构成的数组mem_map表示,而经过页表查找得到的PFN,正好可以用来做这个数组的小标,__pfn_to_page()函数就是专门来完成这个功能的。
#define __pfn_to_page(pfn) (mem_map + ((pfn) - ARCH_PFN_OFFSET))
对于FLATMEM来说,物理内存本身是连续的,如果不连续的话,那么中间一部分物理地址是没有对应的物理内存,就会形成一个个洞,这就浪费了mem_map数组本身占用的内存空间。对于这种模型,其特点如下:
- 内存连续且不存在空隙。
- 这种在大多数情况下,应用于UMA系统“Uniform Memory Access”。
- 通过CONFIG_FLATMEM配置。
3.2、discontiguous memory model (不连续内存模型)
如果CPU在访问物理内存的时候,其地址空间是有一些空洞的,是不连续的,那么这种计算机系统的内存模型就是Discontiguous memory。在什么情况下物理内存是不连续的呢?当NUMA出现后,为了有效的管理NUMA模式的物理内存,一种被称为不连续内存模型的实现于1999年被引入linux系统中。在这中模型中,NUMA中的每个Node用一个叫做pglist_data的结构体表示。
应对不连续物理内存的问题似乎是解决了,可是现在你给我一个物理page的地址,使用DISCONTIGMEM的话,我怎么知道这个page是属于哪个node的呢,PFN中可没有包含node编号啊。pfn_to_page()之前干的活多轻松啊,就是索引下数组就得到数组元素struct page了,现在PFN和page之间的对应关系不是那么直接了,pfn_to_page的任务就开始重起来了
#define __pfn_to_page(pfn) \
({ unsigned long __pfn = (pfn); \
unsigned long __nid = arch_pfn_to_nid(__pfn); \
NODE_DATA(__nid)->node_mem_map + arch_local_page_offset(__pfn, __nid);\
})
物理内存存在空洞,随着Sparse Memory的提出,这种内存模型也逐渐被弃用了。这种内存模型有以下的特点
- 多个内存节点不连续并且存在空隙"hole"。
- 适用于UMA系统和NUMA系统。
- ARM在2010年已经移除了对DISCONTIGMEM的支持。
- 通过CONFIG_CONTIGMEM配置。
3.3、sparse memory model(稀疏内存模型)
内存模型是一个逐渐演化的过程,刚开始的时候,由于内存比较小,使用flat memory模型去抽象一个连续的内存地址空间。但是出现了NUMA架构之后,整个不连续的内存空间被分配成若干个node,每个node上是连续的内存地址空间,为了有效的管理NUMA模型下的物理内存,就开始使用discontiguous memory model。为了解决DISCONTIGMEM存在的弊端,一种新的稀疏内存模型被使用出来。
在sparse memory内存模型下,连续的地址空间按照SECTION被分成一段一段的,其中每一个section都是Hotplug的,因此sparse memory下,内存地址空间可以被切分的更细,支持更离散的Discontiguous memory。在SPARSEMEM中,被管理的物理内存由一个个任意大小的section(struct mem_section表示)构成,因此整个物理内存可被视为一个mem_section数组。每个mem_section包含了一个间接指向struct page数组的指针。
其主要的特点如下:
- 多个内存区域不连续并且存在空隙。
- 支持内存热插拔(hot plug memory),但性能稍逊色于DISCONTIGMEM。
- 在x86或ARM64内存采用该中模型,其性能比DISCONTIGMEM更优并且与FLATMEM相当。
- 对于ARM64平台默认选择该内存模型。
- 以section为单位管理online和hot-plug内存。
- 通过CONFIG_SPARSEMEM配置。
section大小从几十MiB到几GiB不等,取决于体系架构和内核的配置。通常在系统配置中将内存扩展单元「memory expansion unit」用作section大小。比如,如果系统内存可扩展至64GiB,并且最小内存扩展单元为1GiB,则设置section大小也为1GiB。当使用Linux系统作为hypervisor的客户操作系统「guest OS」,也是以section大小为单元在运行时向Linux系统增添内存和移除Linux系统的内存。
四、平台内存模型支持
Linux支持的各种不同体系结构在内存管理方面差别很大,以下是主流的架构支持情况如下表所示,一个体系架构中可能有多种内存模型可用(ARM64只支持一种内存模型),通过可选的内核配置选项来决定使用哪种内存模型。
架构 | 支持的内存模型 | 相关内核配置选项 |
---|---|---|
ARM64 | 稀疏内存模型 (Sparse Memory Model) | 无特别配置选项,默认启用稀疏内存模型 |
x86_64 | 平坦内存模型 (Flat Memory Model) | CONFIG_FLAT_MEMORY=y |
不连续内存模型 (Discontiguous Memory Model) | CONFIG_DISCONTIGMEM=y | |
稀疏内存模型 (Sparse Memory Model) | CONFIG_SPARSEMEM=y | |
PowerPC | 稀疏内存模型 (Sparse Memory Model) | CONFIG_SPARSEMEM=y |
不连续内存模型 (Discontiguous Memory Model) | CONFIG_DISCONTIGMEM=y | |
MIPS | 平坦内存模型 (Flat Memory Model) | CONFIG_FLAT_MEMORY=y |
不连续内存模型 (Discontiguous Memory Model) | CONFIG_DISCONTIGMEM=y | |
稀疏内存模型 (Sparse Memory Model) | CONFIG_SPARSEMEM=y | |
SPARC | 稀疏内存模型 (Sparse Memory Model) | CONFIG_SPARSEMEM=y |
不连续内存模型 (Discontiguous Memory Model) | CONFIG_DISCONTIGMEM=y |
五、总结
内存模型 | 描述 | 适用场景 | 相关配置选项 |
---|---|---|---|
平坦内存模型 | 所有物理内存被视为一个连续的地址空间,内核将物理内存映射为一块连续的虚拟内存地址。 | 适用于简单的硬件架构,内存较小且不需要复杂的内存管理。 | CONFIG_FLAT_MEMORY=y |
不连续内存模型 | 物理内存分为多个区域,这些区域在虚拟地址空间中可以不连续映射。 | 适用于具有多个 NUMA 节点、内存池或需要优化内存分配的复杂系统。 | CONFIG_DISCONTIGMEM=y |
稀疏内存模型 | 物理内存的分配和管理不再是一个连续的地址空间,而是通过一个稀疏的映射来管理内存。稀疏内存模型适用于大内存系统,内存区域可能是离散的。 | 适用于高端服务器、大规模内存系统,优化内存碎片管理。 | CONFIG_SPARSEMEM=y |
Linux 内核通过对不同内存模型的支持,能够根据硬件架构与应用场景的不同,动态选择最合适的内存管理方式。常见的内存管理策略包括:
-
内存分配:
内核根据选择的内存模型,使用不同的算法和数据结构来分配和管理内存。例如,平坦内存模型使用简单的线性映射,而稀疏内存模型则使用基于页表的映射结构。 -
内存碎片管理:
在内存管理中,内存碎片问题尤为突出。稀疏内存模型通过将物理内存分为多个不连续的区域来减少内存碎片。 -
NUMA(非统一内存访问)优化:
NUMA 架构的系统有多个内存节点,每个节点的访问速度不同。内核通过优化内存分配与调度策略,减少跨节点内存访问,从而提升系统性能。 -
内存回收机制:
内核通过页面回收机制(如 页交换、LRU(Least Recently Used)算法 等)来管理空闲内存,提高内存利用率,并减少内存不足时的性能影响。