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