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

相关推荐
爱莉希雅&&&4 小时前
linux中MySQL数据库备份恢复的四种方法(更新中)
linux·数据库·mysql·数据库备份·mysqldumper
coppher4 小时前
Ubuntu 22.04 amd64 离线安装 Docker 完整教程
linux·docker
xyz5995 小时前
如何在 WSL 中删除指定版本的 Ubuntu 以及安装
linux·运维·ubuntu
亚空间仓鼠5 小时前
OpenEuler系统常用服务(五)
linux·运维·服务器·网络
minji...6 小时前
Linux 线程同步与互斥(二) 线程同步,条件变量,pthread_cond_init/wait/signal/broadcast
linux·运维·开发语言·jvm·数据结构·c++
虚伪的空想家6 小时前
k8s集群configmap和secrets备份脚本
linux·容器·kubernetes
the sun346 小时前
从 QEMU 直接启动到 U-Boot 引导:嵌入式 Linux 启动流程的本质差异
linux·运维·服务器
草莓熊Lotso7 小时前
【Linux 线程进阶】进程 vs 线程资源划分 + 线程控制全详解
java·linux·运维·服务器·数据库·c++·mysql
ShineWinsu7 小时前
对于Linux:文件操作以及文件IO的解析
linux·c++·面试·笔试·io·shell·文件操作
-SGlow-7 小时前
Linux相关概念和易错知识点(52)(基于System V的信号量和消息队列)
linux·运维·服务器