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