# 3.2.3 物理内存模型

前面提到，系统管理物理内存的基本单位是页（Page），物理内存被划分成一个个大小固定的页帧（Page Frame），每个页帧都有一个唯一的编号，叫着PFN(Page Frame Number); 内核中使用数据结构 `page` 来封装每个物理页帧的状态，因此物理页帧与 `page` 之间存在着1:1的关系。系统必须提供两者之间相互转换的方式，即系统可以通过PFN 拿到对应的 `page`, 也可以通过 `page` 得到对应的 PFN, 内核中提供了两个宏来完成该转换，分别是 `page_to_pfn` 与 `pfn_to_page` 。如何组织内存页帧，使得这种转换能够高效便捷地实现，是内核设计者需要解决的问题。

我们将组织管理物理页帧的方式叫物理内存模型，内存模型的结构与物理内存本身的结构息息相关，本节我们将简要介绍一下内核物理内存模型的演进过程，以及每个内存模型所适用的场景。

## 3.2.3.1 FLATMEM <a href="#orgdf37a45" id="orgdf37a45"></a>

理想情况下，物理内存是一块地址连续的存储空间，这样物理页帧的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` 做地址运算，效率非常高。

## 3.2.3.2 DISCONTIGMEM <a href="#org3c6fbec" id="org3c6fbec"></a>

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
```

## 3.2.3.3 SPARSEMEM <a href="#org8c32168" id="org8c32168"></a>

DISCONTIGMEM 的本意是应对非连续的物理内存，但其又将NUMA架构下的每个 node 看着是连续的，这其实并不合理，特别是在支持内存热插拔的系统当中。系统需要一种机制能够更灵活地管理粒度更小的连续内存区块，这种内存模型叫着 `SPARSEMEM`, 即稀疏内存模型。

SPARSEMEM模型的核心思想是将每个连续的内存块都单独管理，每个连续的内存块叫着一个 `mem_section`, 该数据结构中的字段 `section_mem_map` 指向连续的 `page` 对象，所有的 memsection 存放在一个全局的数组中，并且每个 `mem_section` 都可以在系统运行时改变 offline/online 状态，以便支持内存的热插拔（hotplug）功能。此时，PFN 与 page 之间的相互转换需要先找到对应的块，然后在通过 `section_mem_map` 属性进行查找。

很明显SPARSEMEM已经完全覆盖了前两个内存模型的所有功能，特别是可以完全替代不够灵活的DISCONTIGMEM.

## 3.2.3.4 Resources

这里我们仅简单讨论了一下物理内存模型的设计思想，想要深入研究的读者可以参考如下资料：

* <https://docs.kernel.org/vm/memory-model.html>
* <https://lwn.net/Articles/789304/>
* <http://www.wowotech.net/memory\\_management/memory\\_model.html>


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://s3.shizhz.me/linux-mm/3.2-wu-li-nei-cun/3.2.3-wu-li-nei-cun-mo-xing.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
