物理内存模型
系统中的物理内存可以以不同的方式进行寻址。最简单的情况是物理内存从地址0开始,并延伸到最大地址的连续范围。然而,这个范围可能包含对CPU不可访问的小空洞。然后可能存在完全不同地址的几个连续范围。而且,不要忘记NUMA,其中不同的内存银行连接到不同的CPU。
Linux使用两种内存模型之一来抽象这种多样性:FLATMEM和SPARSEMEM。每个架构定义了它支持的内存模型,默认的内存模型是什么,以及是否可以手动覆盖默认值。
所有内存模型都使用一个或多个数组中的struct page来跟踪物理页帧的状态。
无论选择的内存模型如何,物理页帧号(PFN)与相应的struct page之间存在一对一的映射。
每个内存模型都定义了pfn_to_page()和page_to_pfn()辅助函数,允许从PFN转换为struct page,反之亦然。
FLATMEM
最简单的内存模型是FLATMEM。这个模型适用于具有连续或大部分连续物理内存的非NUMA系统。
在FLATMEM内存模型中,有一个全局的mem_map数组,用于映射整个物理内存。对于大多数架构,空洞在mem_map数组中有条目。对应于空洞的struct page对象从未完全初始化。
为了分配mem_map数组,架构特定的设置代码应调用free_area_init()函数。然而,在将所有内存交给页面分配器之前,映射数组是不可用的,这需要调用memblock_free_all()。
架构可以释放不覆盖实际物理页的mem_map数组的部分。在这种情况下,架构特定的pfn_valid()实现应考虑mem_map中的空洞。
使用FLATMEM,从PFN到struct page的转换很简单:PFN - ARCH_PFN_OFFSET是mem_map数组的索引。
ARCH_PFN_OFFSET定义了从地址0开始的物理内存系统的第一个页帧号。
SPARSEMEM
SPARSEMEM是Linux中最通用的内存模型,也是唯一支持几个高级功能的内存模型,例如物理内存的热插拔和热拔插,非易失性内存设备的备用内存映射以及较大系统的延迟初始化内存映射。
SPARSEMEM模型将物理内存表示为一组节(section)。一个节用struct mem_section表示,其中包含section_mem_map,逻辑上是指向struct pages数组的指针。然而,它存储了一些其他的魔术值,以帮助管理节。节的大小和最大节数由每个支持SPARSEMEM的架构定义的SECTION_SIZE_BITS和MAX_PHYSMEM_BITS常量指定。虽然MAX_PHYSMEM_BITS是架构支持的物理地址的实际宽度,但SECTION_SIZE_BITS是一个任意值。
最大节数表示为NR_MEM_SECTIONS,并定义为
NR\_MEM\_SECTIONS = 2 ^ {(MAX\_PHYSMEM\_BITS - SECTION\_SIZE\_BITS)}
mem_section对象以二维数组的形式排列,称为mem_sections。该数组的大小和位置取决于CONFIG_SPARSEMEM_EXTREME和最大可能的节数:
-
当禁用CONFIG_SPARSEMEM_EXTREME时,mem_sections数组是静态的,有NR_MEM_SECTIONS行。每行保存一个mem_section对象。
-
当启用CONFIG_SPARSEMEM_EXTREME时,mem_sections数组是动态分配的。每行包含PAGE_SIZE大小的mem_section对象,行数计算以适应所有内存节。
架构设置代码应调用sparse_init()来初始化内存节和内存映射。
使用SPARSEMEM,有两种可能的方法将PFN转换为相应的struct page - "经典稀疏"和"sparse vmemmap"。选择是在构建时进行的,并由CONFIG_SPARSEMEM_VMEMMAP的值确定。
经典稀疏将页的节号编码到page->flags中,并使用PFN的高位访问映射该页帧的节。在节内,PFN是页面数组的索引。
稀疏vmemmap使用虚拟映射的内存映射来优化pfn_to_page和page_to_pfn操作。有一个全局的struct page *vmemmap指针,指向一个虚拟连续的struct page对象数组。PFN是该数组的索引,struct page相对于vmemmap的偏移量是该页的PFN。
要使用vmemmap,架构必须保留一段虚拟地址范围,用于映射包含内存映射的物理页,并确保vmemmap指向该范围。此外,架构应该实现vmemmap_populate()方法,用于分配物理内存并为虚拟内存映射创建页表。如果架构对vmemmap映射没有任何特殊要求,可以使用通用内存管理提供的默认vmemmap_populate_basepages()。
虚拟映射的内存映射允许在预分配的存储器上存储持久性内存设备的struct page对象。这个存储器用struct vmem_altmap表示,最终通过一长串函数调用传递给vmemmap_populate()。vmemmap_populate()的实现可以使用vmem_altmap以及vmemmap_alloc_block_buf()辅助函数在持久性内存设备上分配内存映射。
ZONE_DEVICE
ZONE_DEVICE设施建立在SPARSEMEM_VMEMMAP之上,为设备驱动程序标识的物理地址范围提供struct page mem_map服务。ZONE_DEVICE的"device"方面与这样一个事实相关,即这些地址范围的页面对象从不被标记为在线,并且必须针对设备而不仅仅是页面进行引用,以保持内存固定以供活动使用。ZONE_DEVICE通过devm_memremap_pages()执行足够的内存热插拔,以打开pfn_to_page()、page_to_pfn()和get_user_pages()服务,以供给定范围的pfns使用。由于页面引用计数永远不会低于1,因此页面永远不会被跟踪为空闲内存,并且页面的struct list_head lru空间被重新用于反向引用到映射内存的主机设备/驱动程序。
虽然SPARSEMEM将内存表示为一组节,可选地收集到内存块中,但ZONE_DEVICE用户需要更小的粒度来填充mem_map。鉴于ZONE_DEVICE内存从未标记为在线,因此它随后永远不会通过sysfs内存热插拔API在内存块边界上公开其内存范围。实现依赖于这种缺乏用户API约束,以允许指定子节大小的内存范围给arch_add_memory(),即内存热插拔的上半部分。子节支持允许2MB作为devm_memremap_pages()的跨架构通用对齐粒度。
ZONE_DEVICE的用户有:
-
pmem:将平台持久性内存映射为通过DAX映射进行直接I/O的目标。
-
hmm:通过->page_fault()和->page_free()事件回调扩展ZONE_DEVICE,以允许设备驱动程序协调与设备内存相关的内存管理事件,通常是GPU内存。参见异构内存管理(HMM)。
-
p2pdma:创建struct page对象,允许PCI/-E拓扑中的对等设备之间协调直接DMA操作,即绕过主机内存。