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