xref: /linux/tools/testing/selftests/mm/hugetlb-madvise.c (revision ab475966455ce285c2c9978a3e3bfe97d75ff8d4)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * hugepage-madvise:
4  *
5  * Basic functional testing of madvise MADV_DONTNEED and MADV_REMOVE
6  * on hugetlb mappings.
7  *
8  * Before running this test, make sure the administrator has pre-allocated
9  * at least MIN_FREE_PAGES hugetlb pages and they are free.  In addition,
10  * the test takes an argument that is the path to a file in a hugetlbfs
11  * filesystem.  Therefore, a hugetlbfs filesystem must be mounted on some
12  * directory.
13  */
14 
15 #define _GNU_SOURCE
16 #include <stdlib.h>
17 #include <stdio.h>
18 #include <unistd.h>
19 #include <sys/mman.h>
20 #include <fcntl.h>
21 #include "vm_util.h"
22 
23 #define MIN_FREE_PAGES	20
24 #define NR_HUGE_PAGES	10	/* common number of pages to map/allocate */
25 
26 #define validate_free_pages(exp_free)					\
27 	do {								\
28 		int fhp = get_free_hugepages();				\
29 		if (fhp != (exp_free)) {				\
30 			printf("Unexpected number of free huge "	\
31 				"pages line %d\n", __LINE__);		\
32 			exit(1);					\
33 		}							\
34 	} while (0)
35 
36 unsigned long huge_page_size;
37 unsigned long base_page_size;
38 
39 void write_fault_pages(void *addr, unsigned long nr_pages)
40 {
41 	unsigned long i;
42 
43 	for (i = 0; i < nr_pages; i++)
44 		*((unsigned long *)(addr + (i * huge_page_size))) = i;
45 }
46 
47 void read_fault_pages(void *addr, unsigned long nr_pages)
48 {
49 	volatile unsigned long dummy = 0;
50 	unsigned long i;
51 
52 	for (i = 0; i < nr_pages; i++) {
53 		dummy += *((unsigned long *)(addr + (i * huge_page_size)));
54 
55 		/* Prevent the compiler from optimizing out the entire loop: */
56 		asm volatile("" : "+r" (dummy));
57 	}
58 }
59 
60 int main(int argc, char **argv)
61 {
62 	unsigned long free_hugepages;
63 	void *addr, *addr2;
64 	int fd;
65 	int ret;
66 
67 	huge_page_size = default_huge_page_size();
68 	if (!huge_page_size) {
69 		printf("Unable to determine huge page size, exiting!\n");
70 		exit(1);
71 	}
72 	base_page_size = sysconf(_SC_PAGE_SIZE);
73 	if (!huge_page_size) {
74 		printf("Unable to determine base page size, exiting!\n");
75 		exit(1);
76 	}
77 
78 	free_hugepages = get_free_hugepages();
79 	if (free_hugepages < MIN_FREE_PAGES) {
80 		printf("Not enough free huge pages to test, exiting!\n");
81 		exit(1);
82 	}
83 
84 	fd = memfd_create(argv[0], MFD_HUGETLB);
85 	if (fd < 0) {
86 		perror("memfd_create() failed");
87 		exit(1);
88 	}
89 
90 	/*
91 	 * Test validity of MADV_DONTNEED addr and length arguments.  mmap
92 	 * size is NR_HUGE_PAGES + 2.  One page at the beginning and end of
93 	 * the mapping will be unmapped so we KNOW there is nothing mapped
94 	 * there.
95 	 */
96 	addr = mmap(NULL, (NR_HUGE_PAGES + 2) * huge_page_size,
97 			PROT_READ | PROT_WRITE,
98 			MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
99 			-1, 0);
100 	if (addr == MAP_FAILED) {
101 		perror("mmap");
102 		exit(1);
103 	}
104 	if (munmap(addr, huge_page_size) ||
105 			munmap(addr + (NR_HUGE_PAGES + 1) * huge_page_size,
106 				huge_page_size)) {
107 		perror("munmap");
108 		exit(1);
109 	}
110 	addr = addr + huge_page_size;
111 
112 	write_fault_pages(addr, NR_HUGE_PAGES);
113 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
114 
115 	/* addr before mapping should fail */
116 	ret = madvise(addr - base_page_size, NR_HUGE_PAGES * huge_page_size,
117 		MADV_DONTNEED);
118 	if (!ret) {
119 		printf("Unexpected success of madvise call with invalid addr line %d\n",
120 				__LINE__);
121 			exit(1);
122 	}
123 
124 	/* addr + length after mapping should fail */
125 	ret = madvise(addr, (NR_HUGE_PAGES * huge_page_size) + base_page_size,
126 		MADV_DONTNEED);
127 	if (!ret) {
128 		printf("Unexpected success of madvise call with invalid length line %d\n",
129 				__LINE__);
130 			exit(1);
131 	}
132 
133 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
134 
135 	/*
136 	 * Test alignment of MADV_DONTNEED addr and length arguments
137 	 */
138 	addr = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
139 			PROT_READ | PROT_WRITE,
140 			MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
141 			-1, 0);
142 	if (addr == MAP_FAILED) {
143 		perror("mmap");
144 		exit(1);
145 	}
146 	write_fault_pages(addr, NR_HUGE_PAGES);
147 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
148 
149 	/* addr is not huge page size aligned and should fail */
150 	ret = madvise(addr + base_page_size,
151 			NR_HUGE_PAGES * huge_page_size - base_page_size,
152 			MADV_DONTNEED);
153 	if (!ret) {
154 		printf("Unexpected success of madvise call with unaligned start address %d\n",
155 				__LINE__);
156 			exit(1);
157 	}
158 
159 	/* addr + length should be aligned down to huge page size */
160 	if (madvise(addr,
161 			((NR_HUGE_PAGES - 1) * huge_page_size) + base_page_size,
162 			MADV_DONTNEED)) {
163 		perror("madvise");
164 		exit(1);
165 	}
166 
167 	/* should free all but last page in mapping */
168 	validate_free_pages(free_hugepages - 1);
169 
170 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
171 	validate_free_pages(free_hugepages);
172 
173 	/*
174 	 * Test MADV_DONTNEED on anonymous private mapping
175 	 */
176 	addr = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
177 			PROT_READ | PROT_WRITE,
178 			MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
179 			-1, 0);
180 	if (addr == MAP_FAILED) {
181 		perror("mmap");
182 		exit(1);
183 	}
184 	write_fault_pages(addr, NR_HUGE_PAGES);
185 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
186 
187 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
188 		perror("madvise");
189 		exit(1);
190 	}
191 
192 	/* should free all pages in mapping */
193 	validate_free_pages(free_hugepages);
194 
195 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
196 
197 	/*
198 	 * Test MADV_DONTNEED on private mapping of hugetlb file
199 	 */
200 	if (fallocate(fd, 0, 0, NR_HUGE_PAGES * huge_page_size)) {
201 		perror("fallocate");
202 		exit(1);
203 	}
204 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
205 
206 	addr = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
207 			PROT_READ | PROT_WRITE,
208 			MAP_PRIVATE, fd, 0);
209 	if (addr == MAP_FAILED) {
210 		perror("mmap");
211 		exit(1);
212 	}
213 
214 	/* read should not consume any pages */
215 	read_fault_pages(addr, NR_HUGE_PAGES);
216 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
217 
218 	/* madvise should not free any pages */
219 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
220 		perror("madvise");
221 		exit(1);
222 	}
223 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
224 
225 	/* writes should allocate private pages */
226 	write_fault_pages(addr, NR_HUGE_PAGES);
227 	validate_free_pages(free_hugepages - (2 * NR_HUGE_PAGES));
228 
229 	/* madvise should free private pages */
230 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
231 		perror("madvise");
232 		exit(1);
233 	}
234 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
235 
236 	/* writes should allocate private pages */
237 	write_fault_pages(addr, NR_HUGE_PAGES);
238 	validate_free_pages(free_hugepages - (2 * NR_HUGE_PAGES));
239 
240 	/*
241 	 * The fallocate below certainly should free the pages associated
242 	 * with the file.  However, pages in the private mapping are also
243 	 * freed.  This is not the 'correct' behavior, but is expected
244 	 * because this is how it has worked since the initial hugetlb
245 	 * implementation.
246 	 */
247 	if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
248 					0, NR_HUGE_PAGES * huge_page_size)) {
249 		perror("fallocate");
250 		exit(1);
251 	}
252 	validate_free_pages(free_hugepages);
253 
254 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
255 
256 	/*
257 	 * Test MADV_DONTNEED on shared mapping of hugetlb file
258 	 */
259 	if (fallocate(fd, 0, 0, NR_HUGE_PAGES * huge_page_size)) {
260 		perror("fallocate");
261 		exit(1);
262 	}
263 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
264 
265 	addr = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
266 			PROT_READ | PROT_WRITE,
267 			MAP_SHARED, fd, 0);
268 	if (addr == MAP_FAILED) {
269 		perror("mmap");
270 		exit(1);
271 	}
272 
273 	/* write should not consume any pages */
274 	write_fault_pages(addr, NR_HUGE_PAGES);
275 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
276 
277 	/* madvise should not free any pages */
278 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
279 		perror("madvise");
280 		exit(1);
281 	}
282 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
283 
284 	/*
285 	 * Test MADV_REMOVE on shared mapping of hugetlb file
286 	 *
287 	 * madvise is same as hole punch and should free all pages.
288 	 */
289 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_REMOVE)) {
290 		perror("madvise");
291 		exit(1);
292 	}
293 	validate_free_pages(free_hugepages);
294 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
295 
296 	/*
297 	 * Test MADV_REMOVE on shared and private mapping of hugetlb file
298 	 */
299 	if (fallocate(fd, 0, 0, NR_HUGE_PAGES * huge_page_size)) {
300 		perror("fallocate");
301 		exit(1);
302 	}
303 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
304 
305 	addr = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
306 			PROT_READ | PROT_WRITE,
307 			MAP_SHARED, fd, 0);
308 	if (addr == MAP_FAILED) {
309 		perror("mmap");
310 		exit(1);
311 	}
312 
313 	/* shared write should not consume any additional pages */
314 	write_fault_pages(addr, NR_HUGE_PAGES);
315 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
316 
317 	addr2 = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
318 			PROT_READ | PROT_WRITE,
319 			MAP_PRIVATE, fd, 0);
320 	if (addr2 == MAP_FAILED) {
321 		perror("mmap");
322 		exit(1);
323 	}
324 
325 	/* private read should not consume any pages */
326 	read_fault_pages(addr2, NR_HUGE_PAGES);
327 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
328 
329 	/* private write should consume additional pages */
330 	write_fault_pages(addr2, NR_HUGE_PAGES);
331 	validate_free_pages(free_hugepages - (2 * NR_HUGE_PAGES));
332 
333 	/* madvise of shared mapping should not free any pages */
334 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
335 		perror("madvise");
336 		exit(1);
337 	}
338 	validate_free_pages(free_hugepages - (2 * NR_HUGE_PAGES));
339 
340 	/* madvise of private mapping should free private pages */
341 	if (madvise(addr2, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
342 		perror("madvise");
343 		exit(1);
344 	}
345 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
346 
347 	/* private write should consume additional pages again */
348 	write_fault_pages(addr2, NR_HUGE_PAGES);
349 	validate_free_pages(free_hugepages - (2 * NR_HUGE_PAGES));
350 
351 	/*
352 	 * madvise should free both file and private pages although this is
353 	 * not correct.  private pages should not be freed, but this is
354 	 * expected.  See comment associated with FALLOC_FL_PUNCH_HOLE call.
355 	 */
356 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_REMOVE)) {
357 		perror("madvise");
358 		exit(1);
359 	}
360 	validate_free_pages(free_hugepages);
361 
362 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
363 	(void)munmap(addr2, NR_HUGE_PAGES * huge_page_size);
364 
365 	close(fd);
366 	return 0;
367 }
368