内存管理-70-page_pinner-1-理论
基于 kernel-6.1
一、简介
CONFIG_PAGE_PINNER:回答“这页为什么迁不走,可能被谁长期持有”。目标是排查"页迁移失败/alloc_contig 失败时,谁在持有该页"。配置项定义在 Kconfig.debug,实现在 page_pinner.c。
config PAGE_PINNER bool "Track page pinner" depends on DEBUG_KERNEL && STACKTRACE_SUPPORT select DEBUG_FS select STACKTRACE select STACKDEPOT select PAGE_EXTENSION help 此功能用于跟踪页面的调用链,有助于查找页面迁移失败的原因。即使您在构建中包含此功能, 它默认也是禁用的。您需要将"page_pinner=on"传递给启动参数才能启用它。启用后 会占用相当多的内存。
(1) 它记录什么
a. 失败检测时的调用栈;
b. 页状态快照:pfn、count、mapcount、mapping、flags;
c. 时间信息(失败时刻、从失败到 put 的持续时间);
相关结构见 page_pinner.c 中的 struct page_pinner。
(2) 触发时机(很关键)
a. 页隔离失败时标记:见 page_isolation.c: test_pages_isolated() --> page_pinner_failure_detect(pfn)
b. alloc_contig 迁移失败(-EBUSY)时标记:见 page_alloc.c: __alloc_contig_migrate_range() --> page_pinner_failure_detect(page);
c. put_page 路径会回填“持有时长”记录:见 mm.h: put_page_testzero() --> page_pinner_put_page(page);
d. 页面最终 free 时清理状态:见 page_alloc.c: free_pages_prepare() --> free_page_pinner(page, order);
(3) 启用与输出
编译 CONFIG_PAGE_PINNER, 启动参数 page_pinner=on(默认关闭),见 page_pinner.c: early_param("page_pinner", early_page_pinner_param);
提供 debugfs 有:
/sys/kernel/debug/page_pinner/buffer /sys/kernel/debug/page_pinner/failure_tracking /sys/kernel/debug/page_pinner/buffer_size
创建位置见 page_pinner_init()。
(4) 开销特征
同样依赖 PAGE_EXTENSION/STACKDEPOT/STACKTRACE,见 Kconfig.debug。更偏"问题触发时采样记录",用于迁移失败现场,不是常规性能功能.
二、 CONFIG_PAGE_PINNER 的作用
1. 一句话总结
PAGE_PINNER 追踪那些"钉住"(pin)页面导致内存迁移失败的元凶——记录是谁、在什么时候导致某个页面无法被迁移,帮助诊断 CMA 分配失败、内存规整(compaction)失败、内存热拔除失败等问题。
2. 解决什么问题
当 alloc_contig_range(如 cma_alloc)或 compaction 需要迁移某个页面但失败时,通常的日志只会告诉你"迁移失败了",但不告诉你是谁持有了这个页面的引用导致无法迁移。
典型场景:
camera HAL 调用 cma_alloc() 分配连续内存,需要迁移 CMA 区域上的 MOVABLE 页面,某些页面 refcount > 1,无法迁移,cma_alloc 失败,你想知道:是谁 get_page / pin_user_page 了这些页面?没有 PAGE_PINNER:只知道"某个页面迁移失败了",有了 PAGE_PINNER:记录了完整调用栈——谁 pin 了它、pin 了多久、什么时候释放的。
3. 工作机制
3.1 核心数据结构
struct page_pinner { depot_stack_handle_t handle; /* pin 操作的调用栈(stack depot 压缩存储) */ u64 ts_usec; /* 首次被 pin 的时间戳(微秒) */ atomic_t count; /* pin 计数 */ };
每个 struct page 通过 page_ext(页面扩展结构)挂载一个 page_pinner。
3.2 三个关键事件
------------------------------------------------------------------------------------------------------------------------ 事件 函数 触发时机 ------------------------------------------------------------------------------------------------------------------------ 迁移失败检测 page_pinner_failure_detect(page) alloc_contig_range 中迁移页面失败(refcount > 1 的页面) 页面被释放 free_page_pinner(page, order) free_pages_prepare 中——被标记过的页面最终被释放时记录 引用减少 page_pinner_put_page(page) put_page 路径中——被标记过的页面引用计数下降时记录 ------------------------------------------------------------------------------------------------------------------------
3.3 事件流程图
(1) 正常运行:
某驱动 get_page(page) → page refcount 增加 → 页面被 "pin" 住
(2) CMA 分配时:
cma_alloc → alloc_contig_range → migrate_pages → 迁移某页面失败(refcount > 1) → page_pinner_failure_detect(page) ← 记录事件 + 调用栈 ├── 标记 PAGE_EXT_PINNER_MIGRATION_FAILED ├── 保存此时的调用栈到 stack depot ├── 记录时间戳 └── 写入环形 buffer
(3) 后续:
某驱动最终 put_page(page) → refcount 减少 → page_pinner_put_page(page) ← 记录"被 pin 了多久" └── 计算 elapsed = now - ts_usec,写入 buffer 页面最终被释放 → free_page_pinner(page, order) ← 记录释放事件 └── 清除标记,写入 buffer
4. 如何使用
4.1 开启
内核启动参数加:page_pinner, 或在代码中 CONFIG_PAGE_PINNER=y 编译。
4.2 查看 debugfs
# 查看记录的 pin 事件(最新的在前) cat /sys/kernel/debug/page_pinner/buffer # 控制是否追踪 echo 1 > /sys/kernel/debug/page_pinner/failure_tracking # 开启 echo 0 > /sys/kernel/debug/page_pinner/failure_tracking # 关闭 # 调整 buffer 大小(条目数) echo 8192 > /sys/kernel/debug/page_pinner/buffer_size
4.3 输出示例
Failure detected at [ 123.456789] PFN 0x12345 Block 18 count 3 mapcount 1 mapping ffffffc012340000 Flags 0x4000000000086c(referenced|uptodate|lru|active|mappedtodisk) migrate_pages+0x1a4/0x5b0 alloc_contig_range+0x234/0x4c0 cma_alloc+0x1f0/0x3a0 dma_alloc_contiguous+0x78/0xf0 mtk_camera_alloc+0x120/0x200 At least, pinned for 5234123 us PFN 0x12345 Block 18 count 2 mapcount 1 mapping ffffffc012340000 Flags 0x4000000000086c put_page+0x24/0x40 unmap_page_range+0x5b0/0x890 do_munmap+0x1f0/0x440 __arm64_sys_munmap+0x48/0x60 Freed at [ 128.690912] PFN 0x12345 Block 18 count 0 mapcount 0 mapping 0000000000000000 Flags 0x0 free_pages_prepare+0x1c0/0x400 free_unref_page+0x3c/0xc0
从这个输出可以看出:
a. 迁移失败原因:cma_alloc → alloc_contig_range 尝试迁移 PFN 0x12345 时失败,因为 refcount=3
b. 谁持有引用:某个进程映射了此页(mapcount=1),导致无法迁移
c. pin 了多久:5.2 秒后才被释放(通过 munmap → put_page)
d. 最终释放:128 秒时页面终于被释放
5. 与 PAGE_OWNER 的区别
---------------------------------------------------------------------------------------- 特性 PAGE_OWNER PAGE_PINNER ---------------------------------------------------------------------------------------- 追踪目标 谁分配了页面 谁 pin 住了页面导致迁移失败 记录时机 分配时 迁移失败时 解决问题 内存泄漏定位 CMA/compaction 失败定位 记录内容 分配调用栈 pin 调用栈 + pin 时长 输出位置 /sys/kernel/debug/page_owner /sys/kernel/debug/page_pinner/buffer ----------------------------------------------------------------------------------------
6. 在 free_pages_prepare 中的角色
//free_pages_prepare 中: free_page_pinner(page, order);
这行代码的作用是:如果这个页面之前被标记为"迁移失败"(PAGE_EXT_PINNER_MIGRATION_FAILED),在释放时记录一条"Freed at"事件,表示页面终于自由了,然后清除标记。
这帮助你了解:从迁移失败到页面最终被释放,经过了多长时间——这个时间越长,说明 pin 住页面的持有者越"顽固",对 CMA/compaction 的影响越大。
7. 典型应用场景
(1) CMA 分配失败调试:camera/display 驱动 cma_alloc 失败时,查看谁 pin 住了 CMA 区域的页面;
(2) 内存碎片化分析:compaction 反复失败时,找出哪些页面长期不可迁移;
(3) 内存热拔除失败:服务器场景下拔内存条失败,定位是什么进程/驱动持有了那些页面;
posted on 2026-07-09 16:32 Hello-World3 阅读(19) 评论(0) 收藏 举报
浙公网安备 33010602011771号