369 lines · c
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_SOURCE9 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 documented28 * error conditions in mincore().29 */30TEST(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 mapping80 * but after accessing it (on-demand allocation).81 * Then free the page and check that it's not memory-resident.82 */83TEST(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 and116 * 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 no132 * huge pages available).133 *134 * Make sure the system has at least one free huge page, check135 * "HugePages_Free" in /proc/meminfo.136 * Increment /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages if137 * needed.138 */139TEST(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 else156 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 page181 * containing the address and a number of subsequent pages (readahead).182 *183 * The actual readahead settings depend on the test environment, so we184 * can't make a lot of assumptions about that. This test covers the most185 * general cases.186 */187TEST(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 next246 * 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't269 * 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);280out_close:281 close(fd);282out_free:283 free(vec);284}285 286 287/*288 * Test mincore() behavior on a page backed by a tmpfs file. This test289 * performs the same steps as the previous one. However, we don't expect290 * any readahead in this case.291 */292TEST(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 only345 * 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 368TEST_HARNESS_MAIN369