1 // SPDX-License-Identifier: GPL-2.0+ 2 /* 3 * kselftest suite for mincore(). 4 * 5 * Copyright (C) 2020 Collabora, Ltd. 6 */ 7 8 #define _GNU_SOURCE 9 10 #include <stdio.h> 11 #include <errno.h> 12 #include <unistd.h> 13 #include <stdlib.h> 14 #include <sys/mman.h> 15 #include <string.h> 16 #include <fcntl.h> 17 18 #include "../kselftest.h" 19 #include "../kselftest_harness.h" 20 21 /* Default test file size: 4MB */ 22 #define MB (1UL << 20) 23 #define FILE_SIZE (4 * MB) 24 25 26 /* 27 * Tests the user interface. This test triggers most of the documented 28 * error conditions in mincore(). 29 */ 30 TEST(basic_interface) 31 { 32 int retval; 33 int page_size; 34 unsigned char vec[1]; 35 char *addr; 36 37 page_size = sysconf(_SC_PAGESIZE); 38 39 /* Query a 0 byte sized range */ 40 retval = mincore(0, 0, vec); 41 EXPECT_EQ(0, retval); 42 43 /* Addresses in the specified range are invalid or unmapped */ 44 errno = 0; 45 retval = mincore(NULL, page_size, vec); 46 EXPECT_EQ(-1, retval); 47 EXPECT_EQ(ENOMEM, errno); 48 49 errno = 0; 50 addr = mmap(NULL, page_size, PROT_READ | PROT_WRITE, 51 MAP_SHARED | MAP_ANONYMOUS, -1, 0); 52 ASSERT_NE(MAP_FAILED, addr) { 53 TH_LOG("mmap error: %s", strerror(errno)); 54 } 55 56 /* <addr> argument is not page-aligned */ 57 errno = 0; 58 retval = mincore(addr + 1, page_size, vec); 59 EXPECT_EQ(-1, retval); 60 EXPECT_EQ(EINVAL, errno); 61 62 /* <length> argument is too large */ 63 errno = 0; 64 retval = mincore(addr, -1, vec); 65 EXPECT_EQ(-1, retval); 66 EXPECT_EQ(ENOMEM, errno); 67 68 /* <vec> argument points to an illegal address */ 69 errno = 0; 70 retval = mincore(addr, page_size, NULL); 71 EXPECT_EQ(-1, retval); 72 EXPECT_EQ(EFAULT, errno); 73 munmap(addr, page_size); 74 } 75 76 77 /* 78 * Test mincore() behavior on a private anonymous page mapping. 79 * Check that the page is not loaded into memory right after the mapping 80 * but after accessing it (on-demand allocation). 81 * Then free the page and check that it's not memory-resident. 82 */ 83 TEST(check_anonymous_locked_pages) 84 { 85 unsigned char vec[1]; 86 char *addr; 87 int retval; 88 int page_size; 89 90 page_size = sysconf(_SC_PAGESIZE); 91 92 /* Map one page and check it's not memory-resident */ 93 errno = 0; 94 addr = mmap(NULL, page_size, PROT_READ | PROT_WRITE, 95 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); 96 ASSERT_NE(MAP_FAILED, addr) { 97 TH_LOG("mmap error: %s", strerror(errno)); 98 } 99 retval = mincore(addr, page_size, vec); 100 ASSERT_EQ(0, retval); 101 ASSERT_EQ(0, vec[0]) { 102 TH_LOG("Page found in memory before use"); 103 } 104 105 /* Touch the page and check again. It should now be in memory */ 106 addr[0] = 1; 107 mlock(addr, page_size); 108 retval = mincore(addr, page_size, vec); 109 ASSERT_EQ(0, retval); 110 ASSERT_EQ(1, vec[0]) { 111 TH_LOG("Page not found in memory after use"); 112 } 113 114 /* 115 * It shouldn't be memory-resident after unlocking it and 116 * marking it as unneeded. 117 */ 118 munlock(addr, page_size); 119 madvise(addr, page_size, MADV_DONTNEED); 120 retval = mincore(addr, page_size, vec); 121 ASSERT_EQ(0, retval); 122 ASSERT_EQ(0, vec[0]) { 123 TH_LOG("Page in memory after being zapped"); 124 } 125 munmap(addr, page_size); 126 } 127 128 129 /* 130 * Check mincore() behavior on huge pages. 131 * This test will be skipped if the mapping fails (ie. if there are no 132 * huge pages available). 133 * 134 * Make sure the system has at least one free huge page, check 135 * "HugePages_Free" in /proc/meminfo. 136 * Increment /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages if 137 * needed. 138 */ 139 TEST(check_huge_pages) 140 { 141 unsigned char vec[1]; 142 char *addr; 143 int retval; 144 int page_size; 145 146 page_size = sysconf(_SC_PAGESIZE); 147 148 errno = 0; 149 addr = mmap(NULL, page_size, PROT_READ | PROT_WRITE, 150 MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB, 151 -1, 0); 152 if (addr == MAP_FAILED) { 153 if (errno == ENOMEM || errno == EINVAL) 154 SKIP(return, "No huge pages available or CONFIG_HUGETLB_PAGE disabled."); 155 else 156 TH_LOG("mmap error: %s", strerror(errno)); 157 } 158 retval = mincore(addr, page_size, vec); 159 ASSERT_EQ(0, retval); 160 ASSERT_EQ(0, vec[0]) { 161 TH_LOG("Page found in memory before use"); 162 } 163 164 addr[0] = 1; 165 mlock(addr, page_size); 166 retval = mincore(addr, page_size, vec); 167 ASSERT_EQ(0, retval); 168 ASSERT_EQ(1, vec[0]) { 169 TH_LOG("Page not found in memory after use"); 170 } 171 172 munlock(addr, page_size); 173 munmap(addr, page_size); 174 } 175 176 177 /* 178 * Test mincore() behavior on a file-backed page. 179 * No pages should be loaded into memory right after the mapping. Then, 180 * accessing any address in the mapping range should load the page 181 * containing the address and a number of subsequent pages (readahead). 182 * 183 * The actual readahead settings depend on the test environment, so we 184 * can't make a lot of assumptions about that. This test covers the most 185 * general cases. 186 */ 187 TEST(check_file_mmap) 188 { 189 unsigned char *vec; 190 int vec_size; 191 char *addr; 192 int retval; 193 int page_size; 194 int fd; 195 int i; 196 int ra_pages = 0; 197 198 page_size = sysconf(_SC_PAGESIZE); 199 vec_size = FILE_SIZE / page_size; 200 if (FILE_SIZE % page_size) 201 vec_size++; 202 203 vec = calloc(vec_size, sizeof(unsigned char)); 204 ASSERT_NE(NULL, vec) { 205 TH_LOG("Can't allocate array"); 206 } 207 208 errno = 0; 209 fd = open(".", O_TMPFILE | O_RDWR, 0600); 210 if (fd < 0) { 211 ASSERT_EQ(errno, EOPNOTSUPP) { 212 TH_LOG("Can't create temporary file: %s", 213 strerror(errno)); 214 } 215 SKIP(goto out_free, "O_TMPFILE not supported by filesystem."); 216 } 217 errno = 0; 218 retval = fallocate(fd, 0, 0, FILE_SIZE); 219 if (retval) { 220 ASSERT_EQ(errno, EOPNOTSUPP) { 221 TH_LOG("Error allocating space for the temporary file: %s", 222 strerror(errno)); 223 } 224 SKIP(goto out_close, "fallocate not supported by filesystem."); 225 } 226 227 /* 228 * Map the whole file, the pages shouldn't be fetched yet. 229 */ 230 errno = 0; 231 addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, 232 MAP_SHARED, fd, 0); 233 ASSERT_NE(MAP_FAILED, addr) { 234 TH_LOG("mmap error: %s", strerror(errno)); 235 } 236 retval = mincore(addr, FILE_SIZE, vec); 237 ASSERT_EQ(0, retval); 238 for (i = 0; i < vec_size; i++) { 239 ASSERT_EQ(0, vec[i]) { 240 TH_LOG("Unexpected page in memory"); 241 } 242 } 243 244 /* 245 * Touch a page in the middle of the mapping. We expect the next 246 * few pages (the readahead window) to be populated too. 247 */ 248 addr[FILE_SIZE / 2] = 1; 249 retval = mincore(addr, FILE_SIZE, vec); 250 ASSERT_EQ(0, retval); 251 ASSERT_EQ(1, vec[FILE_SIZE / 2 / page_size]) { 252 TH_LOG("Page not found in memory after use"); 253 } 254 255 i = FILE_SIZE / 2 / page_size + 1; 256 while (i < vec_size && vec[i]) { 257 ra_pages++; 258 i++; 259 } 260 EXPECT_GT(ra_pages, 0) { 261 TH_LOG("No read-ahead pages found in memory"); 262 } 263 264 EXPECT_LT(i, vec_size) { 265 TH_LOG("Read-ahead pages reached the end of the file"); 266 } 267 /* 268 * End of the readahead window. The rest of the pages shouldn't 269 * be in memory. 270 */ 271 if (i < vec_size) { 272 while (i < vec_size && !vec[i]) 273 i++; 274 EXPECT_EQ(vec_size, i) { 275 TH_LOG("Unexpected page in memory beyond readahead window"); 276 } 277 } 278 279 munmap(addr, FILE_SIZE); 280 out_close: 281 close(fd); 282 out_free: 283 free(vec); 284 } 285 286 287 /* 288 * Test mincore() behavior on a page backed by a tmpfs file. This test 289 * performs the same steps as the previous one. However, we don't expect 290 * any readahead in this case. 291 */ 292 TEST(check_tmpfs_mmap) 293 { 294 unsigned char *vec; 295 int vec_size; 296 char *addr; 297 int retval; 298 int page_size; 299 int fd; 300 int i; 301 int ra_pages = 0; 302 303 page_size = sysconf(_SC_PAGESIZE); 304 vec_size = FILE_SIZE / page_size; 305 if (FILE_SIZE % page_size) 306 vec_size++; 307 308 vec = calloc(vec_size, sizeof(unsigned char)); 309 ASSERT_NE(NULL, vec) { 310 TH_LOG("Can't allocate array"); 311 } 312 313 errno = 0; 314 fd = open("/dev/shm", O_TMPFILE | O_RDWR, 0600); 315 ASSERT_NE(-1, fd) { 316 TH_LOG("Can't create temporary file: %s", 317 strerror(errno)); 318 } 319 errno = 0; 320 retval = fallocate(fd, 0, 0, FILE_SIZE); 321 ASSERT_EQ(0, retval) { 322 TH_LOG("Error allocating space for the temporary file: %s", 323 strerror(errno)); 324 } 325 326 /* 327 * Map the whole file, the pages shouldn't be fetched yet. 328 */ 329 errno = 0; 330 addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, 331 MAP_SHARED, fd, 0); 332 ASSERT_NE(MAP_FAILED, addr) { 333 TH_LOG("mmap error: %s", strerror(errno)); 334 } 335 retval = mincore(addr, FILE_SIZE, vec); 336 ASSERT_EQ(0, retval); 337 for (i = 0; i < vec_size; i++) { 338 ASSERT_EQ(0, vec[i]) { 339 TH_LOG("Unexpected page in memory"); 340 } 341 } 342 343 /* 344 * Touch a page in the middle of the mapping. We expect only 345 * that page to be fetched into memory. 346 */ 347 addr[FILE_SIZE / 2] = 1; 348 retval = mincore(addr, FILE_SIZE, vec); 349 ASSERT_EQ(0, retval); 350 ASSERT_EQ(1, vec[FILE_SIZE / 2 / page_size]) { 351 TH_LOG("Page not found in memory after use"); 352 } 353 354 i = FILE_SIZE / 2 / page_size + 1; 355 while (i < vec_size && vec[i]) { 356 ra_pages++; 357 i++; 358 } 359 ASSERT_EQ(ra_pages, 0) { 360 TH_LOG("Read-ahead pages found in memory"); 361 } 362 363 munmap(addr, FILE_SIZE); 364 close(fd); 365 free(vec); 366 } 367 368 TEST_HARNESS_MAIN 369