struct page

 

 

/*
 * 系统中的每一个物理页(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;

 

posted @ 2025-11-02 22:03  苏格拉底的落泪  阅读(4)  评论(0)    收藏  举报