1 // SPDX-License-Identifier: GPL-2.0
2 /*
3 * RMAP functional tests
4 *
5 * Author(s): Wei Yang <richard.weiyang@gmail.com>
6 */
7
8 #include "../kselftest_harness.h"
9 #include <strings.h>
10 #include <pthread.h>
11 #include <numa.h>
12 #include <numaif.h>
13 #include <sys/mman.h>
14 #include <sys/prctl.h>
15 #include <sys/types.h>
16 #include <signal.h>
17 #include <time.h>
18 #include <sys/sem.h>
19 #include <unistd.h>
20 #include <fcntl.h>
21
22 #include "vm_util.h"
23
24 #define TOTAL_LEVEL 5
25 #define MAX_CHILDREN 3
26
27 #define FAIL_ON_CHECK (1 << 0)
28 #define FAIL_ON_WORK (1 << 1)
29
30 struct sembuf sem_wait = {0, -1, 0};
31 struct sembuf sem_signal = {0, 1, 0};
32
33 enum backend_type {
34 ANON,
35 SHM,
36 NORM_FILE,
37 };
38
39 #define PREFIX "kst_rmap"
40 #define MAX_FILENAME_LEN 256
41 const char *suffixes[] = {
42 "",
43 "_shm",
44 "_file",
45 };
46
47 struct global_data;
48 typedef int (*work_fn)(struct global_data *data);
49 typedef int (*check_fn)(struct global_data *data);
50 typedef void (*prepare_fn)(struct global_data *data);
51
52 struct global_data {
53 int worker_level;
54
55 int semid;
56 int pipefd[2];
57
58 unsigned int mapsize;
59 unsigned int rand_seed;
60 char *region;
61
62 prepare_fn do_prepare;
63 work_fn do_work;
64 check_fn do_check;
65
66 enum backend_type backend;
67 char filename[MAX_FILENAME_LEN];
68
69 unsigned long *expected_pfn;
70 };
71
72 /*
73 * Create a process tree with TOTAL_LEVEL height and at most MAX_CHILDREN
74 * children for each.
75 *
76 * It will randomly select one process as 'worker' process which will
77 * 'do_work' until all processes are created. And all other processes will
78 * wait until 'worker' finish its work.
79 */
propagate_children(struct __test_metadata * _metadata,struct global_data * data)80 void propagate_children(struct __test_metadata *_metadata, struct global_data *data)
81 {
82 pid_t root_pid, pid;
83 unsigned int num_child;
84 int status;
85 int ret = 0;
86 int curr_child, worker_child;
87 int curr_level = 1;
88 bool is_worker = true;
89
90 root_pid = getpid();
91 repeat:
92 num_child = rand_r(&data->rand_seed) % MAX_CHILDREN + 1;
93 worker_child = is_worker ? rand_r(&data->rand_seed) % num_child : -1;
94
95 for (curr_child = 0; curr_child < num_child; curr_child++) {
96 pid = fork();
97
98 if (pid < 0) {
99 perror("Error: fork\n");
100 } else if (pid == 0) {
101 curr_level++;
102
103 if (curr_child != worker_child)
104 is_worker = false;
105
106 if (curr_level == TOTAL_LEVEL)
107 break;
108
109 data->rand_seed += curr_child;
110 goto repeat;
111 }
112 }
113
114 if (data->do_prepare)
115 data->do_prepare(data);
116
117 close(data->pipefd[1]);
118
119 if (is_worker && curr_level == data->worker_level) {
120 /* This is the worker process, first wait last process created */
121 char buf;
122
123 while (read(data->pipefd[0], &buf, 1) > 0)
124 ;
125
126 if (data->do_work)
127 ret = data->do_work(data);
128
129 /* Kick others */
130 semctl(data->semid, 0, IPC_RMID);
131 } else {
132 /* Wait worker finish */
133 semop(data->semid, &sem_wait, 1);
134 if (data->do_check)
135 ret = data->do_check(data);
136 }
137
138 /* Wait all child to quit */
139 while (wait(&status) > 0) {
140 if (WIFEXITED(status))
141 ret |= WEXITSTATUS(status);
142 }
143
144 if (getpid() == root_pid) {
145 if (ret & FAIL_ON_WORK)
146 SKIP(return, "Failed in worker");
147
148 ASSERT_EQ(ret, 0);
149 } else {
150 exit(ret);
151 }
152 }
153
FIXTURE(migrate)154 FIXTURE(migrate)
155 {
156 struct global_data data;
157 };
158
FIXTURE_SETUP(migrate)159 FIXTURE_SETUP(migrate)
160 {
161 struct global_data *data = &self->data;
162
163 if (numa_available() < 0)
164 SKIP(return, "NUMA not available");
165 if (numa_bitmask_weight(numa_all_nodes_ptr) <= 1)
166 SKIP(return, "Not enough NUMA nodes available");
167
168 data->mapsize = getpagesize();
169
170 data->expected_pfn = mmap(0, sizeof(unsigned long),
171 PROT_READ | PROT_WRITE,
172 MAP_SHARED | MAP_ANONYMOUS, -1, 0);
173 ASSERT_NE(data->expected_pfn, MAP_FAILED);
174
175 /* Prepare semaphore */
176 data->semid = semget(IPC_PRIVATE, 1, 0666 | IPC_CREAT);
177 ASSERT_NE(data->semid, -1);
178 ASSERT_NE(semctl(data->semid, 0, SETVAL, 0), -1);
179
180 /* Prepare pipe */
181 ASSERT_NE(pipe(data->pipefd), -1);
182
183 data->rand_seed = time(NULL);
184 srand(data->rand_seed);
185
186 data->worker_level = rand() % TOTAL_LEVEL + 1;
187
188 data->do_prepare = NULL;
189 data->do_work = NULL;
190 data->do_check = NULL;
191
192 data->backend = ANON;
193 };
194
FIXTURE_TEARDOWN(migrate)195 FIXTURE_TEARDOWN(migrate)
196 {
197 struct global_data *data = &self->data;
198
199 if (data->region != MAP_FAILED)
200 munmap(data->region, data->mapsize);
201 data->region = MAP_FAILED;
202 if (data->expected_pfn != MAP_FAILED)
203 munmap(data->expected_pfn, sizeof(unsigned long));
204 data->expected_pfn = MAP_FAILED;
205 semctl(data->semid, 0, IPC_RMID);
206 data->semid = -1;
207
208 close(data->pipefd[0]);
209
210 switch (data->backend) {
211 case ANON:
212 break;
213 case SHM:
214 shm_unlink(data->filename);
215 break;
216 case NORM_FILE:
217 unlink(data->filename);
218 break;
219 }
220 }
221
access_region(struct global_data * data)222 void access_region(struct global_data *data)
223 {
224 /*
225 * Force read "region" to make sure page fault in.
226 */
227 FORCE_READ(*data->region);
228 }
229
try_to_move_page(char * region)230 int try_to_move_page(char *region)
231 {
232 int ret;
233 int node;
234 int status = 0;
235 int failures = 0;
236
237 ret = move_pages(0, 1, (void **)®ion, NULL, &status, MPOL_MF_MOVE_ALL);
238 if (ret != 0) {
239 perror("Failed to get original numa");
240 return FAIL_ON_WORK;
241 }
242
243 /* Pick up a different target node */
244 for (node = 0; node <= numa_max_node(); node++) {
245 if (numa_bitmask_isbitset(numa_all_nodes_ptr, node) && node != status)
246 break;
247 }
248
249 if (node > numa_max_node()) {
250 ksft_print_msg("Couldn't find available numa node for testing\n");
251 return FAIL_ON_WORK;
252 }
253
254 while (1) {
255 ret = move_pages(0, 1, (void **)®ion, &node, &status, MPOL_MF_MOVE_ALL);
256
257 /* migrate successfully */
258 if (!ret)
259 break;
260
261 /* error happened */
262 if (ret < 0) {
263 ksft_perror("Failed to move pages");
264 return FAIL_ON_WORK;
265 }
266
267 /* migration is best effort; try again */
268 if (++failures >= 100)
269 return FAIL_ON_WORK;
270 }
271
272 return 0;
273 }
274
move_region(struct global_data * data)275 int move_region(struct global_data *data)
276 {
277 int ret;
278 int pagemap_fd;
279
280 ret = try_to_move_page(data->region);
281 if (ret != 0)
282 return ret;
283
284 pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
285 if (pagemap_fd == -1)
286 return FAIL_ON_WORK;
287 *data->expected_pfn = pagemap_get_pfn(pagemap_fd, data->region);
288
289 return 0;
290 }
291
has_same_pfn(struct global_data * data)292 int has_same_pfn(struct global_data *data)
293 {
294 unsigned long pfn;
295 int pagemap_fd;
296
297 if (data->region == MAP_FAILED)
298 return 0;
299
300 pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
301 if (pagemap_fd == -1)
302 return FAIL_ON_CHECK;
303
304 pfn = pagemap_get_pfn(pagemap_fd, data->region);
305 if (pfn != *data->expected_pfn)
306 return FAIL_ON_CHECK;
307
308 return 0;
309 }
310
TEST_F(migrate,anon)311 TEST_F(migrate, anon)
312 {
313 struct global_data *data = &self->data;
314
315 /* Map an area and fault in */
316 data->region = mmap(0, data->mapsize, PROT_READ | PROT_WRITE,
317 MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
318 ASSERT_NE(data->region, MAP_FAILED);
319 memset(data->region, 0xcf, data->mapsize);
320
321 data->do_prepare = access_region;
322 data->do_work = move_region;
323 data->do_check = has_same_pfn;
324
325 propagate_children(_metadata, data);
326 }
327
TEST_F(migrate,shm)328 TEST_F(migrate, shm)
329 {
330 int shm_fd;
331 struct global_data *data = &self->data;
332
333 snprintf(data->filename, MAX_FILENAME_LEN, "%s%s", PREFIX, suffixes[SHM]);
334 shm_fd = shm_open(data->filename, O_CREAT | O_RDWR, 0666);
335 ASSERT_NE(shm_fd, -1);
336 ftruncate(shm_fd, data->mapsize);
337 data->backend = SHM;
338
339 /* Map a shared area and fault in */
340 data->region = mmap(0, data->mapsize, PROT_READ | PROT_WRITE,
341 MAP_SHARED, shm_fd, 0);
342 ASSERT_NE(data->region, MAP_FAILED);
343 memset(data->region, 0xcf, data->mapsize);
344 close(shm_fd);
345
346 data->do_prepare = access_region;
347 data->do_work = move_region;
348 data->do_check = has_same_pfn;
349
350 propagate_children(_metadata, data);
351 }
352
TEST_F(migrate,file)353 TEST_F(migrate, file)
354 {
355 int fd;
356 struct global_data *data = &self->data;
357
358 snprintf(data->filename, MAX_FILENAME_LEN, "%s%s", PREFIX, suffixes[NORM_FILE]);
359 fd = open(data->filename, O_CREAT | O_RDWR | O_EXCL, 0666);
360 ASSERT_NE(fd, -1);
361 ftruncate(fd, data->mapsize);
362 data->backend = NORM_FILE;
363
364 /* Map a shared area and fault in */
365 data->region = mmap(0, data->mapsize, PROT_READ | PROT_WRITE,
366 MAP_SHARED, fd, 0);
367 ASSERT_NE(data->region, MAP_FAILED);
368 memset(data->region, 0xcf, data->mapsize);
369 close(fd);
370
371 data->do_prepare = access_region;
372 data->do_work = move_region;
373 data->do_check = has_same_pfn;
374
375 propagate_children(_metadata, data);
376 }
377
prepare_local_region(struct global_data * data)378 void prepare_local_region(struct global_data *data)
379 {
380 /* Allocate range and set the same data */
381 data->region = mmap(NULL, data->mapsize, PROT_READ|PROT_WRITE,
382 MAP_PRIVATE|MAP_ANON, -1, 0);
383 if (data->region == MAP_FAILED)
384 return;
385
386 memset(data->region, 0xcf, data->mapsize);
387 }
388
merge_and_migrate(struct global_data * data)389 int merge_and_migrate(struct global_data *data)
390 {
391 int pagemap_fd;
392 int ret = 0;
393
394 if (data->region == MAP_FAILED)
395 return FAIL_ON_WORK;
396
397 if (ksm_start() < 0)
398 return FAIL_ON_WORK;
399
400 ret = try_to_move_page(data->region);
401
402 pagemap_fd = open("/proc/self/pagemap", O_RDONLY);
403 if (pagemap_fd == -1)
404 return FAIL_ON_WORK;
405 *data->expected_pfn = pagemap_get_pfn(pagemap_fd, data->region);
406
407 return ret;
408 }
409
TEST_F(migrate,ksm)410 TEST_F(migrate, ksm)
411 {
412 int ret;
413 struct global_data *data = &self->data;
414
415 if (ksm_stop() < 0)
416 SKIP(return, "accessing \"/sys/kernel/mm/ksm/run\") failed");
417 if (ksm_get_full_scans() < 0)
418 SKIP(return, "accessing \"/sys/kernel/mm/ksm/full_scan\") failed");
419
420 ret = prctl(PR_SET_MEMORY_MERGE, 1, 0, 0, 0);
421 if (ret < 0 && errno == EINVAL)
422 SKIP(return, "PR_SET_MEMORY_MERGE not supported");
423 else if (ret)
424 ksft_exit_fail_perror("PR_SET_MEMORY_MERGE=1 failed");
425
426 data->do_prepare = prepare_local_region;
427 data->do_work = merge_and_migrate;
428 data->do_check = has_same_pfn;
429
430 propagate_children(_metadata, data);
431 }
432
433 TEST_HARNESS_MAIN
434