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