内存管理-5-page->flags
一、简介
1. page->flags 定义
struct page 的 flags 是一个位图。真正“业务语义位”主要来自 enum pageflags。同一个物理位在不同页面类型下可能被复用(别名),这是 page->flags 最容易误解的点。
enum pageflags { //page-flags.h PG_locked, PG_referenced, PG_uptodate, PG_dirty, PG_lru, PG_active, PG_workingset, PG_waiters, PG_error, PG_slab, PG_owner_priv_1, PG_arch_1, PG_reserved, PG_private, PG_private_2, PG_writeback, PG_head, PG_mappedtodisk, PG_reclaim, PG_swapbacked, PG_unevictable, #ifdef CONFIG_MMU //默认使能 PG_mlocked, #endif #ifdef CONFIG_ARCH_USES_PG_UNCACHED //默认不使能 PG_uncached, #endif #ifdef CONFIG_MEMORY_FAILURE //默认不使能 PG_hwpoison, #endif #if defined(CONFIG_IDLE_PAGE_TRACKING) && defined(CONFIG_64BIT) //前者默认不使能 PG_young, PG_idle, #endif __NR_PAGEFLAGS, PG_checked = PG_owner_priv_1, PG_swapcache = PG_owner_priv_1, PG_fscache = PG_private_2, PG_pinned = PG_owner_priv_1, PG_savepinned = PG_dirty, PG_foreign = PG_owner_priv_1, PG_xen_remapped = PG_owner_priv_1, PG_slob_free = PG_private, PG_double_map = PG_private_2, PG_isolated = PG_reclaim, };
enum pageflags 里的位是从低位向上编号。前 21 个是基础位,后面若干位由 Kconfig 决定是否存在。flags 的高位还可能塞了 zone/node/section/last_cpupid 等字段。
针对还可能塞了 zone/node/section/last_cpupid 等字段详解:
zone: 表示该页属于哪个内存 zone(如 DMA/DMA32/NORMAL/MOVABLE),分配器快速路径会用到.
node: NUMA节点号, 用于快速得到该页所在 NUMA node
section: SPARSEMEM 场景下的 section 编号, 在稀疏内存模型中定位物理内存分段.
last_cpupid: NUMA balancing 相关的“最近访问者编码”(CPU + PID 压缩信息), 用来减少不必要的 NUMA hint fault 抖动和重复迁移判断
KASAN tag(可选): 某些 KASAN 模式会占用额外 bit 宽度.
这些字段“是否在 flags 里”取决于配置,需要容纳 SECTIONS_WIDTH(0) + ZONES_WIDTH(1) + NODES_WIDTH(0) + LAST_CPUPID_WIDTH + KASAN_TAG_WIDTH(if KASAN enabled), 详见 page-flags-layout.h,若容纳不下的话,会有 #error "Not enough bits in page flags" 让你编译不过。
NODE_NOT_IN_PAGE_FLAGS:使能的话 node 放不进 flags,走其他方式获取。默认不使能。
LAST_CPUPID_NOT_IN_PAGE_FLAGS:使能的话 last_cpupid 放不进 flags,改用单独存储(你在 mm_types 里会看到 _last_cpupid). 默认不使能。
注: 嵌入式容纳的下,这些默认都是放在 page->flags 中的。
2. 各基础标志含义
(1) PG_locked
含义:页被锁,表示 I/O 或关键状态变更正在进行。典型作用:阻止并发修改、截断等;等待者会在页等待队列睡眠。
(2) PG_referenced
含义:页近期被访问过。用途:回收算法判断冷热页,配合 active/inactive LRU。
(3) PG_uptodate
含义:页内容有效。典型场景:读 I/O 成功后置位;如果 I/O 失败则不置位。
(4) PG_dirty
含义:页被修改过,尚未回写到后端存储。用途:回写子系统扫描脏页并刷盘。
(5) PG_lru
含义:页在某个 LRU 链上。用途:页回收和老化管理。
(6) PG_active
含义:页在 active LRU(更“热”)。用途:回收器优先回收 inactive,active 先降级再回收。
(7) PG_workingset
含义:该页被认为属于工作集,反映活跃集相关信息。用途:帮助回收器判断抖动与冷热切换。
(8) PG_waiters
含义:页上有等待者。特点:必须和 PG_locked 放在同一字节中(5.4 明确要求)。
(9) PG_error
含义:该页发生过 I/O 错误。用途:上层可据此做失败处理。
(10) PG_slab
含义:页由 slab/slub/slob 分配器管理。用途:区分普通页与 slab 页。
(11) PG_owner_priv_1
含义:所有者私有位 1。说明:常被文件系统、swap、xen 等复用(见“别名复用”部分)。
(12) PG_arch_1
含义:架构私有位。说明:语义由具体架构定义,通用层只保证某些场景下会清零。
(13) PG_reserved
含义:保留页,不应按普通可分配页对待。典型对象:内核镜像区、早期保留内存、设备内存、zero page、离线 section 等。
(14) PG_private
含义:页带私有数据(常见于 page cache 页的 page->private)。用途:触发 release page 等私有回调路径。
(15) PG_private_2
含义:页带辅助私有状态。用途:同样常被子系统复用。
(16) PG_writeback
含义:页正在回写。配套:回写开始置位,结束清位。
(17) PG_head
含义:复合页(compound page)的头页。说明:尾页通过 compound_head 指向头页。
(18) PG_mappedtodisk
含义:文件页已分配磁盘块。用途:文件系统和写回路径优化判断。
(19) PG_reclaim
含义:该页被标记为可尽快回收。用途:回收器/写回策略协调。
(20) PG_swapbacked
含义:页后备是 swap/RAM 类型(匿名页等)。用途:与文件页区分,影响回收与写回策略。
(21) PG_unevictable
含义:不可驱逐页(unevictable LRU)。典型:mlock、特殊 pin 场景。
3. 各条件编译标志含义
(22) PG_mlocked(CONFIG_MMU)
含义:页被 mlock 锁定。用途:避免被交换/回收。
(23) PG_uncached(CONFIG_ARCH_USES_PG_UNCACHED)
含义:该页被按 uncached 属性映射。说明:仅某些架构使用。
(24) PG_hwpoison(CONFIG_MEMORY_FAILURE)
含义:硬件中毒页(ECC/内存故障)。规则:此页不可再安全访问,分配器与回收路径会特殊处理。
(25) PG_young(CONFIG_IDLE_PAGE_TRACKING)
含义:idle page tracking 的“近期访问”状态位。
(26) PG_idle(CONFIG_IDLE_PAGE_TRACKING)
含义:idle page tracking 的“空闲候选”状态位。
注: __NR_PAGEFLAGS 不是业务位,而是“pageflags 总数上界”。内核用它做掩码和校验,比如分配器在 prep/free 阶段检查非法残留标志。
4. 同位复用标志位
这些名字不是新增位,而是复用已有位,在特定页面上下文中解释成不同语义:
(1) PG_checked = PG_owner_priv_1
用于部分文件系统“已检查”语义。
(2) PG_swapcache = PG_owner_priv_1
用于 swap cache 页面标识。
(3) PG_fscache = PG_private_2
FS-Cache 使用。
(4) Xen 相关别名
PG_pinned / PG_foreign / PG_xen_remapped 等复用 owner_priv_1; PG_savepinned 复用 PG_dirty。
(5) PG_slob_free = PG_private
SLOB 空闲页语义。
(6) PG_double_map = PG_private_2
THP 相关,表示复合页既有 PMD 映射也有 PTE 映射历史/状态。
(7) PG_isolated = PG_reclaim
表示页处于隔离状态(例如迁移/回收隔离链)。
所以要点是:看位名不能脱离“页类型+子系统上下文”。
5. 和 page_type 标志的区别
还有另一套存储在 page->page_type 里的标志,属于 PageType 机制,比如:
PG_buddy
PG_offline
PG_kmemcg
PG_table
PG_guard
下面有补充。
6. 实战理解建议
读代码时先判断页类别,匿名页、文件页、slab 页、compound 头/尾页,不同类别对同一位解释不同。
再看访问宏,不要直接手写位运算,优先看 PageXxx/SetPageXxx/ClearPageXxx 路径,因为里面编码了 compound policy。
之后看配套位组合,例如脏页通常看 PG_dirty + PG_writeback;回收看 PG_lru/PG_active/PG_referenced/PG_unevictable。
7. 按功能分组
I/O 一致性与状态: PG_locked, PG_uptodate, PG_dirty, PG_writeback, PG_error, PG_mappedtodisk
回收与冷热: PG_lru, PG_active, PG_referenced, PG_workingset, PG_reclaim, PG_unevictable, PG_mlocked
页类型与特殊用途: PG_slab, PG_head, PG_swapbacked, PG_reserved, PG_arch_1, PG_uncached, PG_hwpoison
私有复用位: PG_owner_priv_1, PG_private, PG_private_2(以及它们的别名)
二、场景标志变化
1. 文件第一次读入
场景:进程访问文件映射或 read 触发缺页,页缓存里没有该页,于是从磁盘读入。
(1) 初始
新分配并加入 page cache 的页,通常会先被锁住,常见状态:PG_locked=1,PG_uptodate=0,PG_error=0,PG_dirty=0,PG_writeback=0,进入 LRU 后一般会有 PG_lru=1(是否 active 要看策略).
(2) 关键事件
发起 BIO 读取,I/O 完成且成功,标记页内容有效,变化(成功路径):
PG_uptodate:0 -> 1 (内容有效)
PG_locked:1 -> 0 (解锁,唤醒等待者)
PG_error:保持 0
PG_dirty:保持 0 (纯读不产生脏)
PG_writeback:读路径通常不依赖该位,保持 0
PG_referenced:可能在被访问后置 1 (由访问路径/回收路径维护)
PG_lru:通常保持 1
PG_active:可能随访问升温变 1 (不一定立刻).
(3) 注意
PG_writeback 主要描述“写回中”,不是“读进行中”。
PG_locked 在读入、回写、截断等都可能用此页锁做同步。
简单记忆:文件读入成功,PG_locked: 1 -> 0,PG_uptodate: 0 -> 1,PG_error: 保持 0,PG_dirty: 保持 0。
2. 匿名页回收
场景:内存回收扫描到匿名页,决定换出到 swap。匿名页被回收到 swap 前后,flags 变化:
(1) 初始
常见:PG_swapbacked=1 (匿名页是 swap-backed), 还在 LRU:PG_lru=1, 可能有 PG_referenced/PG_active(冷热状态), PG_dirty 可能是 1 (页内容相对 swap 条目“脏”),PG_mlocked=0 (若 mlock 则一般进不了这条路径).
(2) 关键事件
shrink_page_list 选中页, 写出到 swap(建立 swap entry,必要时写盘)。解除映射,回收物理页框,变化(典型成功回收):
a. 在页仍存活且要写出时:PG_writeback:0 -> 1 (写 swap 期间), 完成后 PG_writeback:1 -> 0
b. 页被最终回收进 buddy 后:该 struct page 将被 allocator 重新初始化,旧业务标志会清理。你不能再把“回收前 flags”当作稳定状态读取。
c. 对“仍在内存但已入 swapcache”的中间态:可能看到 PageSwapCache 语义位(复用位),用于 swap cache 管理, 但这不是独立 PG_xxx 新位,而是别名复用(owner_priv_1).
(3) 常见误区
误区 A:匿名页一定先 PG_dirty=1 才能 swap out。实际:是否需要写出取决于是否已有可用后备内容、是否在 swap cache、是否可直接丢弃等细节。
误区 B:PG_swapbacked 代表“已经在 swap 设备里”。实际:它表示后备类型是匿名/swap 类,不等于“此刻已落盘”。
简单记忆:匿名页成功回收到 swap: PG_swapbacked 通常一直是 1(直到页生命周期结束); PG_writeback: 0 -> 1 -> 0(若发生写出);最终页被释放后,旧 flags 不再可作为稳定语义。
注:“页相对 swap 条目脏” = 内存中的这页比它在 swap 槽里的版本更新,若要回收该页,需要把新内容重新写到 swap。对应两种情况:
(1) 若匿名页还没swapout到swap设备上过,相对于swap条目也是脏的。
(2) swapout到swap设备上后,又swapin后修改了,内存内容比 swap slot 更新,又变成“相对 swap 条目脏。
3. THP split
场景:一个透明大页(compound page)因为回收、mprotect、mapcount 条件等原因被拆分成普通 4K 页。THP split 前后, PG_head 和 PG_double_map 相关位怎么变
(1) split 前(THP 头页 + 尾页)
头页的 PG_head=1, 尾页通过 compound_head 指向头页。若存在“既有 PMD 映射又有 PTE 映射”的历史/状态,可能有 PG_double_map=1。注意,该位复用在首个 tail 页的 flags 存储(不是头页普通位).
(2) 关键事件
split_huge_page 系列路径执行, 将复合页拆成独立小页, mapcount、rmap、LRU、引用计数等按小页重建/修正.
(3) split 后
原头页:PG_head 清零(不再是复合页头), 原 tail 页 compound 关系解除,变成普通页语义。
PG_double_map:清零(复合映射优化状态失效).
各小页按实际映射和回收状态重新体现 PG_lru、PG_active、PG_referenced 等。之后每个 4K 页独立参与回收和映射计数。
(4) 常见误区
误区 A:PG_double_map 在头页。实际:它在首个 tail 页 flags 中承载(5.4 的实现细节)。
误区 B:split 只是清 PG_head。实际:还伴随一整套 mapcount/rmap/LRU 一致性调整,不只是单个位变化。
简单记忆:THP 拆分时,PG_head: 1 -> 0(原头页); PG_double_map: 可能 1 -> 0, 小页进入各自独立 flags 生命周期。
三、page->page_type
page->page_type 用于给某些特殊页打“类型标签”。源码注释典型条件:
(1) 这些页通常不会映射到用户态;
(2) 这些页也不是 PageSlab 页;
(3) page_type 初始化为 -1(即所有bit都是1),且采用“反向位语义”; 所谓反向位语义是指:对 page_type 来说,SetPageXxx 底层是清位, ClearPageXxx 底层是置位, 这是为了和 mapcount 复用时避免歧义,并保留溢出保护区间。所以你看到的 PG_xxx 名字在 page_type 里不是普通 flags 的直观“1表示有”语义,必须通过 PageXxx 宏判断,不要手算位。使用盘对岸宏如 PageBuddy()/__SetPageBuddy()/__ClearPageBuddy()。
page->page_type 里面常见类型位主要是这几个:PG_buddy、PG_offline、PG_kmemcg、PG_table、PG_guard。和 enum pageflags 中定义的用于 page->flags 的标志位长的很像。
//page-flags.h #define PAGE_TYPE_BASE 0xf0000000 //used to distinguish page_mapcount #define PAGE_MAPCOUNT_RESERVE -128 //Reserve 0x0000007f to catch underflows of page_mapcount #define PG_buddy 0x00000080 #define PG_offline 0x00000100 #define PG_kmemcg 0x00000200 #define PG_table 0x00000400 #define PG_guard 0x00000800
复用避免歧义:
在 struct page 里,_mapcount 和 page_type 是联合体复用关系。对可映射页面,这个位置常放 mapcount(映射计数)。对某些特殊页面,这个位置改作 page_type(类型标记)。mapcount 是计数值,不是位图,mapcount 在运行中会增减,可能出现接近边界的值。如果 page_type 也用“正向置位”方案(某位为 1 表示某类型),那某些 mapcount 数值在二进制上可能刚好长得像“类型位被置上了”。
采用反向位语义后,page_type 初始化为 -1(即全1)。page_type 体系里,设置某类型时不是把位设为 1,而是把对应位清为 0。这样做后,page_type 的有效模式会集中在一类“特殊形状”的值,如 PageBuddy(page) 的实现是 page->page_type & (PAGE_TYPE_BASE | PG_buddy) == PAGE_TYPE_BASE(0xf0000000),不容易和正常 mapcount 计数区间撞型。
溢出保护实现原理:
专门保留了一个区间(例如低位的一段保留值, 见 PAGE_MAPCOUNT_RESERVE)用于捕获 mapcount 下溢/异常。比如这一存储空间作为映射计数,异常减为-1了,再加上前面的反向编码,能降低“计数坏掉后误认成合法类型”的概率。本质是把编码空间人为分层:一层给计数,一层给类型,中间留缓冲带。
/* * (1) 作为联合体的mapcount(映射计数)异常减为-1了,得一直减到-128以后,才可能为真.--溢出保护 * (2) 作为联合体的page_type(页类型),只要清理了上面标志位的任意一bit(清理表示设置),就成立。 * 0xfff + 1 = 0 //==> 0xfff是 -1 * 0xf7f + 0x80 + 1 = 0 //==> 清0x80位后是 -129 */ static inline int page_has_type(struct page *page) { return (int)page->page_type < PAGE_MAPCOUNT_RESERVE; }
(1) PG_buddy
含义:页面是 buddy 系统里的空闲页。作用:页分配器用它识别并管理空闲块(伙伴合并/拆分)。
(2) PG_offline
含义:页面逻辑上处于离线状态。典型场景:内存热插拔、balloon 驱动膨胀等。说明:这类页内容可视为陈旧,不应被普通路径读写/转储。
(3) PG_kmemcg
含义:页面带有 kmemcg 计费属性。典型场景:由带 __GFP_ACCOUNT 语义的分配产生。说明:释放时会清掉,辅助内核内存 cgroup 统计。
(4) PG_table
含义:该页当前作为页表页使用。作用:MM子系统可快速识别页表页身份。
(5) PG_guard
含义:调试用途的 guard page(例如 debug_pagealloc)。作用:用于捕获越界访问、UAF 等调试场景。
posted on 2026-04-22 21:07 Hello-World3 阅读(4) 评论(0) 收藏 举报
浙公网安备 33010602011771号