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
write_fault_pages(void * addr,unsigned long nr_pages)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
read_fault_pages(void * addr,unsigned long nr_pages)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
main(int argc,char ** argv)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