File Index
Files and directories
Sign in
FILE

soft-dirty.c

File preview with syntax highlighting.

linux-6.19.14/tools/testing/selftests/mm/soft-dirty.c C
  1// SPDX-License-Identifier: GPL-2.0
  2#include <stdio.h>
  3#include <string.h>
  4#include <stdbool.h>
  5#include <fcntl.h>
  6#include <stdint.h>
  7#include <malloc.h>
  8#include <sys/mman.h>
  9
 10#include "kselftest.h"
 11#include "vm_util.h"
 12#include "thp_settings.h"
 13
 14#define PAGEMAP_FILE_PATH "/proc/self/pagemap"
 15#define TEST_ITERATIONS 10000
 16
 17static void test_simple(int pagemap_fd, int pagesize)
 18{
 19	int i;
 20	char *map;
 21
 22	map = aligned_alloc(pagesize, pagesize);
 23	if (!map)
 24		ksft_exit_fail_msg("mmap failed\n");
 25
 26	clear_softdirty();
 27
 28	for (i = 0 ; i < TEST_ITERATIONS; i++) {
 29		if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
 30			ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
 31			break;
 32		}
 33
 34		clear_softdirty();
 35		// Write something to the page to get the dirty bit enabled on the page
 36		map[0]++;
 37
 38		if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
 39			ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
 40			break;
 41		}
 42
 43		clear_softdirty();
 44	}
 45	free(map);
 46
 47	ksft_test_result(i == TEST_ITERATIONS, "Test %s\n", __func__);
 48}
 49
 50static void test_vma_reuse(int pagemap_fd, int pagesize)
 51{
 52	char *map, *map2;
 53
 54	map = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
 55	if (map == MAP_FAILED)
 56		ksft_exit_fail_msg("mmap failed");
 57
 58	// The kernel always marks new regions as soft dirty
 59	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
 60			 "Test %s dirty bit of allocated page\n", __func__);
 61
 62	clear_softdirty();
 63	munmap(map, pagesize);
 64
 65	map2 = mmap(NULL, pagesize, (PROT_READ | PROT_WRITE), (MAP_PRIVATE | MAP_ANON), -1, 0);
 66	if (map2 == MAP_FAILED)
 67		ksft_exit_fail_msg("mmap failed");
 68
 69	// Dirty bit is set for new regions even if they are reused
 70	if (map == map2)
 71		ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
 72				 "Test %s dirty bit of reused address page\n", __func__);
 73	else
 74		ksft_test_result_skip("Test %s dirty bit of reused address page\n", __func__);
 75
 76	munmap(map2, pagesize);
 77}
 78
 79static void test_hugepage(int pagemap_fd, int pagesize)
 80{
 81	char *map;
 82	int i, ret;
 83
 84	if (!thp_is_enabled()) {
 85		ksft_test_result_skip("Transparent Hugepages not available\n");
 86		return;
 87	}
 88
 89	size_t hpage_len = read_pmd_pagesize();
 90	if (!hpage_len)
 91		ksft_exit_fail_msg("Reading PMD pagesize failed");
 92
 93	map = memalign(hpage_len, hpage_len);
 94	if (!map)
 95		ksft_exit_fail_msg("memalign failed\n");
 96
 97	ret = madvise(map, hpage_len, MADV_HUGEPAGE);
 98	if (ret)
 99		ksft_exit_fail_msg("madvise failed %d\n", ret);
100
101	for (i = 0; i < hpage_len; i++)
102		map[i] = (char)i;
103
104	if (check_huge_anon(map, 1, hpage_len)) {
105		ksft_test_result_pass("Test %s huge page allocation\n", __func__);
106
107		clear_softdirty();
108		for (i = 0 ; i < TEST_ITERATIONS ; i++) {
109			if (pagemap_is_softdirty(pagemap_fd, map) == 1) {
110				ksft_print_msg("dirty bit was 1, but should be 0 (i=%d)\n", i);
111				break;
112			}
113
114			clear_softdirty();
115			// Write something to the page to get the dirty bit enabled on the page
116			map[0]++;
117
118			if (pagemap_is_softdirty(pagemap_fd, map) == 0) {
119				ksft_print_msg("dirty bit was 0, but should be 1 (i=%d)\n", i);
120				break;
121			}
122			clear_softdirty();
123		}
124
125		ksft_test_result(i == TEST_ITERATIONS, "Test %s huge page dirty bit\n", __func__);
126	} else {
127		// hugepage allocation failed. skip these tests
128		ksft_test_result_skip("Test %s huge page allocation\n", __func__);
129		ksft_test_result_skip("Test %s huge page dirty bit\n", __func__);
130	}
131	free(map);
132}
133
134static void test_mprotect(int pagemap_fd, int pagesize, bool anon)
135{
136	const char *type[] = {"file", "anon"};
137	const char *fname = "./soft-dirty-test-file";
138	int test_fd = 0;
139	char *map;
140
141	if (anon) {
142		map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
143			   MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
144		if (!map)
145			ksft_exit_fail_msg("anon mmap failed\n");
146	} else {
147		test_fd = open(fname, O_RDWR | O_CREAT, 0664);
148		if (test_fd < 0) {
149			ksft_test_result_skip("Test %s open() file failed\n", __func__);
150			return;
151		}
152		unlink(fname);
153		ftruncate(test_fd, pagesize);
154		map = mmap(NULL, pagesize, PROT_READ|PROT_WRITE,
155			   MAP_SHARED, test_fd, 0);
156		if (!map)
157			ksft_exit_fail_msg("file mmap failed\n");
158	}
159
160	*map = 1;
161	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
162			 "Test %s-%s dirty bit of new written page\n",
163			 __func__, type[anon]);
164	clear_softdirty();
165	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
166			 "Test %s-%s soft-dirty clear after clear_refs\n",
167			 __func__, type[anon]);
168	mprotect(map, pagesize, PROT_READ);
169	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
170			 "Test %s-%s soft-dirty clear after marking RO\n",
171			 __func__, type[anon]);
172	mprotect(map, pagesize, PROT_READ|PROT_WRITE);
173	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 0,
174			 "Test %s-%s soft-dirty clear after marking RW\n",
175			 __func__, type[anon]);
176	*map = 2;
177	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
178			 "Test %s-%s soft-dirty after rewritten\n",
179			 __func__, type[anon]);
180
181	munmap(map, pagesize);
182
183	if (!anon)
184		close(test_fd);
185}
186
187static void test_merge(int pagemap_fd, int pagesize)
188{
189	char *reserved, *map, *map2;
190
191	/*
192	 * Reserve space for tests:
193	 *
194	 *   ---padding to ---
195	 *   |   avoid adj.  |
196	 *   v     merge     v
197	 * |---|---|---|---|---|
198	 * |   | 1 | 2 | 3 |   |
199	 * |---|---|---|---|---|
200	 */
201	reserved = mmap(NULL, 5 * pagesize, PROT_NONE,
202			MAP_ANON | MAP_PRIVATE, -1, 0);
203	if (reserved == MAP_FAILED)
204		ksft_exit_fail_msg("mmap failed\n");
205	munmap(reserved, 4 * pagesize);
206
207	/*
208	 * Establish initial VMA:
209	 *
210	 *      S/D
211	 * |---|---|---|---|---|
212	 * |   | 1 |   |   |   |
213	 * |---|---|---|---|---|
214	 */
215	map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
216		   MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
217	if (map == MAP_FAILED)
218		ksft_exit_fail_msg("mmap failed\n");
219
220	/* This will clear VM_SOFTDIRTY too. */
221	clear_softdirty();
222
223	/*
224	 * Now place a new mapping which will be marked VM_SOFTDIRTY. Away from
225	 * map:
226	 *
227	 *       -      S/D
228	 * |---|---|---|---|---|
229	 * |   | 1 |   | 2 |   |
230	 * |---|---|---|---|---|
231	 */
232	map2 = mmap(&reserved[3 * pagesize], pagesize, PROT_READ | PROT_WRITE,
233		    MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
234	if (map2 == MAP_FAILED)
235		ksft_exit_fail_msg("mmap failed\n");
236
237	/*
238	 * Now remap it immediately adjacent to map, if the merge correctly
239	 * propagates VM_SOFTDIRTY, we should then observe the VMA as a whole
240	 * being marked soft-dirty:
241	 *
242	 *       merge
243	 *        S/D
244	 * |---|-------|---|---|
245	 * |   |   1   |   |   |
246	 * |---|-------|---|---|
247	 */
248	map2 = mremap(map2, pagesize, pagesize, MREMAP_FIXED | MREMAP_MAYMOVE,
249		      &reserved[2 * pagesize]);
250	if (map2 == MAP_FAILED)
251		ksft_exit_fail_msg("mremap failed\n");
252	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
253			 "Test %s-anon soft-dirty after remap merge 1st pg\n",
254			 __func__);
255	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
256			 "Test %s-anon soft-dirty after remap merge 2nd pg\n",
257			 __func__);
258
259	munmap(map, 2 * pagesize);
260
261	/*
262	 * Now establish another VMA:
263	 *
264	 *      S/D
265	 * |---|---|---|---|---|
266	 * |   | 1 |   |   |   |
267	 * |---|---|---|---|---|
268	 */
269	map = mmap(&reserved[pagesize], pagesize, PROT_READ | PROT_WRITE,
270		   MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
271	if (map == MAP_FAILED)
272		ksft_exit_fail_msg("mmap failed\n");
273
274	/* Clear VM_SOFTDIRTY... */
275	clear_softdirty();
276	/* ...and establish incompatible adjacent VMA:
277	 *
278	 *       -  S/D
279	 * |---|---|---|---|---|
280	 * |   | 1 | 2 |   |   |
281	 * |---|---|---|---|---|
282	 */
283	map2 = mmap(&reserved[2 * pagesize], pagesize,
284	PROT_READ | PROT_WRITE | PROT_EXEC,
285		   MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
286	if (map2 == MAP_FAILED)
287		ksft_exit_fail_msg("mmap failed\n");
288
289	/*
290	 * Now mprotect() VMA 1 so it's compatible with 2 and therefore merges:
291	 *
292	 *       merge
293	 *        S/D
294	 * |---|-------|---|---|
295	 * |   |   1   |   |   |
296	 * |---|-------|---|---|
297	 */
298	if (mprotect(map, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC))
299		ksft_exit_fail_msg("mprotect failed\n");
300
301	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map) == 1,
302			 "Test %s-anon soft-dirty after mprotect merge 1st pg\n",
303			 __func__);
304	ksft_test_result(pagemap_is_softdirty(pagemap_fd, map2) == 1,
305			 "Test %s-anon soft-dirty after mprotect merge 2nd pg\n",
306			 __func__);
307
308	munmap(map, 2 * pagesize);
309}
310
311static void test_mprotect_anon(int pagemap_fd, int pagesize)
312{
313	test_mprotect(pagemap_fd, pagesize, true);
314}
315
316static void test_mprotect_file(int pagemap_fd, int pagesize)
317{
318	test_mprotect(pagemap_fd, pagesize, false);
319}
320
321int main(int argc, char **argv)
322{
323	int pagemap_fd;
324	int pagesize;
325
326	ksft_print_header();
327
328	if (!softdirty_supported())
329		ksft_exit_skip("soft-dirty is not support\n");
330
331	ksft_set_plan(19);
332	pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY);
333	if (pagemap_fd < 0)
334		ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH);
335
336	pagesize = getpagesize();
337
338	test_simple(pagemap_fd, pagesize);
339	test_vma_reuse(pagemap_fd, pagesize);
340	test_hugepage(pagemap_fd, pagesize);
341	test_mprotect_anon(pagemap_fd, pagesize);
342	test_mprotect_file(pagemap_fd, pagesize);
343	test_merge(pagemap_fd, pagesize);
344
345	close(pagemap_fd);
346
347	ksft_finished();
348}
~/ Read-only file preview.