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 */
TEST(basic_interface)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 */
TEST(check_anonymous_locked_pages)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 */
TEST(check_huge_pages)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 */
TEST(check_file_mmap)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 */
TEST(check_tmpfs_mmap)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