内存管理-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)    收藏  举报

导航