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