1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * The main purpose of the tests here is to exercise the migration entry code |
4 | * paths in the kernel. |
5 | */ |
6 | |
7 | #include "../kselftest_harness.h" |
8 | #include <strings.h> |
9 | #include <pthread.h> |
10 | #include <numa.h> |
11 | #include <numaif.h> |
12 | #include <sys/mman.h> |
13 | #include <sys/prctl.h> |
14 | #include <sys/types.h> |
15 | #include <signal.h> |
16 | #include <time.h> |
17 | |
18 | #define TWOMEG (2<<20) |
19 | #define RUNTIME (20) |
20 | |
21 | #define ALIGN(x, a) (((x) + (a - 1)) & (~((a) - 1))) |
22 | |
23 | FIXTURE(migration) |
24 | { |
25 | pthread_t *threads; |
26 | pid_t *pids; |
27 | int nthreads; |
28 | int n1; |
29 | int n2; |
30 | }; |
31 | |
32 | FIXTURE_SETUP(migration) |
33 | { |
34 | int n; |
35 | |
36 | ASSERT_EQ(numa_available(), 0); |
37 | self->nthreads = numa_num_task_cpus() - 1; |
38 | self->n1 = -1; |
39 | self->n2 = -1; |
40 | |
41 | for (n = 0; n < numa_max_possible_node(); n++) |
42 | if (numa_bitmask_isbitset(numa_all_nodes_ptr, n)) { |
43 | if (self->n1 == -1) { |
44 | self->n1 = n; |
45 | } else { |
46 | self->n2 = n; |
47 | break; |
48 | } |
49 | } |
50 | |
51 | self->threads = malloc(self->nthreads * sizeof(*self->threads)); |
52 | ASSERT_NE(self->threads, NULL); |
53 | self->pids = malloc(self->nthreads * sizeof(*self->pids)); |
54 | ASSERT_NE(self->pids, NULL); |
55 | }; |
56 | |
57 | FIXTURE_TEARDOWN(migration) |
58 | { |
59 | free(self->threads); |
60 | free(self->pids); |
61 | } |
62 | |
63 | int migrate(uint64_t *ptr, int n1, int n2) |
64 | { |
65 | int ret, tmp; |
66 | int status = 0; |
67 | struct timespec ts1, ts2; |
68 | |
69 | if (clock_gettime(CLOCK_MONOTONIC, &ts1)) |
70 | return -1; |
71 | |
72 | while (1) { |
73 | if (clock_gettime(CLOCK_MONOTONIC, &ts2)) |
74 | return -1; |
75 | |
76 | if (ts2.tv_sec - ts1.tv_sec >= RUNTIME) |
77 | return 0; |
78 | |
79 | ret = move_pages(0, 1, (void **) &ptr, &n2, &status, |
80 | MPOL_MF_MOVE_ALL); |
81 | if (ret) { |
82 | if (ret > 0) |
83 | printf("Didn't migrate %d pages\n" , ret); |
84 | else |
85 | perror("Couldn't migrate pages" ); |
86 | return -2; |
87 | } |
88 | |
89 | tmp = n2; |
90 | n2 = n1; |
91 | n1 = tmp; |
92 | } |
93 | |
94 | return 0; |
95 | } |
96 | |
97 | void *access_mem(void *ptr) |
98 | { |
99 | volatile uint64_t y = 0; |
100 | volatile uint64_t *x = ptr; |
101 | |
102 | while (1) { |
103 | pthread_testcancel(); |
104 | y += *x; |
105 | |
106 | /* Prevent the compiler from optimizing out the writes to y: */ |
107 | asm volatile("" : "+r" (y)); |
108 | } |
109 | |
110 | return NULL; |
111 | } |
112 | |
113 | /* |
114 | * Basic migration entry testing. One thread will move pages back and forth |
115 | * between nodes whilst other threads try and access them triggering the |
116 | * migration entry wait paths in the kernel. |
117 | */ |
118 | TEST_F_TIMEOUT(migration, private_anon, 2*RUNTIME) |
119 | { |
120 | uint64_t *ptr; |
121 | int i; |
122 | |
123 | if (self->nthreads < 2 || self->n1 < 0 || self->n2 < 0) |
124 | SKIP(return, "Not enough threads or NUMA nodes available" ); |
125 | |
126 | ptr = mmap(NULL, TWOMEG, PROT_READ | PROT_WRITE, |
127 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
128 | ASSERT_NE(ptr, MAP_FAILED); |
129 | |
130 | memset(ptr, 0xde, TWOMEG); |
131 | for (i = 0; i < self->nthreads - 1; i++) |
132 | if (pthread_create(&self->threads[i], NULL, access_mem, ptr)) |
133 | perror("Couldn't create thread" ); |
134 | |
135 | ASSERT_EQ(migrate(ptr, self->n1, self->n2), 0); |
136 | for (i = 0; i < self->nthreads - 1; i++) |
137 | ASSERT_EQ(pthread_cancel(self->threads[i]), 0); |
138 | } |
139 | |
140 | /* |
141 | * Same as the previous test but with shared memory. |
142 | */ |
143 | TEST_F_TIMEOUT(migration, shared_anon, 2*RUNTIME) |
144 | { |
145 | pid_t pid; |
146 | uint64_t *ptr; |
147 | int i; |
148 | |
149 | if (self->nthreads < 2 || self->n1 < 0 || self->n2 < 0) |
150 | SKIP(return, "Not enough threads or NUMA nodes available" ); |
151 | |
152 | ptr = mmap(NULL, TWOMEG, PROT_READ | PROT_WRITE, |
153 | MAP_SHARED | MAP_ANONYMOUS, -1, 0); |
154 | ASSERT_NE(ptr, MAP_FAILED); |
155 | |
156 | memset(ptr, 0xde, TWOMEG); |
157 | for (i = 0; i < self->nthreads - 1; i++) { |
158 | pid = fork(); |
159 | if (!pid) { |
160 | prctl(PR_SET_PDEATHSIG, SIGHUP); |
161 | /* Parent may have died before prctl so check now. */ |
162 | if (getppid() == 1) |
163 | kill(getpid(), SIGHUP); |
164 | access_mem(ptr); |
165 | } else { |
166 | self->pids[i] = pid; |
167 | } |
168 | } |
169 | |
170 | ASSERT_EQ(migrate(ptr, self->n1, self->n2), 0); |
171 | for (i = 0; i < self->nthreads - 1; i++) |
172 | ASSERT_EQ(kill(self->pids[i], SIGTERM), 0); |
173 | } |
174 | |
175 | /* |
176 | * Tests the pmd migration entry paths. |
177 | */ |
178 | TEST_F_TIMEOUT(migration, private_anon_thp, 2*RUNTIME) |
179 | { |
180 | uint64_t *ptr; |
181 | int i; |
182 | |
183 | if (self->nthreads < 2 || self->n1 < 0 || self->n2 < 0) |
184 | SKIP(return, "Not enough threads or NUMA nodes available" ); |
185 | |
186 | ptr = mmap(NULL, 2*TWOMEG, PROT_READ | PROT_WRITE, |
187 | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); |
188 | ASSERT_NE(ptr, MAP_FAILED); |
189 | |
190 | ptr = (uint64_t *) ALIGN((uintptr_t) ptr, TWOMEG); |
191 | ASSERT_EQ(madvise(ptr, TWOMEG, MADV_HUGEPAGE), 0); |
192 | memset(ptr, 0xde, TWOMEG); |
193 | for (i = 0; i < self->nthreads - 1; i++) |
194 | if (pthread_create(&self->threads[i], NULL, access_mem, ptr)) |
195 | perror("Couldn't create thread" ); |
196 | |
197 | ASSERT_EQ(migrate(ptr, self->n1, self->n2), 0); |
198 | for (i = 0; i < self->nthreads - 1; i++) |
199 | ASSERT_EQ(pthread_cancel(self->threads[i]), 0); |
200 | } |
201 | |
202 | TEST_HARNESS_MAIN |
203 | |