文章目录
- [1. 前言](#1. 前言)
- [2. 概述](#2. 概述)
- [3. 实现](#3. 实现)
-
- [3.1 初始化 page owner](#3.1 初始化 page owner)
- [3.2 分配时记录 page 的 page owner 信息](#3.2 分配时记录 page 的 page owner 信息)
- [4. 使用](#4. 使用)
- [5. 参考资料](#5. 参考资料)
1. 前言
限于作者能力水平,本文可能存在谬误,因此而给读者带来的损失,作者不做任何承诺。
2. 概述
page owner 用于追踪每个页面的分配者。它可用于调试内存泄漏或查找内存占用者。当发生分配时,分配信息(如调用栈和 page order)会被存储在每个页面的特定存储区中。当需要了解所有页面的状态时,我们可以获取并分析这些信息。
尽管已有用于追踪页面分配/释放的跟踪点,但将其用于分析具体分配者较为复杂。需扩大跟踪缓冲区以避免用户空间程序启动前的数据覆盖。且启动的程序会持续转储跟踪缓冲区供后续分析,这种操作会改变系统行为模式,相比直接保留内存数据更不利于调试。
page owner 机制还可拓展多种用途。例如通过各页面的 gfp 标志信息,可获取精确的碎片化统计数据------该功能在启用 page owner 时已实现并激活。其他应用场景同样值得探索。
page owner 功能默认处于禁用状态。若需启用,需开启内核配置 CONFIG_PAGE_OWNER=y,并在内核启动命令行中添加 page_owner=on。若内核在编译时启用了页面 page owner 功能,但在运行时因未启用启动选项而禁用,则运行时开销微乎其微。若在运行时禁用,则无需内存存储所有者信息,因此不存在运行时内存开销。此外,page owner 机制仅在页面分配器的热点路径中插入两个低概率分支。若未启用该功能,分配过程将与未启用页面所有者的内核相同。这两个低概率分支不应影响分配性能,尤其当 jump label 功能可用时。以下是该功能导致的内核代码大小变化。
存在一个由实现细节引发的注意事项:page owner 通过 struct page 扩展(struct page_ext)将信息存储至内存中。在稀疏内存系统中,该内存的初始化时间晚于页面分配器的启动时间,因此在初始化完成前,可能已有大量页面被分配却未携带所有者信息。为解决此问题,初始化阶段会对这些早期分配的页面进行检测并标记为已分配状态。虽然这不能保证它们拥有正确的拥有者信息,但至少能更准确地判断页面是否已被分配。在 2GB 内存的 x86-64 虚拟机环境中,共捕获并标记了 13343 个早期分配页面------尽管其中大部分源自 struct page 扩展功能。但不管怎样,此后所有页面均处于可追踪状态。
3. 实现
3.1 初始化 page owner
c
/* mm/page_owner.c */
static const struct file_operations proc_page_owner_operations = {
.read = read_page_owner,
};
static int __init pageowner_init(void)
{
struct dentry *dentry;
/* page owner 默认是关闭的,需通过内核命令行参数 "page_owner=on" 开启 */
if (!static_branch_unlikely(&page_owner_inited)) {
pr_info("page_owner is disabled\n");
return 0;
}
/* 创建 /sys/kernel/debug/page_owner */
dentry = debugfs_create_file("page_owner", S_IRUSR, NULL,
NULL, &proc_page_owner_operations);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
return 0;
}
late_initcall(pageowner_init)
初始化创建 /sys/kernel/debug/page_owner 文件,用于导出分配 page 的 page owner 信息。导出新的接口函数为 read_page_owner(),感兴趣的读者可以进一步查看其细节。read_page_owner() 函数的主干逻辑比较简单,这里就不做展开了。
3.2 分配时记录 page 的 page owner 信息
c
/* mm/page_owner.c */
/* 各种 buddy page 分配场景 */
static struct page *
get_page_from_freelist(gfp_t gfp_mask, unsigned int order, int alloc_flags,
const struct alloc_context *ac)
{
...
try_this_zone:
/* 从内存区域 @zone 的 ac->migratetype 类型页面空闲列表 分配一个页面 */
page = rmqueue(ac->preferred_zoneref->zone, zone, order,
gfp_mask, alloc_flags, ac->migratetype);
if (page) { /* 分配页面成功 */
prep_new_page(page, order, gfp_mask, alloc_flags);
...
return page; /* 返回分配的页面 */
}
...
}
prep_new_page()
post_alloc_hook()
set_page_owner()
__set_page_owner()
noinline void __set_page_owner(struct page *page, unsigned int order,
gfp_t gfp_mask)
{
struct page_ext *page_ext = lookup_page_ext(page);
depot_stack_handle_t handle;
if (unlikely(!page_ext))
return;
handle = save_stack(gfp_mask); /* 记录 页面 分配调用栈信息 */
__set_page_owner_handle(page_ext, handle, order, gfp_mask);
}
static inline void __set_page_owner_handle(struct page_ext *page_ext,
depot_stack_handle_t handle, unsigned int order, gfp_t gfp_mask)
{
struct page_owner *page_owner;
page_owner = get_page_owner(page_ext);
page_owner->handle = handle;
page_owner->order = order;
page_owner->gfp_mask = gfp_mask;
page_owner->last_migrate_reason = -1;
__set_bit(PAGE_EXT_OWNER, &page_ext->flags);
}
4. 使用
在内核命令行添加 page_owner=on,在 Linux 系统启动后,观察 page owner 信息:
bash
# cat page_owner
Page allocated via order 0, mask 0x620848(GFP_NOFS|__GFP_NOFAIL|__GFP_HARDWALL|__GFP_MOVABLE)
PFN 393216 type Movable Block 384 type Movable Flags 0x1064(referenced|lru|active|private)
__alloc_pages_nodemask+0x100/0x1128
pagecache_get_page+0xfc/0x32c
__getblk_gfp+0xbc/0x29c
__breadahead+0x20/0x6c
__ext4_get_inode_loc+0x520/0x588
__ext4_iget+0xcc/0xddc
ext4_get_journal_inode+0x20/0xd4
ext4_fill_super+0x2884/0x3818
mount_bdev+0x15c/0x188
ext4_mount+0x18/0x20
mount_fs+0x14/0xa8
vfs_kern_mount+0x4c/0x10c
do_mount+0x180/0xc24
ksys_mount+0x8c/0xbc
mount_block_root+0x14c/0x2c0
mount_root+0x148/0x164
Page allocated via order 0, mask 0x620848(GFP_NOFS|__GFP_NOFAIL|__GFP_HARDWALL|__GFP_MOVABLE)
PFN 393217 type Movable Block 384 type Movable Flags 0x1064(referenced|lru|active|private)
__alloc_pages_nodemask+0x100/0x1128
pagecache_get_page+0xfc/0x32c
__getblk_gfp+0xbc/0x29c
__breadahead+0x20/0x6c
__ext4_get_inode_loc+0x520/0x588
__ext4_iget+0xcc/0xddc
ext4_get_journal_inode+0x20/0xd4
ext4_fill_super+0x2884/0x3818
mount_bdev+0x15c/0x188
ext4_mount+0x18/0x20
mount_fs+0x14/0xa8
vfs_kern_mount+0x4c/0x10c
do_mount+0x180/0xc24
ksys_mount+0x8c/0xbc
mount_block_root+0x14c/0x2c0
mount_root+0x148/0x164
[......]
可以看到,page owner 导出了分配 page 的 order、页框号、分配 flags 掩码、调用栈 等等信息。
内核还提供工具 tools/vm/page_owner_sort.c,可以对导出的 page owner 信息进行排序:
bash
cd tools/vm
make page_owner_sort
bash
cat /sys/kernel/debug/page_owner > page_owner_full.txt
grep -v ^PFN page_owner_full.txt > page_owner.txt
./page_owner_sort page_owner.txt sorted_page_owner.txt
5. 参考资料
1\] [page owner: Tracking about who allocated each page](https://docs.kernel.org/4.20/vm/page_owner.html "page owner: Tracking about who allocated each page")