3.2.3 物理内存模型
前面提到,系统管理物理内存的基本单位是页(Page),物理内存被 划分成一个个大小固定的页帧(Page Frame),每个页帧都有一个唯一的编号,叫着PFN(Page Frame Number); 内核中使用数据结构
page
来封装每个物理页帧的状态,因此物理页帧与 page
之间存在着1:1的关系。系统必须提供两者之间相互转换的方式,即系统可以通过PFN 拿到对应的 page
, 也可以通过 page
得到对应的 PFN, 内核中提供了两个宏来完成该转换,分别是 page_to_pfn
与 pfn_to_page
。如何组织内存页帧,使得这种转换能够高效便捷地实现,是内核设计者需要解决的问题。我们将组织管理物理页帧的方式叫物理内存模型,内存模型的结构与物理内存本身的结构息息相关,本节我们将简要介绍一下内核物理内存模型的演进过程,以及每个内存模型所适用的场景。
理想情况下,物理内存是一块地址连续的存储空间,这样物理页帧的PFN也是连续的,因此最简单直接地方式是将所有
page
放在一个一维数组中,每个 page
的索引就是对应物理页帧的PFN. 这种内存模型叫着平坦内存模型(Flat Memory Model),Linux早期使用的就是这种内存模型,所有的 page
保存在全局变量 mem_map
中,PFN 与 page
相互转换的逻辑实现也很直接,参考如下代码:/* file: include/asm-generic/memory_model.h */
#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
是页帧号的起始偏移量,总体来说两个宏的逻辑都是基于 mem_map
做地址运算,效率非常高。FLATMEM很适合用来管理连续的物理内存,但对于内存不连续的情况就不太友好,如果物理内存存在大块的不连续区间,由于
mem_map
使用PFN作为 page
的索引,那么这些不连续区间对应的PFN就也会占用 mem_map
中的位置,形成空洞(hole),造成大量的内存空间浪费。另外就是在NUMA架构下,每个 Node 都有自己单独的内存区域,使用全局的变量来追踪所有的物理内存也不合理。为了解决该问题,内核提供了新的内存模型叫着
DISCONTIGMEM
, 意在消除内存空洞对 mem_map
的资源浪费。为了简化,内核在实现时仅根据 NUMA 的内存节点进行了划分,依然将每个 node 的内存看着是连续的,FLATMEM 中的全局变量 mem_map
变成了 pglist_data
中的一个变量:/* file: include/linux/mmzone.h */
typedef struct pglist_data {
#ifdef CONFIG_FLAT_NODE_MEM_MAP /* means !SPARSEMEM */
struct page *node_mem_map;
#endif
该模型实际上是 FLATMEM 的扩展,PFN与
page
之间的转换比FLATMEM多了一步:通过PFN或者 page
确定所在的node. 其余逻辑就与FLATMEM 一样了。具体代码如下:/* file: include/asm-generic/memory_model.h */
#if defined(CONFIG_DISCONTIGMEM)
#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); \
})
#define __page_to_pfn(pg) \
({ \
const struct page *__pg = (pg); \
struct pglist_data *__pgdat = NODE_DATA(page_to_nid(__pg)); \
(unsigned long)(__pg - __pgdat->node_mem_map) + \
__pgdat->node_start_pfn; \
})
#endif
DISCONTIGMEM 的本意是应对非连续的物理内存,但其又将NUMA架构下的每个 node 看着是连续的,这其实并不合理,特别是在支持内存热插拔的系统当中。系统需要一种机制能够更灵活地管理粒度更小的连续内存区块,这种内存模型叫着
SPARSEMEM
, 即稀疏内存模型。SPARSEMEM模型的核心思想是将每个连续的内存块都单独管理,每个连续的内存块叫着一个
mem_section
, 该数据结构中的字段 section_mem_map
指向连续的 page
对象,所有的 memsection 存放在一个全局的数组中,并且每个 mem_section
都可以在系统运行时改变 offline/online 状态,以便支持内存的热插拔(hotplug)功能。此时,PFN 与 page 之间的相互转换需要先找到对应的块,然后在通过 section_mem_map
属性进行查找。很明显SPARSEMEM已经完全覆盖了前两个内存模型的所有功能,特别是可以完全替代不够灵活的DISCONTIGMEM.
这里我们仅简单讨论了一下物理内存模型的设计思想,想要深入研究的读者可以参考如下资料:
- https://docs.kernel.org/vm/memory-model.html
- https://lwn.net/Articles/789304/
- http://www.wowotech.net/memory_management/memory_model.html
Last modified 1yr ago