1 | /* Generic test case for CPU affinity functions. |
2 | Copyright (C) 2015-2024 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | /* This file is included by the tst-affinity*.c files to test the two |
20 | variants of the functions, under different conditions. The |
21 | following functions have to be defined: |
22 | |
23 | static int getaffinity (size_t, cpu_set_t *); |
24 | static int setaffinity (size_t, const cpu_set_t *); |
25 | static bool early_test (struct conf *); |
26 | |
27 | The first two functions shall affect the affinity mask for the |
28 | current thread and return 0 for success, -1 for error (with an |
29 | error code in errno). |
30 | |
31 | early_test is invoked before the tests in this file affect the |
32 | affinity masks. If it returns true, testing continues, otherwise |
33 | no more tests run and the overall test exits with status 1. |
34 | */ |
35 | |
36 | #include <errno.h> |
37 | #include <limits.h> |
38 | #include <sched.h> |
39 | #include <stdbool.h> |
40 | #include <stdio.h> |
41 | |
42 | /* CPU set configuration determined. Can be used from early_test. */ |
43 | struct conf |
44 | { |
45 | int set_size; /* in bits */ |
46 | int last_cpu; |
47 | }; |
48 | |
49 | static int |
50 | find_set_size (void) |
51 | { |
52 | /* There is considerable controversy about how to determine the size |
53 | of the kernel CPU mask. The probing loop below is only intended |
54 | for testing purposes. */ |
55 | for (int num_cpus = 64; num_cpus <= INT_MAX / 2; ++num_cpus) |
56 | { |
57 | cpu_set_t *set = CPU_ALLOC (num_cpus); |
58 | size_t size = CPU_ALLOC_SIZE (num_cpus); |
59 | |
60 | if (set == NULL) |
61 | { |
62 | printf (format: "error: CPU_ALLOC (%d) failed\n" , num_cpus); |
63 | return -1; |
64 | } |
65 | if (getaffinity (size, set) == 0) |
66 | { |
67 | CPU_FREE (set); |
68 | return num_cpus; |
69 | } |
70 | if (errno != EINVAL) |
71 | { |
72 | printf (format: "error: getaffinity for %d CPUs: %m\n" , num_cpus); |
73 | CPU_FREE (set); |
74 | return -1; |
75 | } |
76 | CPU_FREE (set); |
77 | } |
78 | puts (s: "error: Cannot find maximum CPU number" ); |
79 | return -1; |
80 | } |
81 | |
82 | static int |
83 | find_last_cpu (const cpu_set_t *set, size_t size) |
84 | { |
85 | /* We need to determine the set size with CPU_COUNT_S and the |
86 | cpus_found counter because there is no direct way to obtain the |
87 | actual CPU set size, in bits, from the value of |
88 | CPU_ALLOC_SIZE. */ |
89 | size_t cpus_found = 0; |
90 | size_t total_cpus = CPU_COUNT_S (size, set); |
91 | int last_cpu = -1; |
92 | |
93 | for (int cpu = 0; cpus_found < total_cpus; ++cpu) |
94 | { |
95 | if (CPU_ISSET_S (cpu, size, set)) |
96 | { |
97 | last_cpu = cpu; |
98 | ++cpus_found; |
99 | } |
100 | } |
101 | return last_cpu; |
102 | } |
103 | |
104 | static void |
105 | setup_conf (struct conf *conf) |
106 | { |
107 | *conf = (struct conf) {-1, -1}; |
108 | conf->set_size = find_set_size (); |
109 | if (conf->set_size > 0) |
110 | { |
111 | cpu_set_t *set = CPU_ALLOC (conf->set_size); |
112 | |
113 | if (set == NULL) |
114 | { |
115 | printf (format: "error: CPU_ALLOC (%d) failed\n" , conf->set_size); |
116 | CPU_FREE (set); |
117 | return; |
118 | } |
119 | if (getaffinity (CPU_ALLOC_SIZE (conf->set_size), set) < 0) |
120 | { |
121 | printf (format: "error: getaffinity failed: %m\n" ); |
122 | CPU_FREE (set); |
123 | return; |
124 | } |
125 | conf->last_cpu = find_last_cpu (set, CPU_ALLOC_SIZE (conf->set_size)); |
126 | if (conf->last_cpu < 0) |
127 | puts (s: "info: No test CPU found" ); |
128 | CPU_FREE (set); |
129 | } |
130 | } |
131 | |
132 | static bool |
133 | test_size (const struct conf *conf, size_t size) |
134 | { |
135 | if (size < conf->set_size) |
136 | { |
137 | printf (format: "info: Test not run for CPU set size %zu\n" , size); |
138 | return true; |
139 | } |
140 | |
141 | cpu_set_t *initial_set = CPU_ALLOC (size); |
142 | cpu_set_t *set2 = CPU_ALLOC (size); |
143 | cpu_set_t *active_cpu_set = CPU_ALLOC (size); |
144 | |
145 | if (initial_set == NULL || set2 == NULL || active_cpu_set == NULL) |
146 | { |
147 | printf (format: "error: size %zu: CPU_ALLOC failed\n" , size); |
148 | return false; |
149 | } |
150 | size_t kernel_size = CPU_ALLOC_SIZE (size); |
151 | |
152 | if (getaffinity (size: kernel_size, set: initial_set) < 0) |
153 | { |
154 | printf (format: "error: size %zu: getaffinity: %m\n" , size); |
155 | return false; |
156 | } |
157 | if (setaffinity (size: kernel_size, set: initial_set) < 0) |
158 | { |
159 | printf (format: "error: size %zu: setaffinity: %m\n" , size); |
160 | return true; |
161 | } |
162 | |
163 | /* Use one-CPU set to test switching between CPUs. */ |
164 | int last_active_cpu = -1; |
165 | for (int cpu = 0; cpu <= conf->last_cpu; ++cpu) |
166 | { |
167 | int active_cpu = sched_getcpu (); |
168 | if (last_active_cpu >= 0 && last_active_cpu != active_cpu) |
169 | { |
170 | printf (format: "error: Unexpected CPU %d, expected %d\n" , |
171 | active_cpu, last_active_cpu); |
172 | return false; |
173 | } |
174 | |
175 | if (!CPU_ISSET_S (cpu, kernel_size, initial_set)) |
176 | continue; |
177 | last_active_cpu = cpu; |
178 | |
179 | CPU_ZERO_S (kernel_size, active_cpu_set); |
180 | CPU_SET_S (cpu, kernel_size, active_cpu_set); |
181 | if (setaffinity (size: kernel_size, set: active_cpu_set) < 0) |
182 | { |
183 | printf (format: "error: size %zu: setaffinity (%d): %m\n" , size, cpu); |
184 | return false; |
185 | } |
186 | active_cpu = sched_getcpu (); |
187 | if (active_cpu != cpu) |
188 | { |
189 | printf (format: "error: Unexpected CPU %d, expected %d\n" , active_cpu, cpu); |
190 | return false; |
191 | } |
192 | unsigned int numa_cpu, numa_node; |
193 | if (getcpu (&numa_cpu, &numa_node) != 0) |
194 | { |
195 | printf (format: "error: getcpu: %m\n" ); |
196 | return false; |
197 | } |
198 | if ((unsigned int) active_cpu != numa_cpu) |
199 | { |
200 | printf (format: "error: Unexpected CPU %d, expected %d\n" , |
201 | active_cpu, numa_cpu); |
202 | return false; |
203 | } |
204 | if (getaffinity (size: kernel_size, set: set2) < 0) |
205 | { |
206 | printf (format: "error: size %zu: getaffinity (2): %m\n" , size); |
207 | return false; |
208 | } |
209 | if (!CPU_EQUAL_S (kernel_size, active_cpu_set, set2)) |
210 | { |
211 | printf (format: "error: size %zu: CPU sets do not match\n" , size); |
212 | return false; |
213 | } |
214 | } |
215 | |
216 | /* Test setting the all-ones set. */ |
217 | for (int cpu = 0; cpu < size; ++cpu) |
218 | CPU_SET_S (cpu, kernel_size, set2); |
219 | if (setaffinity (size: kernel_size, set: set2) < 0) |
220 | { |
221 | printf (format: "error: size %zu: setaffinity (3): %m\n" , size); |
222 | return false; |
223 | } |
224 | |
225 | if (setaffinity (size: kernel_size, set: initial_set) < 0) |
226 | { |
227 | printf (format: "error: size %zu: setaffinity (4): %m\n" , size); |
228 | return false; |
229 | } |
230 | if (getaffinity (size: kernel_size, set: set2) < 0) |
231 | { |
232 | printf (format: "error: size %zu: getaffinity (3): %m\n" , size); |
233 | return false; |
234 | } |
235 | if (!CPU_EQUAL_S (kernel_size, initial_set, set2)) |
236 | { |
237 | printf (format: "error: size %zu: CPU sets do not match (2)\n" , size); |
238 | return false; |
239 | } |
240 | |
241 | CPU_FREE (initial_set); |
242 | CPU_FREE (set2); |
243 | CPU_FREE (active_cpu_set); |
244 | |
245 | return true; |
246 | } |
247 | |
248 | static int |
249 | do_test (void) |
250 | { |
251 | { |
252 | cpu_set_t set; |
253 | if (getaffinity (size: sizeof (set), set: &set) < 0 && errno == ENOSYS) |
254 | { |
255 | puts (s: "warning: getaffinity not supported, test cannot run" ); |
256 | return 0; |
257 | } |
258 | if (sched_getcpu () < 0 && errno == ENOSYS) |
259 | { |
260 | puts (s: "warning: sched_getcpu not supported, test cannot run" ); |
261 | return 0; |
262 | } |
263 | } |
264 | |
265 | struct conf conf; |
266 | setup_conf (&conf); |
267 | /* Note: The CPU set size in bits can be less than the CPU count |
268 | (and the maximum test CPU) because the userspace interface rounds |
269 | up the bit count, and the rounded-up buffer size is passed into |
270 | the kernel. The kernel does not know that some of the buffer are |
271 | actually padding, and writes data there. */ |
272 | printf (format: "info: Detected CPU set size (in bits): %d\n" , conf.set_size); |
273 | printf (format: "info: Maximum test CPU: %d\n" , conf.last_cpu); |
274 | if (conf.set_size < 0 || conf.last_cpu < 0) |
275 | return 1; |
276 | |
277 | if (!early_test (&conf)) |
278 | return 1; |
279 | |
280 | bool error = false; |
281 | error |= !test_size (conf: &conf, size: 1024); |
282 | error |= !test_size (conf: &conf, size: conf.set_size); |
283 | error |= !test_size (conf: &conf, size: 2); |
284 | error |= !test_size (conf: &conf, size: 32); |
285 | error |= !test_size (conf: &conf, size: 40); |
286 | error |= !test_size (conf: &conf, size: 64); |
287 | error |= !test_size (conf: &conf, size: 96); |
288 | error |= !test_size (conf: &conf, size: 128); |
289 | error |= !test_size (conf: &conf, size: 256); |
290 | error |= !test_size (conf: &conf, size: 8192); |
291 | return error; |
292 | } |
293 | |
294 | #define TEST_FUNCTION do_test () |
295 | #include "../test-skeleton.c" |
296 | |