xref: /OK3568_Linux_fs/kernel/tools/testing/selftests/mincore/mincore_selftest.c (revision 4882a59341e53eb6f0b4789bf948001014eff981)
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