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