/*
* 系统中的每一个物理页(physical page)都对应一个 struct page 结构,
* 用于追踪该页当前的用途。注意,我们无法追踪哪些任务(task)
* 正在使用这个页;但如果该页是一个 pagecache 页面,
* 我们可以通过 rmap(reverse mapping)结构来知道是谁映射了它。
*
* 如果你是通过 alloc_pages() 分配页面,
* 那么你可以把 struct page 中的一部分空间用于你自己的目的。
* 在主 union 中的五个字(5 words, 即 20/40 字节)是可用的,
* 但第一个字(word)的 bit 0 必须保持清零。
* 许多使用者会把这个字用于存储一个已对齐的指针。
* 如果你使用了与 page->mapping 相同的存储位置,
* 那么在释放该页面前必须把它恢复为 NULL。
*
* 如果你的页面不会映射到用户空间(userspace),
* 那么你还可以使用 mapcount union 中的四个字节,
* 但在释放页面前必须调用 page_mapcount_reset()。
*
* 如果你想使用 refcount 字段,
* 那么必须保证其他 CPU 临时增加或减少引用计数时不会引发问题。
* 从 alloc_pages() 得到的页面,其 refcount 初始为正数。
*
* 如果你分配的是 order > 0 的连续页(compound page),
* 那么你可以在每个子页中使用部分字段,
* 但在释放之前可能需要恢复它们的值。
*
* SLUB 使用 cmpxchg_double() 来原子更新 freelist 和计数器。
* 这要求 freelist 和 counters 必须相邻且双字(double-word)对齐。
* 我们会把所有 struct page 按双字边界对齐,
* 并保证结构中的 'freelist' 字段在结构体中是对齐的。
*/
#ifdef CONFIG_HAVE_ALIGNED_STRUCT_PAGE
#define _struct_page_alignment __aligned(2 * sizeof(unsigned long))
#else
#define _struct_page_alignment
#endif
struct page {
unsigned long flags; /* 原子标志位(atomic flags),部分可能异步更新 */
/*
* 这个 union 中有五个字(20/40 字节)可用。
* 注意:第一个字(word)的 bit 0 被 PageTail() 使用。
* 因此,其他使用这个 union 的代码不能使用该位,
* 否则会引发冲突和误判 PageTail()。
*/
union {
struct { /* 用于 page cache 和匿名页(anonymous pages) */
/**
* @lru: 页面回收链表(pageout list),
* 例如 active_list,由 pgdat->lru_lock 保护。
* 有时也被页面所有者作为通用链表使用。
*/
struct list_head lru;
/* 参见 page-flags.h 中的 PAGE_MAPPING_FLAGS */
struct address_space *mapping;
pgoff_t index; /* 在 mapping 中的偏移(页索引) */
/**
* @private: 映射私有的不透明数据。
* 如果 PagePrivate 被设置,通常用于 buffer_heads。
* 如果是 PageSwapCache,存放 swp_entry_t。
* 如果是 PageBuddy,表示 buddy 系统中的 order。
*/
unsigned long private;
};
struct { /* 网络栈(netstack)使用的 page_pool */
/**
* @dma_addr: 在 32 位架构上可能需要 64 位值。
*/
unsigned long dma_addr[2];
};
struct { /* slab、slob 和 slub 分配器使用 */
union {
struct list_head slab_list;
struct { /* 部分页(Partial pages) */
struct page *next;
#ifdef CONFIG_64BIT
int pages; /* 剩余页数 */
int pobjects; /* 大致对象数 */
#else
short int pages;
short int pobjects;
#endif
};
};
struct kmem_cache *slab_cache; /* 非 slob 使用 */
/* 双字对齐边界 */
void *freelist; /* 第一个空闲对象 */
union {
void *s_mem; /* slab:第一个对象 */
unsigned long counters; /* SLUB 模式 */
struct { /* SLUB 模式 */
unsigned inuse:16;
unsigned objects:15;
unsigned frozen:1;
};
};
};
struct { /* compound page 的尾页(tail page) */
unsigned long compound_head; /* 第 0 位(bit zero)被置位 */
/* 仅第一个 tail page 使用 */
unsigned char compound_dtor;
unsigned char compound_order;
atomic_t compound_mapcount;
unsigned int compound_nr; /* 1 << compound_order */
};
struct { /* compound page 的第二个尾页 */
unsigned long _compound_pad_1; /* compound_head 占位 */
atomic_t hpage_pinned_refcount;
/* 同时用于全局和 memcg */
struct list_head deferred_list;
};
struct { /* 页表页(page table pages) */
unsigned long _pt_pad_1; /* compound_head 占位 */
pgtable_t pmd_huge_pte; /* 由 page->ptl 保护 */
unsigned long _pt_pad_2; /* mapping 占位 */
union {
struct mm_struct *pt_mm; /* 仅 x86 pgd 使用 */
atomic_t pt_frag_refcount; /* powerpc 使用 */
};
#if ALLOC_SPLIT_PTLOCKS
spinlock_t *ptl;
#else
spinlock_t ptl;
#endif
};
struct { /* ZONE_DEVICE 类型的页面 */
/** @pgmap: 指向对应的设备页映射结构 */
struct dev_pagemap *pgmap;
void *zone_device_data;
/*
* ZONE_DEVICE 私有页面会被认为是已映射的,
* 因此接下来的三个字(mapping、index、private)
* 用于在页面被迁移到设备私有内存时,
* 保存源匿名页或页缓存页的信息。
* 对于 ZONE_DEVICE 类型为 MEMORY_DEVICE_FS_DAX 的页,
* 当 pmem 支持的 DAX 文件被映射时,
* 这些字段(mapping、index、private)同样会被使用。
*/
};
/** @rcu_head: 可用于通过 RCU 延迟释放页面。 */
struct rcu_head rcu_head;
};
union { /* 该 union 占 4 字节 */
/*
* 如果页面可映射到用户空间,
* 则该字段表示该页面被页表引用的次数。
*/
atomic_t _mapcount;
/*
* 如果页面既不是 PageSlab,也不会映射到用户空间,
* 那么这里存储的值可以用来表示页面的用途。
* 可参考 page-flags.h 中当前定义的页面类型。
*/
unsigned int page_type;
unsigned int active; /* SLAB 使用 */
int units; /* SLOB 使用 */
};
/* 使用计数。不要直接使用,请参见 page_ref.h */
atomic_t _refcount;
#ifdef CONFIG_MEMCG
union {
struct mem_cgroup *mem_cgroup;
struct obj_cgroup **obj_cgroups;
};
#endif
/*
* 在所有物理内存都映射到内核地址空间的机器上,
* 可以直接计算出虚拟地址。
* 但在 highmem 架构上,一部分内存是动态映射的,
* 因此需要保存映射时的虚拟地址。
* 注意:在 x86 上该字段可能只有 16 位。
*
* 对于乘法较慢的架构,可以在 asm/page.h 中定义
* WANT_PAGE_VIRTUAL。
*/
#if defined(WANT_PAGE_VIRTUAL)
void *virtual; /* 内核虚拟地址(如果未 kmap 则为 NULL,即 highmem) */
#endif /* WANT_PAGE_VIRTUAL */
#ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS
int _last_cpupid;
#endif
} _struct_page_alignment;