xref: /linux/tools/testing/selftests/mm/prctl_thp_disable.c (revision 681f45deca1c7f517299d032783f655e5f2c36b4)
1*681f45deSUsama Arif // SPDX-License-Identifier: GPL-2.0
2*681f45deSUsama Arif /*
3*681f45deSUsama Arif  * Basic tests for PR_GET/SET_THP_DISABLE prctl calls
4*681f45deSUsama Arif  *
5*681f45deSUsama Arif  * Author(s): Usama Arif <usamaarif642@gmail.com>
6*681f45deSUsama Arif  */
7*681f45deSUsama Arif #include <stdio.h>
8*681f45deSUsama Arif #include <stdlib.h>
9*681f45deSUsama Arif #include <string.h>
10*681f45deSUsama Arif #include <unistd.h>
11*681f45deSUsama Arif #include <sys/mman.h>
12*681f45deSUsama Arif #include <linux/mman.h>
13*681f45deSUsama Arif #include <sys/prctl.h>
14*681f45deSUsama Arif #include <sys/wait.h>
15*681f45deSUsama Arif 
16*681f45deSUsama Arif #include "../kselftest_harness.h"
17*681f45deSUsama Arif #include "thp_settings.h"
18*681f45deSUsama Arif #include "vm_util.h"
19*681f45deSUsama Arif 
20*681f45deSUsama Arif enum thp_collapse_type {
21*681f45deSUsama Arif 	THP_COLLAPSE_NONE,
22*681f45deSUsama Arif 	THP_COLLAPSE_MADV_NOHUGEPAGE,
23*681f45deSUsama Arif 	THP_COLLAPSE_MADV_HUGEPAGE,	/* MADV_HUGEPAGE before access */
24*681f45deSUsama Arif 	THP_COLLAPSE_MADV_COLLAPSE,	/* MADV_COLLAPSE after access */
25*681f45deSUsama Arif };
26*681f45deSUsama Arif 
27*681f45deSUsama Arif /*
28*681f45deSUsama Arif  * Function to mmap a buffer, fault it in, madvise it appropriately (before
29*681f45deSUsama Arif  * page fault for MADV_HUGE, and after for MADV_COLLAPSE), and check if the
30*681f45deSUsama Arif  * mmap region is huge.
31*681f45deSUsama Arif  * Returns:
32*681f45deSUsama Arif  * 0 if test doesn't give hugepage
33*681f45deSUsama Arif  * 1 if test gives a hugepage
34*681f45deSUsama Arif  * -errno if mmap fails
35*681f45deSUsama Arif  */
36*681f45deSUsama Arif static int test_mmap_thp(enum thp_collapse_type madvise_buf, size_t pmdsize)
37*681f45deSUsama Arif {
38*681f45deSUsama Arif 	char *mem, *mmap_mem;
39*681f45deSUsama Arif 	size_t mmap_size;
40*681f45deSUsama Arif 	int ret;
41*681f45deSUsama Arif 
42*681f45deSUsama Arif 	/* For alignment purposes, we need twice the THP size. */
43*681f45deSUsama Arif 	mmap_size = 2 * pmdsize;
44*681f45deSUsama Arif 	mmap_mem = (char *)mmap(NULL, mmap_size, PROT_READ | PROT_WRITE,
45*681f45deSUsama Arif 				    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
46*681f45deSUsama Arif 	if (mmap_mem == MAP_FAILED)
47*681f45deSUsama Arif 		return -errno;
48*681f45deSUsama Arif 
49*681f45deSUsama Arif 	/* We need a THP-aligned memory area. */
50*681f45deSUsama Arif 	mem = (char *)(((uintptr_t)mmap_mem + pmdsize) & ~(pmdsize - 1));
51*681f45deSUsama Arif 
52*681f45deSUsama Arif 	if (madvise_buf == THP_COLLAPSE_MADV_HUGEPAGE)
53*681f45deSUsama Arif 		madvise(mem, pmdsize, MADV_HUGEPAGE);
54*681f45deSUsama Arif 	else if (madvise_buf == THP_COLLAPSE_MADV_NOHUGEPAGE)
55*681f45deSUsama Arif 		madvise(mem, pmdsize, MADV_NOHUGEPAGE);
56*681f45deSUsama Arif 
57*681f45deSUsama Arif 	/* Ensure memory is allocated */
58*681f45deSUsama Arif 	memset(mem, 1, pmdsize);
59*681f45deSUsama Arif 
60*681f45deSUsama Arif 	if (madvise_buf == THP_COLLAPSE_MADV_COLLAPSE)
61*681f45deSUsama Arif 		madvise(mem, pmdsize, MADV_COLLAPSE);
62*681f45deSUsama Arif 
63*681f45deSUsama Arif 	/* HACK: make sure we have a separate VMA that we can check reliably. */
64*681f45deSUsama Arif 	mprotect(mem, pmdsize, PROT_READ);
65*681f45deSUsama Arif 
66*681f45deSUsama Arif 	ret = check_huge_anon(mem, 1, pmdsize);
67*681f45deSUsama Arif 	munmap(mmap_mem, mmap_size);
68*681f45deSUsama Arif 	return ret;
69*681f45deSUsama Arif }
70*681f45deSUsama Arif 
71*681f45deSUsama Arif static void prctl_thp_disable_completely_test(struct __test_metadata *const _metadata,
72*681f45deSUsama Arif 					      size_t pmdsize,
73*681f45deSUsama Arif 					      enum thp_enabled thp_policy)
74*681f45deSUsama Arif {
75*681f45deSUsama Arif 	ASSERT_EQ(prctl(PR_GET_THP_DISABLE, NULL, NULL, NULL, NULL), 1);
76*681f45deSUsama Arif 
77*681f45deSUsama Arif 	/* tests after prctl overrides global policy */
78*681f45deSUsama Arif 	ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_NONE, pmdsize), 0);
79*681f45deSUsama Arif 
80*681f45deSUsama Arif 	ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_NOHUGEPAGE, pmdsize), 0);
81*681f45deSUsama Arif 
82*681f45deSUsama Arif 	ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_HUGEPAGE, pmdsize), 0);
83*681f45deSUsama Arif 
84*681f45deSUsama Arif 	ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_COLLAPSE, pmdsize), 0);
85*681f45deSUsama Arif 
86*681f45deSUsama Arif 	/* Reset to global policy */
87*681f45deSUsama Arif 	ASSERT_EQ(prctl(PR_SET_THP_DISABLE, 0, NULL, NULL, NULL), 0);
88*681f45deSUsama Arif 
89*681f45deSUsama Arif 	/* tests after prctl is cleared, and only global policy is effective */
90*681f45deSUsama Arif 	ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_NONE, pmdsize),
91*681f45deSUsama Arif 		  thp_policy == THP_ALWAYS ? 1 : 0);
92*681f45deSUsama Arif 
93*681f45deSUsama Arif 	ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_NOHUGEPAGE, pmdsize), 0);
94*681f45deSUsama Arif 
95*681f45deSUsama Arif 	ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_HUGEPAGE, pmdsize),
96*681f45deSUsama Arif 		  thp_policy == THP_NEVER ? 0 : 1);
97*681f45deSUsama Arif 
98*681f45deSUsama Arif 	ASSERT_EQ(test_mmap_thp(THP_COLLAPSE_MADV_COLLAPSE, pmdsize), 1);
99*681f45deSUsama Arif }
100*681f45deSUsama Arif 
101*681f45deSUsama Arif FIXTURE(prctl_thp_disable_completely)
102*681f45deSUsama Arif {
103*681f45deSUsama Arif 	struct thp_settings settings;
104*681f45deSUsama Arif 	size_t pmdsize;
105*681f45deSUsama Arif };
106*681f45deSUsama Arif 
107*681f45deSUsama Arif FIXTURE_VARIANT(prctl_thp_disable_completely)
108*681f45deSUsama Arif {
109*681f45deSUsama Arif 	enum thp_enabled thp_policy;
110*681f45deSUsama Arif };
111*681f45deSUsama Arif 
112*681f45deSUsama Arif FIXTURE_VARIANT_ADD(prctl_thp_disable_completely, never)
113*681f45deSUsama Arif {
114*681f45deSUsama Arif 	.thp_policy = THP_NEVER,
115*681f45deSUsama Arif };
116*681f45deSUsama Arif 
117*681f45deSUsama Arif FIXTURE_VARIANT_ADD(prctl_thp_disable_completely, madvise)
118*681f45deSUsama Arif {
119*681f45deSUsama Arif 	.thp_policy = THP_MADVISE,
120*681f45deSUsama Arif };
121*681f45deSUsama Arif 
122*681f45deSUsama Arif FIXTURE_VARIANT_ADD(prctl_thp_disable_completely, always)
123*681f45deSUsama Arif {
124*681f45deSUsama Arif 	.thp_policy = THP_ALWAYS,
125*681f45deSUsama Arif };
126*681f45deSUsama Arif 
127*681f45deSUsama Arif FIXTURE_SETUP(prctl_thp_disable_completely)
128*681f45deSUsama Arif {
129*681f45deSUsama Arif 	if (!thp_available())
130*681f45deSUsama Arif 		SKIP(return, "Transparent Hugepages not available\n");
131*681f45deSUsama Arif 
132*681f45deSUsama Arif 	self->pmdsize = read_pmd_pagesize();
133*681f45deSUsama Arif 	if (!self->pmdsize)
134*681f45deSUsama Arif 		SKIP(return, "Unable to read PMD size\n");
135*681f45deSUsama Arif 
136*681f45deSUsama Arif 	if (prctl(PR_SET_THP_DISABLE, 1, NULL, NULL, NULL))
137*681f45deSUsama Arif 		SKIP(return, "Unable to disable THPs completely for the process\n");
138*681f45deSUsama Arif 
139*681f45deSUsama Arif 	thp_save_settings();
140*681f45deSUsama Arif 	thp_read_settings(&self->settings);
141*681f45deSUsama Arif 	self->settings.thp_enabled = variant->thp_policy;
142*681f45deSUsama Arif 	self->settings.hugepages[sz2ord(self->pmdsize, getpagesize())].enabled = THP_INHERIT;
143*681f45deSUsama Arif 	thp_write_settings(&self->settings);
144*681f45deSUsama Arif }
145*681f45deSUsama Arif 
146*681f45deSUsama Arif FIXTURE_TEARDOWN(prctl_thp_disable_completely)
147*681f45deSUsama Arif {
148*681f45deSUsama Arif 	thp_restore_settings();
149*681f45deSUsama Arif }
150*681f45deSUsama Arif 
151*681f45deSUsama Arif TEST_F(prctl_thp_disable_completely, nofork)
152*681f45deSUsama Arif {
153*681f45deSUsama Arif 	prctl_thp_disable_completely_test(_metadata, self->pmdsize, variant->thp_policy);
154*681f45deSUsama Arif }
155*681f45deSUsama Arif 
156*681f45deSUsama Arif TEST_F(prctl_thp_disable_completely, fork)
157*681f45deSUsama Arif {
158*681f45deSUsama Arif 	int ret = 0;
159*681f45deSUsama Arif 	pid_t pid;
160*681f45deSUsama Arif 
161*681f45deSUsama Arif 	/* Make sure prctl changes are carried across fork */
162*681f45deSUsama Arif 	pid = fork();
163*681f45deSUsama Arif 	ASSERT_GE(pid, 0);
164*681f45deSUsama Arif 
165*681f45deSUsama Arif 	if (!pid) {
166*681f45deSUsama Arif 		prctl_thp_disable_completely_test(_metadata, self->pmdsize, variant->thp_policy);
167*681f45deSUsama Arif 		return;
168*681f45deSUsama Arif 	}
169*681f45deSUsama Arif 
170*681f45deSUsama Arif 	wait(&ret);
171*681f45deSUsama Arif 	if (WIFEXITED(ret))
172*681f45deSUsama Arif 		ret = WEXITSTATUS(ret);
173*681f45deSUsama Arif 	else
174*681f45deSUsama Arif 		ret = -EINVAL;
175*681f45deSUsama Arif 	ASSERT_EQ(ret, 0);
176*681f45deSUsama Arif }
177*681f45deSUsama Arif 
178*681f45deSUsama Arif TEST_HARNESS_MAIN
179