page->flags 虽然只占 8 个字节,但 64 个 bit 位里存了较多的信息,且受若干配置选项的影响,比如内存模型、zone 数量、node 数量等,有必要简单梳理一下。从下面内核源码的注释可以看到,page->flags 的布局一共分为 5 种情况,第2、4种格式只是多了一个 LAST_CPUID 域,可以暂且不看,同时 FLAGS 域相对比较固定,也可以暂且不看,这里需要分析 SECTION、NODE、ZONE 域的去留问题。
内核中对 page->flags 布局的描述:
/*
* page->flags layout:
*
* There are five possibilities for how page->flags get laid out. The first
* pair is for the normal case without sparsemem. The second pair is for
* sparsemem when there is plenty of space for node and section information.
* The last is when there is insufficient space in page->flags and a separate
* lookup is necessary.
*
* No sparsemem or sparsemem vmemmap: | NODE | ZONE | ... | FLAGS |
* " plus space for last_cpupid: | NODE | ZONE | LAST_CPUPID ... | FLAGS |
* classic sparse with space for node:| SECTION | NODE | ZONE | ... | FLAGS |
* " plus space for last_cpupid: | SECTION | NODE | ZONE | LAST_CPUPID ... | FLAGS |
* classic sparse no space for node: | SECTION | ZONE | ... | FLAGS |
*/
第 1 种格式分为两种情况:
情况一:非稀疏型内存模型
稀疏型内存模型将整个物理地址空间划分成多个区段(section),每个区段由一个 mem_section 描述,因此在 page->flags 中需要存储该页所属的 section_id,但在非稀疏型内存模型中没有 mem_section,所以 page->flags 中不需要存储 section_id。
情况二:vmemmap 稀疏型内存模型
在 sparsemem vmemmap 模型中,page 和 pfn 一一对应,由 page 可以得到 pfn,由 pfn 可以得到 section_id,因此 page->flags 中也不需要存储 section_id。
第 3 种格式:常规稀疏型内存模型
page->flags 需要存储 section_id,同时也存储了 node 和 zone 信息。
第 5 种格式:常规稀疏型内存模型
page->flags 需要存储 section_id 和 zone 信息,但没有空间用来存储 node 信息,因此,在这种情况下存在一个 section_to_node_table[ ] 全局数组,它存储了 section_id 和 node_id 的对应关系,在获取 page 的 node_id 时,先得到 page 的 section_id,再由上述数组查找到 node_id。
section_to_node_table[ ] 数组定义:
#ifdef NODE_NOT_IN_PAGE_FLAGS
/*
* If we did not store the node number in the page then we have to
* do a lookup in the section_to_node_table in order to find which
* node the page belongs to.
*/
#if MAX_NUMNODES <= 256
static u8 section_to_node_table[NR_MEM_SECTIONS] __cacheline_aligned;
#else
static u16 section_to_node_table[NR_MEM_SECTIONS] __cacheline_aligned;
#endif
int page_to_nid(const struct page *page)
{
return section_to_node_table[page_to_section(page)];
}
EXPORT_SYMBOL(page_to_nid);
static void set_section_nid(unsigned long section_nr, int nid)
{
section_to_node_table[section_nr] = nid;
}
#else /* !NODE_NOT_IN_PAGE_FLAGS */
static inline void set_section_nid(unsigned long section_nr, int nid)
{
}
#endif
不管是哪种格式,ZONE 域和 FLAGS 域始终是保留的。zone、node、section 信息是在页初始化的时候设置的,其中 zone 信息在 free_area_init_core() 函数中设置完成后不再改变。
pages->flags 的初始化:
static inline void set_page_links(struct page *page, enum zone_type zone,
unsigned long node, unsigned long pfn)
{
set_page_zone(page, zone);
set_page_node(page, node);
#ifdef SECTION_IN_PAGE_FLAGS
set_page_section(page, pfn_to_section_nr(pfn));
#endif
}