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 unsigned long get_free_hugepages(void)
40 {
41 	unsigned long fhp = 0;
42 	char *line = NULL;
43 	size_t linelen = 0;
44 	FILE *f = fopen("/proc/meminfo", "r");
45 
46 	if (!f)
47 		return fhp;
48 	while (getline(&line, &linelen, f) > 0) {
49 		if (sscanf(line, "HugePages_Free:      %lu", &fhp) == 1)
50 			break;
51 	}
52 
53 	free(line);
54 	fclose(f);
55 	return fhp;
56 }
57 
58 void write_fault_pages(void *addr, unsigned long nr_pages)
59 {
60 	unsigned long i;
61 
62 	for (i = 0; i < nr_pages; i++)
63 		*((unsigned long *)(addr + (i * huge_page_size))) = i;
64 }
65 
66 void read_fault_pages(void *addr, unsigned long nr_pages)
67 {
68 	unsigned long dummy = 0;
69 	unsigned long i;
70 
71 	for (i = 0; i < nr_pages; i++)
72 		dummy += *((unsigned long *)(addr + (i * huge_page_size)));
73 }
74 
75 int main(int argc, char **argv)
76 {
77 	unsigned long free_hugepages;
78 	void *addr, *addr2;
79 	int fd;
80 	int ret;
81 
82 	huge_page_size = default_huge_page_size();
83 	if (!huge_page_size) {
84 		printf("Unable to determine huge page size, exiting!\n");
85 		exit(1);
86 	}
87 	base_page_size = sysconf(_SC_PAGE_SIZE);
88 	if (!huge_page_size) {
89 		printf("Unable to determine base page size, exiting!\n");
90 		exit(1);
91 	}
92 
93 	free_hugepages = get_free_hugepages();
94 	if (free_hugepages < MIN_FREE_PAGES) {
95 		printf("Not enough free huge pages to test, exiting!\n");
96 		exit(1);
97 	}
98 
99 	fd = memfd_create(argv[0], MFD_HUGETLB);
100 	if (fd < 0) {
101 		perror("memfd_create() failed");
102 		exit(1);
103 	}
104 
105 	/*
106 	 * Test validity of MADV_DONTNEED addr and length arguments.  mmap
107 	 * size is NR_HUGE_PAGES + 2.  One page at the beginning and end of
108 	 * the mapping will be unmapped so we KNOW there is nothing mapped
109 	 * there.
110 	 */
111 	addr = mmap(NULL, (NR_HUGE_PAGES + 2) * huge_page_size,
112 			PROT_READ | PROT_WRITE,
113 			MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
114 			-1, 0);
115 	if (addr == MAP_FAILED) {
116 		perror("mmap");
117 		exit(1);
118 	}
119 	if (munmap(addr, huge_page_size) ||
120 			munmap(addr + (NR_HUGE_PAGES + 1) * huge_page_size,
121 				huge_page_size)) {
122 		perror("munmap");
123 		exit(1);
124 	}
125 	addr = addr + huge_page_size;
126 
127 	write_fault_pages(addr, NR_HUGE_PAGES);
128 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
129 
130 	/* addr before mapping should fail */
131 	ret = madvise(addr - base_page_size, NR_HUGE_PAGES * huge_page_size,
132 		MADV_DONTNEED);
133 	if (!ret) {
134 		printf("Unexpected success of madvise call with invalid addr line %d\n",
135 				__LINE__);
136 			exit(1);
137 	}
138 
139 	/* addr + length after mapping should fail */
140 	ret = madvise(addr, (NR_HUGE_PAGES * huge_page_size) + base_page_size,
141 		MADV_DONTNEED);
142 	if (!ret) {
143 		printf("Unexpected success of madvise call with invalid length line %d\n",
144 				__LINE__);
145 			exit(1);
146 	}
147 
148 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
149 
150 	/*
151 	 * Test alignment of MADV_DONTNEED addr and length arguments
152 	 */
153 	addr = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
154 			PROT_READ | PROT_WRITE,
155 			MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
156 			-1, 0);
157 	if (addr == MAP_FAILED) {
158 		perror("mmap");
159 		exit(1);
160 	}
161 	write_fault_pages(addr, NR_HUGE_PAGES);
162 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
163 
164 	/* addr is not huge page size aligned and should fail */
165 	ret = madvise(addr + base_page_size,
166 			NR_HUGE_PAGES * huge_page_size - base_page_size,
167 			MADV_DONTNEED);
168 	if (!ret) {
169 		printf("Unexpected success of madvise call with unaligned start address %d\n",
170 				__LINE__);
171 			exit(1);
172 	}
173 
174 	/* addr + length should be aligned down to huge page size */
175 	if (madvise(addr,
176 			((NR_HUGE_PAGES - 1) * huge_page_size) + base_page_size,
177 			MADV_DONTNEED)) {
178 		perror("madvise");
179 		exit(1);
180 	}
181 
182 	/* should free all but last page in mapping */
183 	validate_free_pages(free_hugepages - 1);
184 
185 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
186 	validate_free_pages(free_hugepages);
187 
188 	/*
189 	 * Test MADV_DONTNEED on anonymous private mapping
190 	 */
191 	addr = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
192 			PROT_READ | PROT_WRITE,
193 			MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
194 			-1, 0);
195 	if (addr == MAP_FAILED) {
196 		perror("mmap");
197 		exit(1);
198 	}
199 	write_fault_pages(addr, NR_HUGE_PAGES);
200 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
201 
202 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
203 		perror("madvise");
204 		exit(1);
205 	}
206 
207 	/* should free all pages in mapping */
208 	validate_free_pages(free_hugepages);
209 
210 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
211 
212 	/*
213 	 * Test MADV_DONTNEED on private mapping of hugetlb file
214 	 */
215 	if (fallocate(fd, 0, 0, NR_HUGE_PAGES * huge_page_size)) {
216 		perror("fallocate");
217 		exit(1);
218 	}
219 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
220 
221 	addr = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
222 			PROT_READ | PROT_WRITE,
223 			MAP_PRIVATE, fd, 0);
224 	if (addr == MAP_FAILED) {
225 		perror("mmap");
226 		exit(1);
227 	}
228 
229 	/* read should not consume any pages */
230 	read_fault_pages(addr, NR_HUGE_PAGES);
231 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
232 
233 	/* madvise should not free any pages */
234 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
235 		perror("madvise");
236 		exit(1);
237 	}
238 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
239 
240 	/* writes should allocate private pages */
241 	write_fault_pages(addr, NR_HUGE_PAGES);
242 	validate_free_pages(free_hugepages - (2 * NR_HUGE_PAGES));
243 
244 	/* madvise should free private pages */
245 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
246 		perror("madvise");
247 		exit(1);
248 	}
249 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
250 
251 	/* writes should allocate private pages */
252 	write_fault_pages(addr, NR_HUGE_PAGES);
253 	validate_free_pages(free_hugepages - (2 * NR_HUGE_PAGES));
254 
255 	/*
256 	 * The fallocate below certainly should free the pages associated
257 	 * with the file.  However, pages in the private mapping are also
258 	 * freed.  This is not the 'correct' behavior, but is expected
259 	 * because this is how it has worked since the initial hugetlb
260 	 * implementation.
261 	 */
262 	if (fallocate(fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE,
263 					0, NR_HUGE_PAGES * huge_page_size)) {
264 		perror("fallocate");
265 		exit(1);
266 	}
267 	validate_free_pages(free_hugepages);
268 
269 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
270 
271 	/*
272 	 * Test MADV_DONTNEED on shared mapping of hugetlb file
273 	 */
274 	if (fallocate(fd, 0, 0, NR_HUGE_PAGES * huge_page_size)) {
275 		perror("fallocate");
276 		exit(1);
277 	}
278 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
279 
280 	addr = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
281 			PROT_READ | PROT_WRITE,
282 			MAP_SHARED, fd, 0);
283 	if (addr == MAP_FAILED) {
284 		perror("mmap");
285 		exit(1);
286 	}
287 
288 	/* write should not consume any pages */
289 	write_fault_pages(addr, NR_HUGE_PAGES);
290 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
291 
292 	/* madvise should not free any pages */
293 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
294 		perror("madvise");
295 		exit(1);
296 	}
297 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
298 
299 	/*
300 	 * Test MADV_REMOVE on shared mapping of hugetlb file
301 	 *
302 	 * madvise is same as hole punch and should free all pages.
303 	 */
304 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_REMOVE)) {
305 		perror("madvise");
306 		exit(1);
307 	}
308 	validate_free_pages(free_hugepages);
309 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
310 
311 	/*
312 	 * Test MADV_REMOVE on shared and private mapping of hugetlb file
313 	 */
314 	if (fallocate(fd, 0, 0, NR_HUGE_PAGES * huge_page_size)) {
315 		perror("fallocate");
316 		exit(1);
317 	}
318 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
319 
320 	addr = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
321 			PROT_READ | PROT_WRITE,
322 			MAP_SHARED, fd, 0);
323 	if (addr == MAP_FAILED) {
324 		perror("mmap");
325 		exit(1);
326 	}
327 
328 	/* shared write should not consume any additional pages */
329 	write_fault_pages(addr, NR_HUGE_PAGES);
330 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
331 
332 	addr2 = mmap(NULL, NR_HUGE_PAGES * huge_page_size,
333 			PROT_READ | PROT_WRITE,
334 			MAP_PRIVATE, fd, 0);
335 	if (addr2 == MAP_FAILED) {
336 		perror("mmap");
337 		exit(1);
338 	}
339 
340 	/* private read should not consume any pages */
341 	read_fault_pages(addr2, NR_HUGE_PAGES);
342 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
343 
344 	/* private write should consume additional pages */
345 	write_fault_pages(addr2, NR_HUGE_PAGES);
346 	validate_free_pages(free_hugepages - (2 * NR_HUGE_PAGES));
347 
348 	/* madvise of shared mapping should not free any pages */
349 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
350 		perror("madvise");
351 		exit(1);
352 	}
353 	validate_free_pages(free_hugepages - (2 * NR_HUGE_PAGES));
354 
355 	/* madvise of private mapping should free private pages */
356 	if (madvise(addr2, NR_HUGE_PAGES * huge_page_size, MADV_DONTNEED)) {
357 		perror("madvise");
358 		exit(1);
359 	}
360 	validate_free_pages(free_hugepages - NR_HUGE_PAGES);
361 
362 	/* private write should consume additional pages again */
363 	write_fault_pages(addr2, NR_HUGE_PAGES);
364 	validate_free_pages(free_hugepages - (2 * NR_HUGE_PAGES));
365 
366 	/*
367 	 * madvise should free both file and private pages although this is
368 	 * not correct.  private pages should not be freed, but this is
369 	 * expected.  See comment associated with FALLOC_FL_PUNCH_HOLE call.
370 	 */
371 	if (madvise(addr, NR_HUGE_PAGES * huge_page_size, MADV_REMOVE)) {
372 		perror("madvise");
373 		exit(1);
374 	}
375 	validate_free_pages(free_hugepages);
376 
377 	(void)munmap(addr, NR_HUGE_PAGES * huge_page_size);
378 	(void)munmap(addr2, NR_HUGE_PAGES * huge_page_size);
379 
380 	close(fd);
381 	return 0;
382 }
383