内存管理-5-物理内存数据结构-3-struct page
基于msm-5.4
一、struct page简介
物理页帧,系统中的每个物理页面都有一个与之关联的 struct page,用于跟踪我们当前正在使用该页面的用途。请注意,我们无法跟踪哪些任务正在使用页面,但如果它是页面缓存页面,rmap 结构可以告诉我们谁在映射它。
如果您使用 alloc_pages() 分配页面,则可以将 struct page 中的部分空间用于自己的目的。主联合中的五个字(40B)可用,但第一个字的位 0 除外,必须保持清0。许多用户使用此字来存储指向保证对齐的对象的指针。如果您使用与 page->mapping
相同的存储,则必须在释放页面之前将其恢复为 NULL。
如果您的页面不会映射到用户空间,您也可以使用 mapcount 联合中的四个字节,但必须在释放它之前调用 page_mapcount_reset()。
如果要使用 refcount 字段,则必须以其他 CPU 临时增加然后减少 refcount 的方式使用它,而不会导致问题。从 alloc_pages() 接收页面时,refcount 将为正数。
如果分配 order > 0 的页面,则可以使用每个子页面中的某些字段,但之后可能需要恢复它们的值。
SLUB 使用 cmpxchg_double() 原子更新其空闲列表和计数器。这要求空闲列表和计数器相邻且双字8字节对齐。 我们将所有结构页面对齐到双字边界,
并确保“空闲列表”在结构内对齐。
page结构里面有很多union类型,因为page可以用来表示不同类型的物理内存。
page cache: 页缓存,磁盘在去读写的时候在内存中会有一个缓存,叫页缓存。平时申请的匿名页,也会使用page cache类型表示。
page_pool:
slab:
Tail pages:
Second tail page:
Page table:
系统在初始化的时候,对于每一个物理页帧,都会定义一个 struct page 结构去表示它。比如有一千个页帧,就要定义一千个page结构体,这些page结构体也会存放在内存中,使用 vmemmap 指针指向这个区域。此时物理页帧号pfn和page结构之间就存在了一一对应的关系,因为两者都是线性排列的。
#define __pfn_to_page(pfn) (vmemmap + (pfn)) //返回指针 #define __page_to_pfn(page) (unsigned long)((page) - vmemmap) //参数page是指针
//注:pfn = paddr >> PAGE_SHIFT
1.1 struct page 在内存管理中扮演的角色:
struct page 是物理内存页的元数据载体,其核心作用是为内核提供统一管理物理内存页的基础结构。每个物理页帧(Page Frame)对应一个 struct page 实例,通过它内核可以追踪页的状态、属性及关联信息。
struct page 记录页的物理属性(如是否空闲、脏页、锁定状态等); 系统所有 struct page 组成全局数组 mem_map, 通过页帧号(PFN)可直接索引到对应页的元数据。它在内存回收、LRU链表、反向映射、页缓存与文件映射。
二、成员介绍
//include/linux/mm_types.h struct page { unsigned long flags; /* 此union结构是40B */ union { /* Page cache and anonymous pages 40B */ struct { struct list_head lru; struct address_space *mapping; pgoff_t index; unsigned long private; }; /* page_pool used by netstack 8B */ struct { dma_addr_t dma_addr; }; /* slab, slob and slub 40B */ struct { union { struct list_head slab_list; /* Partial pages */ struct { struct page *next; int pages; int pobjects; }; }; struct kmem_cache *slab_cache; void *freelist; union { void *s_mem; unsigned long counters; struct { unsigned inuse:16; unsigned objects:15; unsigned frozen:1; }; }; }; /* Tail pages of compound page 16B */ struct { unsigned long compound_head; /* First tail page only */ unsigned char compound_dtor; unsigned char compound_order; atomic_t compound_mapcount; }; /* Second tail page of compound page 32B */ struct { unsigned long _compound_pad_1; unsigned long _compound_pad_2; struct list_head deferred_list; }; /* Page table pages 40B */ struct { unsigned long _pt_pad_1; pgtable_t pmd_huge_pte; unsigned long _pt_pad_2; union { struct mm_struct *pt_mm; atomic_t pt_frag_refcount; }; spinlock_t *ptl; }; /* ZONE_DEVICE pages 16B */ struct { struct dev_pagemap *pgmap; void *zone_device_data; }; /* 16B */ struct rcu_head rcu_head; }; /* 4B */ union { atomic_t _mapcount; unsigned int page_type; unsigned int active; int units; }; atomic_t _refcount; struct mem_cgroup *mem_cgroup; //CONFIG_MEMCG int _last_cpupid; } __aligned(2 * sizeof(unsigned long));
__init_single_page() 中在初始化一个page结构之前,先将其清0.
成员介绍:
1. 固定存在的成员
flags
set_page_zone()中表明bit63表示此page属于哪个zone,由 set_page_links() 可知,当前配置下,node_id 和 section_id 都没保存在这个flags中。
//mmzone.h Page flags 组成: | [SECTION] | [NODE] | ZONE | [LAST_CPUPID] | ... | FLAGS | int page_to_nid(const struct page *page) //获取page所属node return (page->flags >> NODES_PGSHIFT) & NODES_MASK;
此外还有 page_zone() page_zonenum()
页标志位集合。用于记录这个页当前状态,例如是否脏页、是否在 LRU、是否是 slab 页、是否是复合页、是否是 swapcache、是否被锁等,后续根据它判断可以使用联合提中的哪些成员。flags 的高位还常编码 zone/node/section、最近 CPU/PID、KASAN tag 等元信息(是否在 flags 中取决于配置)。
字段解释总表(按页面类型):
--------------------------------------------------------------------------------- 页面类型 flags标志 可用字段 禁用字段 --------------------------------------------------------------------------------- 文件/匿名页 PG_lru lru, mapping, index, private slab_cache, freelist Slab对象 PG_slab slab_list, slab_cache, freelist mapping, index 巨页 PG_compound compound_head, compound_order 大部分字段 设备页 PG_hwpoison pgmap, zone_device_data 常规字段 页表页 PG_table pt_mm, ptl 联合体大多数 交换缓存 PG_swapcache swap_offset, mapping 常规LRU字段 ---------------------------------------------------------------------------------
比如 PageLRU(PG_lru) 判断是否是文件页、PageSlab(PG_slab) 判断是否是Slab页、 PageCompound(PG_head) 判断是否是复合页。
需要先检查类型,再访问对应字段,比如:
if (PageSlab(page)) { // 访问 slab 字段 cache = page->slab_cache; } else if (PageLRU(page)) { // 访问 LRU 字段 mapping = page->mapping; }
判断是否是页表页,使用的是 PageTable(page->page_type)。
更多见《内存管理-5-page->flags》
_refcount
引用计数。决定该页是否还能被释放。大于 0 表示页仍在使用,变为 0 表示可回收到伙伴系统。
记录内核中对该页框的所有引用次数,包括内核模块的引用、页缓存、LRU链表、slab分配器等。等于 0 表示该页可以被释放。
联合体(mapcount / page_type / active / units)
_mapcount:
用户页表映射计数(从 -1 编码起)。 是映射计数,反映物理页被映射到多少个虚拟内存区域。初始值是 -1, 加上1以后才是真实的映射计数,建议使用内联函数 page_mapcount() 获取页的映射计数。
page->_mapcount 初始值为 -1,而不是 0,语义是“该物理页被页表映射的次数”。
初始状态(-1):当一个物理页刚被分配出来,还没有任何进程的页表项(PTE)映射到它时,_mapcount 被设置为 -1。这个 -1 代表“零次映射”的基准点。
第一次映射 (变为 0):当第一个进程(通常是分配该页的进程)通过缺页中断建立页表映射后,内核会调用 page_add_anon_rmap() 或 page_add_file_rmap() 等函数,将 _mapcount 增加 1。此时 _mapcount 从 -1 变为 0. _mapcount == 0 表示该页被一个进程的页表映射。
第二次映射 (变为 1):如果另一个进程(例如通过 fork() 共享该页)也映射了这个物理页,_mapcount 会再次加 1,变为 1。此时表示该页被两个进程映射.
page_type: 非可映射页的类型编码。
active: SLAB 场景字段。
units: SLOB 场景字段。
2. 大联合体(5个long复用区)
根据“页用途”解释为不同子结构。
(1) 作为页缓存/匿名页时(最常见)
lru
挂到 LRU 或其他链表。
mapping
指向 address_space(文件页) 或相关映射对象。
利用指针总是4的整数倍这个特性,成员 mapping 的最低两位用来作为页映射标志,最低位 PAGE_MAPPING_ANON 表示匿名页。如果物理页是匿名页,page.mapping = (struct anon_vma 的地址 | PAGE_MAPPING ANON)。如果物理页是文件页 page.mapping 指向结构体 address_space。
mapping 还记录着是否可移动:
static __always_inline int __PageMovable(struct page *page) { return ((unsigned long)page->mapping & PAGE_MAPPING_FLAGS) == PAGE_MAPPING_MOVABLE; }
index
在 mapping 中的页偏移(按 PAGE_SIZE)。
页面的pageblock的迁移类型的缓存值,在将页面放入 pcplist 时使用。在大多数情况下,用于在从 pcplist 释放时避免页面块迁移类型查找. 使用见 get_pcppage_migratetype();
它是在映射里面的偏移,单位是页。如果是匿名映射,那么 index 是物理页对应的虚拟页在虚拟内存区域中的页偏移,如果是文件映射那么 index 是物理页存储的数据在文件中的页偏移。
private
私有字段,典型用途:
a. PagePrivate 时挂 buffer_heads
b. PageSwapCache 时存 swap entry offset
c. PageBuddy 时存伙伴阶数信息
page->private 当页在 buddy 系统中时,存储页块的"阶"(order)。当页面是 buddy 块时,page->private = order,当页面不是 buddy 块时,page->private 应为 0 或无意义。页从伙伴系统分配出来时,del_page_from_free_list --> page->private = 0; 页释放回伙伴系统时,也需要传这个 order,不同场景来源不同:
--------------------------------------------------------------------------------------------------- 场景 Order 来源 获取方法 谁负责 --------------------------------------------------------------------------------------------------- Hugepage page[1]->compound_order compound_order(page) 内核自动读取 THP page[1]->compound_order compound_order(page) 内核自动读取 kmalloc slub/slab 记录 kmem_cache→obj_to_index 分配器记录 直接 __alloc_pages() 调用者记住 显式参数传递 用户负责! PCP 快速路径 隐含 order-0 free_unref_page(page, order) 调用者传递 ---------------------------------------------------------------------------------------------------
(2) 作为网络 page_pool 页
dma_addr:网卡/网络栈复用的 DMA 地址信息.
(3) 作为 slab/slub/sblob 页
slab_list 或 partial 链接信息.
slab_cache: 所属 kmem_cache.
freelist: 空闲对象链表头.
counters 或 inuse/objects/frozen: 对象计数与冻结状态.
(4) 作为复合页(compound page)尾页首个 tail 页
compound_head(低 bit 标记 tail)
compound_dtor
compound_order
compound_mapcount
(5) 作为复合页(compound page)尾页第二个 tail 页
deferred_list(例如延迟拆分相关链表)
(6) 作为页表页时
pmd_huge_pte: THP 相关
pt_mm 或 pt_frag_refcount(架构相关)
ptl(页表锁,可能是指针或内嵌锁,取决于是否 split ptlock)
(7) 作为 ZONE_DEVICE 页
pgmap: 设备页映射描述
zone_device_data: 设备私有数据,并且会复用 mapping/index/private 保存迁移或 DAX 相关上下文。
rcu_head: 可用于 RCU 延迟释放路径。
3. 可配置成员
mem_cgroup(CONFIG_MEMCG): 用于记录该页归属的内存 cgroup。
virtual(WANT_PAGE_VIRTUAL: 主要用于 highmem 架构缓存内核虚拟地址。arm64 通常不用这个字段。
_last_cpupid(LAST_CPUPID_NOT_IN_PAGE_FLAGS): 用于NUMA balancing 的最近 CPU/PID 追踪。
三、辅助函数
1. 获取page对应的zone、node、section
//include/linux/mm.h static inline struct zone *page_zone(const struct page *page) { return &NODE_DATA(page_to_nid(page))->node_zones[page_zonenum(page)]; } static inline pg_data_t *page_pgdat(const struct page *page) { return NODE_DATA(page_to_nid(page)); } static inline unsigned long page_to_section(const struct page *page) { return (page->flags >> SECTIONS_PGSHIFT) & SECTIONS_MASK; //64 0 }
四、其它相关
1. __mm_zero_struct_page() 中有对 struct page 的大小进行检查,必须是 56, 64, 72, 80 这几个大小,否则在编译时报错。
2. page有一组debug trace
trace_page_ref_{set/mod/mod_and_test/mod_and_return/mod_unless/freeze/unfreeze}, 使用见 debug_page_ref.c
五、Linux-6.1中的struct page
struct page { unsigned long flags; union { struct { /* Page cache and anonymous pages */ union { struct list_head lru; struct { void *__filler; unsigned int mlock_count; }; struct list_head buddy_list; struct list_head pcp_list; }; struct address_space *mapping; pgoff_t index; unsigned long private; }; struct { /* page_pool used by netstack */ unsigned long pp_magic; struct page_pool *pp; unsigned long _pp_mapping_pad; unsigned long dma_addr; union { unsigned long dma_addr_upper; atomic_long_t pp_frag_count; }; }; struct { /* Tail pages of compound page */ unsigned long compound_head; unsigned char compound_dtor; unsigned char compound_order; atomic_t compound_mapcount; atomic_t compound_pincount; unsigned int compound_nr; }; struct { /* Second tail page of compound page */ unsigned long _compound_pad_1; unsigned long _compound_pad_2; struct list_head deferred_list; }; struct { /* Page table pages */ unsigned long _pt_pad_1; pgtable_t pmd_huge_pte; unsigned long _pt_pad_2; union { struct mm_struct *pt_mm; atomic_t pt_frag_refcount; }; spinlock_t *ptl; }; struct { /* ZONE_DEVICE pages */ struct dev_pagemap *pgmap; void *zone_device_data; }; struct rcu_head rcu_head; }; union { /* This union is 4 bytes in size. */ atomic_t _mapcount; unsigned int page_type; }; atomic_t _refcount; #ifdef CONFIG_MEMCG unsigned long memcg_data; #endif #ifdef LAST_CPUPID_NOT_IN_PAGE_FLAGS int _last_cpupid; #endif } _struct_page_alignment;
1. lru、buddy_list、pcp_list
page->lru、page->buddy_list、page->pcp_list 是同一块内存的三个名字(union 成员),get_page_from_free_area 用 lru 是历史遗留代码尚未重构,与 add_to_free_list 用 buddy_list 在功能上完全等价,不会产生任何问题。 页面在 buddy 系统中时,语义正确的名字应该是 buddy_list。
posted on 2026-04-02 10:41 Hello-World3 阅读(43) 评论(0) 收藏 举报
浙公网安备 33010602011771号