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
test_simple(int pagemap_fd,int pagesize)17 static 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
test_vma_reuse(int pagemap_fd,int pagesize)50 static 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
test_hugepage(int pagemap_fd,int pagesize)79 static 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
test_mprotect(int pagemap_fd,int pagesize,bool anon)134 static 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
test_mprotect_anon(int pagemap_fd,int pagesize)187 static void test_mprotect_anon(int pagemap_fd, int pagesize)
188 {
189 test_mprotect(pagemap_fd, pagesize, true);
190 }
191
test_mprotect_file(int pagemap_fd,int pagesize)192 static void test_mprotect_file(int pagemap_fd, int pagesize)
193 {
194 test_mprotect(pagemap_fd, pagesize, false);
195 }
196
main(int argc,char ** argv)197 int main(int argc, char **argv)
198 {
199 int pagemap_fd;
200 int pagesize;
201
202 ksft_print_header();
203 ksft_set_plan(15);
204
205 pagemap_fd = open(PAGEMAP_FILE_PATH, O_RDONLY);
206 if (pagemap_fd < 0)
207 ksft_exit_fail_msg("Failed to open %s\n", PAGEMAP_FILE_PATH);
208
209 pagesize = getpagesize();
210
211 test_simple(pagemap_fd, pagesize);
212 test_vma_reuse(pagemap_fd, pagesize);
213 test_hugepage(pagemap_fd, pagesize);
214 test_mprotect_anon(pagemap_fd, pagesize);
215 test_mprotect_file(pagemap_fd, pagesize);
216
217 close(pagemap_fd);
218
219 ksft_finished();
220 }
221