xref: /linux/Documentation/translations/zh_CN/mm/page_owner.rst (revision fcc79e1714e8c2b8e216dc3149812edd37884eef)
1:Original: Documentation/mm/page_owner.rst
2
3:翻译:
4
5 司延腾 Yanteng Si <siyanteng@loongson.cn>
6
7:校译:
8
9
10================================
11page owner: 跟踪谁分配的每个页面
12================================
13
14概述
15====
16
17page owner是用来追踪谁分配的每一个页面。它可以用来调试内存泄漏或找到内存占用者。
18当分配发生时,有关分配的信息,如调用堆栈和页面的顺序被存储到每个页面的特定存储中。
19当我们需要了解所有页面的状态时,我们可以获得并分析这些信息。
20
21尽管我们已经有了追踪页面分配/释放的tracepoint,但用它来分析谁分配的每个页面是
22相当复杂的。我们需要扩大跟踪缓冲区,以防止在用户空间程序启动前出现重叠。而且,启
23动的程序会不断地将跟踪缓冲区转出,供以后分析,这将会改变系统的行为,会产生更多的
24可能性,而不是仅仅保留在内存中,所以不利于调试。
25
26页面所有者也可以用于各种目的。例如,可以通过每个页面的gfp标志信息获得精确的碎片
27统计。如果启用了page owner,它就已经实现并激活了。我们非常欢迎其他用途。
28
29它也可以用来显示所有的栈以及它们当前分配的基础页面数,这让我们能够快速了解内存的
30使用情况,而无需浏览所有页面并匹配分配和释放操作。
31
32page owner在默认情况下是禁用的。所以,如果你想使用它,你需要在你的启动cmdline
33中加入"page_owner=on"。如果内核是用page owner构建的,并且由于没有启用启动
34选项而在运行时禁用page owner,那么运行时的开销是很小的。如果在运行时禁用,它不
35需要内存来存储所有者信息,所以没有运行时内存开销。而且,页面所有者在页面分配器的
36热路径中只插入了两个不可能的分支,如果不启用,那么分配就会像没有页面所有者的内核
37一样进行。这两个不可能的分支应该不会影响到分配的性能,特别是在静态键跳转标签修补
38功能可用的情况下。以下是由于这个功能而导致的内核代码大小的变化。
39
40尽管启用page owner会使内核的大小增加几千字节,但这些代码大部分都在页面分配器和
41热路径之外。构建带有page owner的内核,并在需要时打开它,将是调试内核内存问题的
42最佳选择。
43
44有一个问题是由实现细节引起的。页所有者将信息存储到struct page扩展的内存中。这
45个内存的初始化时间比稀疏内存系统中的页面分配器启动的时间要晚一些,所以,在初始化
46之前,许多页面可以被分配,但它们没有所有者信息。为了解决这个问题,这些早期分配的
47页面在初始化阶段被调查并标记为分配。虽然这并不意味着它们有正确的所有者信息,但至
48少,我们可以更准确地判断该页是否被分配。在2GB内存的x86-64虚拟机上,有13343
49个早期分配的页面被捕捉和标记,尽管它们大部分是由结构页扩展功能分配的。总之,在这
50之后,没有任何页面处于未追踪状态。
51
52使用方法
53========
54
551) 构建用户空间的帮助::
56
57	cd tools/mm
58	make page_owner_sort
59
602) 启用page owner: 添加 "page_owner=on" 到 boot cmdline.
61
623) 做你想调试的工作。
63
644) 分析来自页面所有者的信息::
65
66	cat /sys/kernel/debug/page_owner_stacks/show_stacks > stacks.txt
67	cat stacks.txt
68	 post_alloc_hook+0x177/0x1a0
69	 get_page_from_freelist+0xd01/0xd80
70	 __alloc_pages+0x39e/0x7e0
71	 allocate_slab+0xbc/0x3f0
72	 ___slab_alloc+0x528/0x8a0
73	 kmem_cache_alloc+0x224/0x3b0
74	 sk_prot_alloc+0x58/0x1a0
75	 sk_alloc+0x32/0x4f0
76	 inet_create+0x427/0xb50
77	 __sock_create+0x2e4/0x650
78	 inet_ctl_sock_create+0x30/0x180
79	 igmp_net_init+0xc1/0x130
80	 ops_init+0x167/0x410
81	 setup_net+0x304/0xa60
82	 copy_net_ns+0x29b/0x4a0
83	 create_new_namespaces+0x4a1/0x820
84	nr_base_pages: 16
85	...
86	...
87	echo 7000 > /sys/kernel/debug/page_owner_stacks/count_threshold
88	cat /sys/kernel/debug/page_owner_stacks/show_stacks> stacks_7000.txt
89	cat stacks_7000.txt
90	 post_alloc_hook+0x177/0x1a0
91	 get_page_from_freelist+0xd01/0xd80
92	 __alloc_pages+0x39e/0x7e0
93	 alloc_pages_mpol+0x22e/0x490
94	 folio_alloc+0xd5/0x110
95	 filemap_alloc_folio+0x78/0x230
96	 page_cache_ra_order+0x287/0x6f0
97	 filemap_get_pages+0x517/0x1160
98	 filemap_read+0x304/0x9f0
99	 xfs_file_buffered_read+0xe6/0x1d0 [xfs]
100	 xfs_file_read_iter+0x1f0/0x380 [xfs]
101	 __kernel_read+0x3b9/0x730
102	 kernel_read_file+0x309/0x4d0
103	 __do_sys_finit_module+0x381/0x730
104	 do_syscall_64+0x8d/0x150
105	 entry_SYSCALL_64_after_hwframe+0x62/0x6a
106	nr_base_pages: 20824
107	...
108
109	cat /sys/kernel/debug/page_owner > page_owner_full.txt
110	./page_owner_sort page_owner_full.txt sorted_page_owner.txt
111
112   ``page_owner_full.txt`` 的一般输出情况如下::
113
114	Page allocated via order XXX, ...
115	PFN XXX ...
116	// 栈详情
117
118	Page allocated via order XXX, ...
119	PFN XXX ...
120	// 栈详情
121    默认情况下,它将以一个给定的pfn开始,做完整的pfn转储,且page_owner支持fseek。
122
123    FILE *fp = fopen("/sys/kernel/debug/page_owner", "r");
124    fseek(fp, pfn_start, SEEK_SET);
125
126   ``page_owner_sort`` 工具忽略了 ``PFN`` 行,将剩余的行放在buf中,使用regexp提
127   取页序值,计算buf的次数和页数,最后根据参数进行排序。
128
129   在 ``sorted_page_owner.txt`` 中可以看到关于谁分配了每个页面的结果。一般输出::
130
131	XXX times, XXX pages:
132	Page allocated via order XXX, ...
133	// Detailed stack
134
135   默认情况下, ``page_owner_sort`` 是根据buf的时间来排序的。如果你想
136   按buf的页数排序,请使用-m参数。详细的参数是:
137
138   基本函数::
139
140	排序:
141		-a		按内存分配时间排序
142		-m		按总内存排序
143		-p		按pid排序。
144		-P		按tgid排序。
145		-n		按任务命令名称排序。
146		-r		按内存释放时间排序。
147		-s		按堆栈跟踪排序。
148		-t		按时间排序(默认)。
149       --sort <order> 指定排序顺序。排序的语法是[+|-]key[,[+|-]key[,...]]。从
150       **标准格式指定器**那一节选择一个键。"+"是可选的,因为默认的方向是数字或
151       词法的增加。允许混合使用缩写和完整格式的键。
152
153        例子:
154				./page_owner_sort <input> <output> --sort=n,+pid,-tgid
155				./page_owner_sort <input> <output> --sort=at
156
157    其它函数::
158
159	剔除:
160		--cull <rules>
161		        指定剔除规则。剔除的语法是key[,key[,...]]。从**标准格式指定器**
162				部分选择一个多字母键。
163		<rules>是一个以逗号分隔的列表形式的单一参数,它提供了一种指定单个剔除规则的
164		方法。 识别的关键字在下面的**标准格式指定器**部分有描述。<规则>可以通过键的
165		序列k1,k2,...来指定,在下面的标准排序键部分有描述。允许混合使用简写和完整形
166		式的键。
167
168		Examples:
169				./page_owner_sort <input> <output> --cull=stacktrace
170				./page_owner_sort <input> <output> --cull=st,pid,name
171				./page_owner_sort <input> <output> --cull=n,f
172
173	过滤:
174		-f		过滤掉内存已被释放的块的信息。
175
176	选择:
177		--pid <pidlist>		按pid选择。这将选择进程ID号出现在<pidlist>中的块。
178		--tgid <tgidlist>	按tgid选择。这将选择其线程组ID号出现在<tgidlist>
179		                    中的块。
180		--name <cmdlist>	按任务命令名称选择。这将选择其任务命令名称出现在
181		                    <cmdlist>中的区块。
182
183		<pidlist>, <tgidlist>, <cmdlist>是以逗号分隔的列表形式的单个参数,
184		它提供了一种指定单个选择规则的方法。
185
186
187		例子:
188				./page_owner_sort <input> <output> --pid=1
189				./page_owner_sort <input> <output> --tgid=1,2,3
190				./page_owner_sort <input> <output> --name name1,name2
191
192标准格式指定器
193==============
194::
195
196  --sort的选项:
197
198	短键		长键		描述
199	p		pid		进程ID
200	tg		tgid		线程组ID
201	n		name		任务命令名称
202	st		stacktrace	页面分配的堆栈跟踪
203	T		txt		块的全文
204	ft		free_ts		页面释放时的时间戳
205	at		alloc_ts	页面被分配时的时间戳
206	ator		allocator	页面的内存分配器
207
208  --curl的选项:
209
210	短键		长键		描述
211	p		pid		进程ID
212	tg		tgid		线程组ID
213	n		name		任务命令名称
214	f		free		该页是否已经释放
215	st		stacktrace	页面分配的堆栈跟踪
216	ator		allocator	页面的内存分配器
217