Linux内存管理 | 四、物理地址空间设计模型 #
前面几篇文章,主要讲解了虚拟内存空间的布局和管理,下面同步来聊聊物理内存空间的布局和管理。
1、物理内存 #
什么是物理内存?
我们平时聊的内存,也叫随机访问存储器(random-access memory),也叫RAM。
RAM分为两类:
- SRAM:静态- RAM,其主要用于- CPU高速缓存- L1Cache,- L2Cache,- L3Cache,其特点是访问速度快,访问速度为 1 - 30 个时钟周期,但是容量小,造价高。

- DRAM:动态- RAM,其主要用于我们常说的主存上,其特点的是访问速度慢(相对高速缓存),访问速度为 50 - 200 个时钟周期,但是容量大,造价便宜些(相对高速缓存)。

DRAM经过组合起来,就作为我们的计算机内存,也是物理内存。
2、物理内存访问模型 #
上面介绍了物理内存的基本组成,那么CPU是如何访问物理内存的呢?
对于CPU访问物理内存,Linux提供了两种架构:UMA(Uniform Memory Access)一致内存访问,NUMA(Non-Uniform Memory Access)非一致内存访问。
2.1 UMA #
在UMA架构下,多核处理器中的多个CPU,位于总线的一侧,所有的内存条组成的物理内存位于总线的另一侧。
所有的CPU访问内存都要经过总线,并且距离都是一样的,所以在UMA架构下,所有CPU具有相同的访问特性,即对内存的访问具有相同的速度。

2.2 NUMA #
这种架构,系统中的各个处理器都有本地内存,处理器与处理器之间也通过总线连接,以便于其他处理器对本地内存的访问。
与UMA不同的是,处理器访问本地内存的速度要快于对其他处理器本地内存的访问。

3、物理内存组织模型 #
内存页是物理内存管理中最小单位,有时也成为页帧(Page Frame)。
内核对物理内存划分为一页一页的连续的内存块,每页大小4KB,并且使用struct page结构体来表示页结构,其中封装了每个页的状态信息,包括:组织结构,使用信息,统计信息等。
page结构体较为复杂,我们后续再深入了解。
更多干货可见:高级工程师聚集地,助力大家更上一层楼!
3.1 FLATMEM平坦内存模型 #
FLATMEM即:flat memory model。
我们把物理内存想象成它是由连续的一页一页的块组成的,我们从0开始对物理页编号,这样每个物理页都会有页号。
由于物理地址是连续的,页也是连续的,每个页大小也是一样的。因而对于任何一个地址,只要直接除一下每页的大小,很容易直接算出在哪一页。
如果是这样,整个物理内存的布局就非常简单、易管理,这就是最经典的平坦内存模型(Flat Memory Model)。

如上图,平坦内存模型中,内核使用一个mem_map的全局数组,来组织所有划分出来的物理内存页,下标由PFN表示。
在平坦内存模型下 ,page_to_pfn 与 pfn_to_page 的计算逻辑就非常简单,本质就是基于 mem_map 数组进行偏移操作。
#ifndef ARCH_PFN_OFFSET
#define ARCH_PFN_OFFSET		(0UL)
#endif
#if defined(CONFIG_FLATMEM)
#define __pfn_to_page(pfn) (mem_map + ((pfn)-ARCH_PFN_OFFSET))
#define __page_to_pfn(page) ((unsigned long)((page)-mem_map) + ARCH_PFN_OFFSET)
#endif
ARCH_PFN_OFFSET是PFN的起始偏移量。
3.2 DISCONTIGMEM 不连续内存模型 #
DISCONTIGMEM即:discontiguous memory model。
我们早期内核使用的是FLATMEM模型,该模型对于较小的,连续的物理空间是方便使用的,但是当物理内存不连续时,使用mem_map管理,就会出现空洞,这会浪费mem_map数组本身占用的内存空间。

对于NUMA访问内存模型,物理内存分布就是不连续的,为了有效管理,DISCONTIGMEM 不连续内存模型出现了。

在不连续的物理内存中,DISCONTIGMEM不连续内存模型,将物理内存分成了一个个的node,然后每个node管理一块连续的物理内存,连续的物理内存仍然使用FLATMEM平坦内存模型来管理,从而避免了内存空洞的浪费。
我们可以看出
DISCONTIGMEM非连续内存模型其实就是FLATMEM平坦内存模型的一种扩展。
DISCONTIGMEM是个稍纵即逝的内存模型,在SPARSEMEM出现后即被完全替代。
3.3 SPARSEMEM稀疏内存模型 #
随着内存技术的发展,内核可以支持物理内存的热插拔了(像我们的内存条,可以直接插入拔出),这样不连续物理内存已然称为常态。
SPARSEMEM稀疏内存模型的核心思想就是对粒度更小的连续内存块进行精细的管理,用于管理连续内存块的单元被称作 section 。
物理页大小为
4k的情况下,section的大小为128M,物理页大小为16k的情况下,section的大小为512M。
在内核中,使用struct mem_section结构体表示SPARSEMEM模型中的section
struct mem_section {
	unsigned long section_mem_map;
        ...
}
- 每个 - mem_section管理一片小的,物理内存连续的区域,并且支持对该区域的- offline/online状态
- 所有的 - mem_section都保存在一个全局数组中
整体的框架如下:

在 SPARSEMEM 稀疏内存模型下 page_to_pfn 与 pfn_to_page 的计算逻辑又发生了变化。
#if defined(CONFIG_SPARSEMEM)
/*
 * Note: section's mem_map is encoded to reflect its start_pfn.
 * section[i].section_mem_map == mem_map's address - start_pfn;
 */
#define __page_to_pfn(pg)					\
({	const struct page *__pg = (pg);				\
	int __sec = page_to_section(__pg);			\
	(unsigned long)(__pg - __section_mem_map_addr(__nr_to_section(__sec)));	\
})
#define __pfn_to_page(pfn)				\
({	unsigned long __pfn = (pfn);			\
	struct mem_section *__sec = __pfn_to_section(__pfn);	\
	__section_mem_map_addr(__sec) + __pfn;		\
})
#endif
- 在 page_to_pfn的转换中,首先需要通过page_to_section根据struct page结构定位到mem_section数组中具体的section结构。然后在通过section_mem_map定位到具体的PFN。
- 在 pfn_to_page的转换中,首先需要通过__pfn_to_section根据PFN定位到mem_section数组中具体的section结构。然后在通过PFN在section_mem_map数组中定位到具体的物理页Page。
4、总结 #
以上,我们先对物理内存空间有一个基础的了解,明白物理内存空间的内存访问模型和组织模型,下面我们再详细介绍物理内存空间的布局和管理。
