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) 154 SKIP(return, "No huge pages available."); 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 ASSERT_NE(-1, fd) { 211 TH_LOG("Can't create temporary file: %s", 212 strerror(errno)); 213 } 214 errno = 0; 215 retval = fallocate(fd, 0, 0, FILE_SIZE); 216 ASSERT_EQ(0, retval) { 217 TH_LOG("Error allocating space for the temporary file: %s", 218 strerror(errno)); 219 } 220 221 /* 222 * Map the whole file, the pages shouldn't be fetched yet. 223 */ 224 errno = 0; 225 addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, 226 MAP_SHARED, fd, 0); 227 ASSERT_NE(MAP_FAILED, addr) { 228 TH_LOG("mmap error: %s", strerror(errno)); 229 } 230 retval = mincore(addr, FILE_SIZE, vec); 231 ASSERT_EQ(0, retval); 232 for (i = 0; i < vec_size; i++) { 233 ASSERT_EQ(0, vec[i]) { 234 TH_LOG("Unexpected page in memory"); 235 } 236 } 237 238 /* 239 * Touch a page in the middle of the mapping. We expect the next 240 * few pages (the readahead window) to be populated too. 241 */ 242 addr[FILE_SIZE / 2] = 1; 243 retval = mincore(addr, FILE_SIZE, vec); 244 ASSERT_EQ(0, retval); 245 ASSERT_EQ(1, vec[FILE_SIZE / 2 / page_size]) { 246 TH_LOG("Page not found in memory after use"); 247 } 248 249 i = FILE_SIZE / 2 / page_size + 1; 250 while (i < vec_size && vec[i]) { 251 ra_pages++; 252 i++; 253 } 254 EXPECT_GT(ra_pages, 0) { 255 TH_LOG("No read-ahead pages found in memory"); 256 } 257 258 EXPECT_LT(i, vec_size) { 259 TH_LOG("Read-ahead pages reached the end of the file"); 260 } 261 /* 262 * End of the readahead window. The rest of the pages shouldn't 263 * be in memory. 264 */ 265 if (i < vec_size) { 266 while (i < vec_size && !vec[i]) 267 i++; 268 EXPECT_EQ(vec_size, i) { 269 TH_LOG("Unexpected page in memory beyond readahead window"); 270 } 271 } 272 273 munmap(addr, FILE_SIZE); 274 close(fd); 275 free(vec); 276 } 277 278 279 /* 280 * Test mincore() behavior on a page backed by a tmpfs file. This test 281 * performs the same steps as the previous one. However, we don't expect 282 * any readahead in this case. 283 */ 284 TEST(check_tmpfs_mmap) 285 { 286 unsigned char *vec; 287 int vec_size; 288 char *addr; 289 int retval; 290 int page_size; 291 int fd; 292 int i; 293 int ra_pages = 0; 294 295 page_size = sysconf(_SC_PAGESIZE); 296 vec_size = FILE_SIZE / page_size; 297 if (FILE_SIZE % page_size) 298 vec_size++; 299 300 vec = calloc(vec_size, sizeof(unsigned char)); 301 ASSERT_NE(NULL, vec) { 302 TH_LOG("Can't allocate array"); 303 } 304 305 errno = 0; 306 fd = open("/dev/shm", O_TMPFILE | O_RDWR, 0600); 307 ASSERT_NE(-1, fd) { 308 TH_LOG("Can't create temporary file: %s", 309 strerror(errno)); 310 } 311 errno = 0; 312 retval = fallocate(fd, 0, 0, FILE_SIZE); 313 ASSERT_EQ(0, retval) { 314 TH_LOG("Error allocating space for the temporary file: %s", 315 strerror(errno)); 316 } 317 318 /* 319 * Map the whole file, the pages shouldn't be fetched yet. 320 */ 321 errno = 0; 322 addr = mmap(NULL, FILE_SIZE, PROT_READ | PROT_WRITE, 323 MAP_SHARED, fd, 0); 324 ASSERT_NE(MAP_FAILED, addr) { 325 TH_LOG("mmap error: %s", strerror(errno)); 326 } 327 retval = mincore(addr, FILE_SIZE, vec); 328 ASSERT_EQ(0, retval); 329 for (i = 0; i < vec_size; i++) { 330 ASSERT_EQ(0, vec[i]) { 331 TH_LOG("Unexpected page in memory"); 332 } 333 } 334 335 /* 336 * Touch a page in the middle of the mapping. We expect only 337 * that page to be fetched into memory. 338 */ 339 addr[FILE_SIZE / 2] = 1; 340 retval = mincore(addr, FILE_SIZE, vec); 341 ASSERT_EQ(0, retval); 342 ASSERT_EQ(1, vec[FILE_SIZE / 2 / page_size]) { 343 TH_LOG("Page not found in memory after use"); 344 } 345 346 i = FILE_SIZE / 2 / page_size + 1; 347 while (i < vec_size && vec[i]) { 348 ra_pages++; 349 i++; 350 } 351 ASSERT_EQ(ra_pages, 0) { 352 TH_LOG("Read-ahead pages found in memory"); 353 } 354 355 munmap(addr, FILE_SIZE); 356 close(fd); 357 free(vec); 358 } 359 360 TEST_HARNESS_MAIN 361