内存模型
Linux 内核把页面当作物理内存管理的基本单位(一般情况下,虚拟页叫页面,而物理页叫页帧)。每个页帧对应一个 struct page 结构体(页描述符),这是一个体系结构无关的数据结构。pfn 是物理页的编号。
在内核中,每个页帧必然有唯一一个页描述符与之对应。于是,页描述符的地址与pfn之间存在对应关系,即 page_to_pfn() 和 pfn_to_page()。
同时,Linux 内核支持 3 种不同的“内存模型”,分别是:Flat Memory、Discontigous Memory、Sparse Memory。
对于 Sparse Memory 模型,还有两个变体: Sparse Extrem Memory、Sparse Vmemmap Memory。
Sparse Memory内存模型
在 Sparse Memory 模型中,内存按照 section 进行划分,每个 section 用 struct mem_section 来描述(对于 x86 而言,一个 section 是 128MB)。其中在 struct mem_section 的 section_mem_map 变量中存了该 section 对应的 struct page 数组的首地址(实际上里面还编码了当前 section 起始 pfn 的偏移量,这确保了下面第四步计算结果的正确性)。
在 Sparse Memory 模型中,由 page_to_pfn() 需要四步:
- 由 page 结构体得到其所在的 section 编号
- 由 section 编号得到 section 结构体
- 由 section 结构体得到其对应的 struct page 数组首地址
- 由 page 首地址减去 struct page 数组首地址得到物理帧号
pfn_to_page() 反之亦然。在 Sparse Memory 模型中,pfn 是连续的,但 struct page 数组不见得是连续的,也可以是分段的。
Sparse Vmemmap Memory内存模型
在 Sparse Vmemmap Memory 模型中,struct page 数组是通过页表映射进行访问的。
其中 struct page 数组的首地址是 vmemmap,并且 struct page 数组在虚拟地址空间是连续的,因此 page_to_pfn() 只需要一步, 就等于 page - vmemmap。pfn_to_page() 反之亦然,和 Sparse Memory 模型相比,page_to_pfn() 的计算更加简单,不需要访存即可得到结果。
内核代码
#define page_to_pfn __page_to_pfn
#define pfn_to_page __pfn_to_page
#if defined(CONFIG_SPARSEMEM_VMEMMAP)
/* memmap is virtually contiguous. */
#define __pfn_to_page(pfn) (vmemmap + (pfn))
#define __page_to_pfn(page) (unsigned long)((page) - vmemmap)
#elif 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 /* CONFIG_FLATMEM/DISCONTIGMEM/SPARSEMEM */