内存管理-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中。
页标志位集合。用于记录这个页当前状态,例如是否脏页、是否在 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 表示可回收到伙伴系统。
联合体(mapcount / page_type / active / units)
_mapcount: 用户页表映射计数(从 -1 编码起)。 是映射计数,反映物理页被映射到多少个虚拟内存区域。初始值是 -1, 加上1以后才是真实的映射计数,建议使用内联函数 page_mapcount() 获取页的映射计数。
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。
index
在 mapping 中的页偏移(按 PAGE_SIZE)。
页面的pageblock的迁移类型的缓存值,在将页面放入 pcplist 时使用。在大多数情况下,用于在从 pcplist 释放时避免页面块迁移类型查找. 使用见 get_pcppage_migratetype();
它是在映射里面的偏移,单位是页。如果是匿名映射,那么 index 是物理页对应的虚拟页在虚拟内存区域中的页偏移,如果是文件映射那么 index 是物理页存储的数据在文件中的页偏移。
private
私有字段,典型用途:
a. PagePrivate 时挂 buffer_heads
b. PageSwapCache 时存 swap entry offset
c. PageBuddy 时存伙伴阶数信息
(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
posted on 2026-04-02 10:41 Hello-World3 阅读(19) 评论(0) 收藏 举报
浙公网安备 33010602011771号