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 	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