162306a36Sopenharmony_ci.. SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci:Original: Documentation/mm/memory-model.rst
462306a36Sopenharmony_ci
562306a36Sopenharmony_ci:翻译:
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci 司延腾 Yanteng Si <siyanteng@loongson.cn>
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci:校译:
1062306a36Sopenharmony_ci
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_ci============
1362306a36Sopenharmony_ci物理内存模型
1462306a36Sopenharmony_ci============
1562306a36Sopenharmony_ci
1662306a36Sopenharmony_ci系统中的物理内存可以用不同的方式进行寻址。最简单的情况是,物理内存从地址0开
1762306a36Sopenharmony_ci始,跨越一个连续的范围,直到最大的地址。然而,这个范围可能包含CPU无法访问的
1862306a36Sopenharmony_ci小孔隙。那么,在完全不同的地址可能有几个连续的范围。而且,别忘了NUMA,即不
1962306a36Sopenharmony_ci同的内存库连接到不同的CPU。
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_ciLinux使用两种内存模型中的一种对这种多样性进行抽象。FLATMEM和SPARSEM。每
2262306a36Sopenharmony_ci个架构都定义了它所支持的内存模型,默认的内存模型是什么,以及是否有可能手动
2362306a36Sopenharmony_ci覆盖该默认值。
2462306a36Sopenharmony_ci
2562306a36Sopenharmony_ci所有的内存模型都使用排列在一个或多个数组中的 `struct page` 来跟踪物理页
2662306a36Sopenharmony_ci帧的状态。
2762306a36Sopenharmony_ci
2862306a36Sopenharmony_ci无论选择哪种内存模型,物理页框号(PFN)和相应的 `struct page` 之间都存
2962306a36Sopenharmony_ci在一对一的映射关系。
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci每个内存模型都定义了 :c:func:`pfn_to_page` 和 :c:func:`page_to_pfn`
3262306a36Sopenharmony_ci帮助函数,允许从PFN到 `struct page` 的转换,反之亦然。
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ciFLATMEM
3562306a36Sopenharmony_ci=======
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci最简单的内存模型是FLATMEM。这个模型适用于非NUMA系统的连续或大部分连续的
3862306a36Sopenharmony_ci物理内存。
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci在FLATMEM内存模型中,有一个全局的 `mem_map` 数组来映射整个物理内存。对
4162306a36Sopenharmony_ci于大多数架构,孔隙在 `mem_map` 数组中都有条目。与孔洞相对应的 `struct page`
4262306a36Sopenharmony_ci对象从未被完全初始化。
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_ci为了分配 `mem_map` 数组,架构特定的设置代码应该调用free_area_init()函数。
4562306a36Sopenharmony_ci然而,在调用memblock_free_all()函数之前,映射数组是不能使用的,该函数
4662306a36Sopenharmony_ci将所有的内存交给页分配器。
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci一个架构可能会释放 `mem_map` 数组中不包括实际物理页的部分。在这种情况下,特
4962306a36Sopenharmony_ci定架构的 :c:func:`pfn_valid` 实现应该考虑到 `mem_map` 中的孔隙。
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci使用FLATMEM,PFN和 `struct page` 之间的转换是直接的。 `PFN - ARCH_PFN_OFFSET`
5262306a36Sopenharmony_ci是 `mem_map` 数组的一个索引。
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_ci`ARCH_PFN_OFFSET` 定义了物理内存起始地址不同于0的系统的第一个页框号。
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_ciSPARSEMEM
5762306a36Sopenharmony_ci=========
5862306a36Sopenharmony_ci
5962306a36Sopenharmony_ciSPARSEMEM是Linux中最通用的内存模型,它是唯一支持若干高级功能的内存模型,
6062306a36Sopenharmony_ci如物理内存的热插拔、非易失性内存设备的替代内存图和较大系统的内存图的延迟
6162306a36Sopenharmony_ci初始化。
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ciSPARSEMEM模型将物理内存显示为一个部分的集合。一个区段用mem_section结构
6462306a36Sopenharmony_ci体表示,它包含 `section_mem_map` ,从逻辑上讲,它是一个指向 `struct page`
6562306a36Sopenharmony_ci阵列的指针。然而,它被存储在一些其他的magic中,以帮助分区管理。区段的大小
6662306a36Sopenharmony_ci和最大区段数是使用 `SECTION_SIZE_BITS` 和 `MAX_PHYSMEM_BITS` 常量
6762306a36Sopenharmony_ci来指定的,这两个常量是由每个支持SPARSEMEM的架构定义的。 `MAX_PHYSMEM_BITS`
6862306a36Sopenharmony_ci是一个架构所支持的物理地址的实际宽度,而 `SECTION_SIZE_BITS` 是一个任
6962306a36Sopenharmony_ci意的值。
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci最大的段数表示为 `NR_MEM_SECTIONS` ,定义为
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci.. math::
7462306a36Sopenharmony_ci
7562306a36Sopenharmony_ci   NR\_MEM\_SECTIONS = 2 ^ {(MAX\_PHYSMEM\_BITS - SECTION\_SIZE\_BITS)}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci`mem_section` 对象被安排在一个叫做 `mem_sections` 的二维数组中。这个数组的
7862306a36Sopenharmony_ci大小和位置取决于 `CONFIG_SPARSEM_EXTREME` 和可能的最大段数:
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci* 当 `CONFIG_SPARSEMEM_EXTREME` 被禁用时, `mem_sections` 数组是静态的,有
8162306a36Sopenharmony_ci  `NR_MEM_SECTIONS` 行。每一行持有一个 `mem_section` 对象。
8262306a36Sopenharmony_ci* 当 `CONFIG_SPARSEMEM_EXTREME` 被启用时, `mem_sections` 数组被动态分配。
8362306a36Sopenharmony_ci  每一行包含价值 `PAGE_SIZE` 的 `mem_section` 对象,行数的计算是为了适应所有的
8462306a36Sopenharmony_ci  内存区。
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci架构设置代码应该调用sparse_init()来初始化内存区和内存映射。
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci通过SPARSEMEM,有两种可能的方式将PFN转换为相应的 `struct page` --"classic sparse"和
8962306a36Sopenharmony_ci "sparse vmemmap"。选择是在构建时进行的,它由 `CONFIG_SPARSEMEM_VMEMMAP` 的
9062306a36Sopenharmony_ci 值决定。
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ciClassic sparse在page->flags中编码了一个页面的段号,并使用PFN的高位来访问映射该页
9362306a36Sopenharmony_ci框的段。在一个区段内,PFN是指向页数组的索引。
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ciSparse vmemmapvmemmap使用虚拟映射的内存映射来优化pfn_to_page和page_to_pfn操
9662306a36Sopenharmony_ci作。有一个全局的 `struct page *vmemmap` 指针,指向一个虚拟连续的 `struct page`
9762306a36Sopenharmony_ci对象阵列。PFN是该数组的一个索引,`struct page` 从 `vmemmap` 的偏移量是该页的PFN。
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci为了使用vmemmap,一个架构必须保留一个虚拟地址的范围,以映射包含内存映射的物理页,并
10062306a36Sopenharmony_ci确保 `vmemmap`指向该范围。此外,架构应该实现 :c:func:`vmemmap_populate` 方法,
10162306a36Sopenharmony_ci它将分配物理内存并为虚拟内存映射创建页表。如果一个架构对vmemmap映射没有任何特殊要求,
10262306a36Sopenharmony_ci它可以使用通用内存管理提供的默认 :c:func:`vmemmap_populate_basepages`。
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci虚拟映射的内存映射允许将持久性内存设备的 `struct page` 对象存储在这些设备上预先分
10562306a36Sopenharmony_ci配的存储中。这种存储用vmem_altmap结构表示,最终通过一长串的函数调用传递给
10662306a36Sopenharmony_civmemmap_populate()。vmemmap_populate()实现可以使用 `vmem_altmap` 和
10762306a36Sopenharmony_ci:c:func:`vmemmap_alloc_block_buf` 助手来分配持久性内存设备上的内存映射。
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ciZONE_DEVICE
11062306a36Sopenharmony_ci===========
11162306a36Sopenharmony_ci`ZONE_DEVICE` 设施建立在 `SPARSEM_VMEMMAP` 之上,为设备驱动识别的物理地址范
11262306a36Sopenharmony_ci围提供 `struct page` `mem_map` 服务。 `ZONE_DEVICE` 的 "设备" 方面与以下
11362306a36Sopenharmony_ci事实有关:这些地址范围的页面对象从未被在线标记过,而且必须对设备进行引用,而不仅仅
11462306a36Sopenharmony_ci是页面,以保持内存被“锁定”以便使用。 `ZONE_DEVICE` ,通过 :c:func:`devm_memremap_pages` ,
11562306a36Sopenharmony_ci为给定的pfns范围执行足够的内存热插拔来开启 :c:func:`pfn_to_page`,
11662306a36Sopenharmony_ci:c:func:`page_to_pfn`, ,和 :c:func:`get_user_pages` 服务。由于页面引
11762306a36Sopenharmony_ci用计数永远不会低于1,所以页面永远不会被追踪为空闲内存,页面的 `struct list_head lru`
11862306a36Sopenharmony_ci空间被重新利用,用于向映射该内存的主机设备/驱动程序进行反向引用。
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci虽然 `SPARSEMEM` 将内存作为一个区段的集合,可以选择收集并合成内存块,但
12162306a36Sopenharmony_ci`ZONE_DEVICE` 用户需要更小的颗粒度来填充 `mem_map` 。鉴于 `ZONE_DEVICE`
12262306a36Sopenharmony_ci内存从未被在线标记,因此它的内存范围从未通过sysfs内存热插拔api暴露在内存块边界
12362306a36Sopenharmony_ci上。这个实现依赖于这种缺乏用户接口的约束,允许子段大小的内存范围被指定给
12462306a36Sopenharmony_ci:c:func:`arch_add_memory` ,即内存热插拔的上半部分。子段支持允许2MB作为
12562306a36Sopenharmony_ci:c:func:`devm_memremap_pages` 的跨架构通用对齐颗粒度。
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci`ZONE_DEVICE` 的用户是:
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci* pmem: 通过DAX映射将平台持久性内存作为直接I/O目标使用。
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_ci* hmm: 用 `->page_fault()` 和 `->page_free()` 事件回调扩展 `ZONE_DEVICE` ,
13262306a36Sopenharmony_ci  以允许设备驱动程序协调与设备内存相关的内存管理事件,通常是GPU内存。参见Documentation/mm/hmm.rst13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci* p2pdma: 创建 `struct page` 对象,允许PCI/E拓扑结构中的peer设备协调它们之间的
13562306a36Sopenharmony_ci  直接DMA操作,即绕过主机内存。
136