Linux 内存管理 (7):page owner

文章目录

  • [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")

相关推荐
时空无限6 分钟前
EFK 中使用 ruby 和 javascript 脚本去掉日志中颜色字符详解
linux·javascript·elk·ruby
张火火isgudi7 小时前
fedora43 安装 nvidia 驱动以及开启视频编解码硬件加速
linux·运维·视频编解码·nvidia
IT19958 小时前
Qt笔记-使用SSH2进行远程连接linux服务器并上传文件
linux·服务器·笔记
XXYBMOOO8 小时前
内核驱动开发与用户级驱动开发:深度对比与应用场景解析
linux·c++·驱动开发·嵌入式硬件·fpga开发·硬件工程
lengjingzju9 小时前
一网打尽Linux IPC(三):System V IPC
linux·服务器·c语言
大聪明-PLUS9 小时前
如何编写你的第一个 Linux 内核模块
linux·嵌入式·arm·smarc
知识分享小能手10 小时前
Ubuntu入门学习教程,从入门到精通,Ubuntu 22.04文件压缩与解压缩知识点详解(12)
linux·学习·ubuntu
用户61354114601610 小时前
Krb5-libs-1.18.2-5.ky10.x86_64.rpm 安装失败怎么办?附详细步骤
linux
zhougl99611 小时前
Vuex 模块命名冲突:问题解析与完整解决方案
linux·服务器·apache
一世琉璃白_Y11 小时前
Ubuntu(VMware)虚拟机网络异常排查与解决方案
linux·网络·ubuntu