]>
Commit | Line | Data |
---|---|---|
2359035a | 1 | /* Generic test for CPU affinity functions, multi-threaded variant. |
d614a753 | 2 | Copyright (C) 2015-2020 Free Software Foundation, Inc. |
2359035a FW |
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 | |
5a82c748 | 17 | <https://www.gnu.org/licenses/>. */ |
2359035a FW |
18 | |
19 | /* Before including this file, a test has to declare the helper | |
20 | getaffinity and setaffinity functions described in | |
21 | tst-skeleton-affinity.c, which is included below. */ | |
22 | ||
23 | #include <errno.h> | |
24 | #include <pthread.h> | |
25 | #include <stdbool.h> | |
26 | #include <stdlib.h> | |
10cf7f52 | 27 | #include <support/xthread.h> |
2359035a FW |
28 | #include <sys/time.h> |
29 | ||
30 | struct conf; | |
31 | static bool early_test (struct conf *); | |
32 | ||
33 | /* Arbitrary run time for each pass. */ | |
34 | #define PASS_TIMEOUT 2 | |
35 | ||
36 | /* There are two passes (one with sched_yield, one without), and we | |
37 | double the timeout to be on the safe side. */ | |
38 | #define TIMEOUT (2 * PASS_TIMEOUT * 2) | |
39 | ||
40 | #include "tst-skeleton-affinity.c" | |
41 | ||
42 | /* 0 if still running, 1 of stopping requested. */ | |
43 | static int still_running; | |
44 | ||
45 | /* 0 if no scheduling failures, 1 if failures are encountered. */ | |
46 | static int failed; | |
47 | ||
48 | static void * | |
49 | thread_burn_one_cpu (void *closure) | |
50 | { | |
51 | int cpu = (uintptr_t) closure; | |
52 | while (__atomic_load_n (&still_running, __ATOMIC_RELAXED) == 0) | |
53 | { | |
54 | int current = sched_getcpu (); | |
55 | if (sched_getcpu () != cpu) | |
56 | { | |
57 | printf ("error: Pinned thread %d ran on impossible cpu %d\n", | |
58 | cpu, current); | |
59 | __atomic_store_n (&failed, 1, __ATOMIC_RELAXED); | |
60 | /* Terminate early. */ | |
61 | __atomic_store_n (&still_running, 1, __ATOMIC_RELAXED); | |
62 | } | |
63 | } | |
64 | return NULL; | |
65 | } | |
66 | ||
67 | struct burn_thread | |
68 | { | |
69 | pthread_t self; | |
70 | struct conf *conf; | |
71 | cpu_set_t *initial_set; | |
72 | cpu_set_t *seen_set; | |
73 | int thread; | |
74 | }; | |
75 | ||
76 | static void * | |
77 | thread_burn_any_cpu (void *closure) | |
78 | { | |
79 | struct burn_thread *param = closure; | |
80 | ||
81 | /* Schedule this thread around a bit to see if it lands on another | |
82 | CPU. Run this for 2 seconds, once with sched_yield, once | |
83 | without. */ | |
84 | for (int pass = 1; pass <= 2; ++pass) | |
85 | { | |
86 | time_t start = time (NULL); | |
87 | while (time (NULL) - start <= PASS_TIMEOUT) | |
88 | { | |
89 | int cpu = sched_getcpu (); | |
90 | if (cpu > param->conf->last_cpu | |
91 | || !CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (param->conf->set_size), | |
92 | param->initial_set)) | |
93 | { | |
94 | printf ("error: Unpinned thread %d ran on impossible CPU %d\n", | |
95 | param->thread, cpu); | |
96 | __atomic_store_n (&failed, 1, __ATOMIC_RELAXED); | |
97 | return NULL; | |
98 | } | |
99 | CPU_SET_S (cpu, CPU_ALLOC_SIZE (param->conf->set_size), | |
100 | param->seen_set); | |
101 | if (pass == 1) | |
102 | sched_yield (); | |
103 | } | |
104 | } | |
105 | return NULL; | |
106 | } | |
107 | ||
108 | static void | |
109 | stop_and_join_threads (struct conf *conf, cpu_set_t *set, | |
110 | pthread_t *pinned_first, pthread_t *pinned_last, | |
111 | struct burn_thread *other_first, | |
112 | struct burn_thread *other_last) | |
113 | { | |
114 | __atomic_store_n (&still_running, 1, __ATOMIC_RELAXED); | |
115 | for (pthread_t *p = pinned_first; p < pinned_last; ++p) | |
116 | { | |
117 | int cpu = p - pinned_first; | |
118 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), set)) | |
119 | continue; | |
120 | ||
121 | int ret = pthread_join (*p, NULL); | |
122 | if (ret != 0) | |
123 | { | |
124 | printf ("error: Failed to join thread %d: %s\n", cpu, strerror (ret)); | |
125 | fflush (stdout); | |
126 | /* Cannot shut down cleanly with threads still running. */ | |
127 | abort (); | |
128 | } | |
129 | } | |
130 | ||
131 | for (struct burn_thread *p = other_first; p < other_last; ++p) | |
132 | { | |
133 | int cpu = p - other_first; | |
134 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), set)) | |
135 | continue; | |
136 | ||
137 | int ret = pthread_join (p->self, NULL); | |
138 | if (ret != 0) | |
139 | { | |
140 | printf ("error: Failed to join thread %d: %s\n", cpu, strerror (ret)); | |
141 | fflush (stdout); | |
142 | /* Cannot shut down cleanly with threads still running. */ | |
143 | abort (); | |
144 | } | |
145 | } | |
146 | } | |
147 | ||
148 | /* Tries to check that the initial set of CPUs is complete and that | |
149 | the main thread will not run on any other threads. */ | |
150 | static bool | |
151 | early_test (struct conf *conf) | |
152 | { | |
153 | pthread_t *pinned_threads | |
154 | = calloc (conf->last_cpu + 1, sizeof (*pinned_threads)); | |
155 | struct burn_thread *other_threads | |
156 | = calloc (conf->last_cpu + 1, sizeof (*other_threads)); | |
157 | cpu_set_t *initial_set = CPU_ALLOC (conf->set_size); | |
158 | cpu_set_t *scratch_set = CPU_ALLOC (conf->set_size); | |
159 | ||
160 | if (pinned_threads == NULL || other_threads == NULL | |
161 | || initial_set == NULL || scratch_set == NULL) | |
162 | { | |
163 | puts ("error: Memory allocation failure"); | |
164 | return false; | |
165 | } | |
166 | if (getaffinity (CPU_ALLOC_SIZE (conf->set_size), initial_set) < 0) | |
167 | { | |
168 | printf ("error: pthread_getaffinity_np failed: %m\n"); | |
169 | return false; | |
170 | } | |
171 | for (int cpu = 0; cpu <= conf->last_cpu; ++cpu) | |
172 | { | |
173 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set)) | |
174 | continue; | |
175 | other_threads[cpu].conf = conf; | |
176 | other_threads[cpu].initial_set = initial_set; | |
177 | other_threads[cpu].thread = cpu; | |
178 | other_threads[cpu].seen_set = CPU_ALLOC (conf->set_size); | |
179 | if (other_threads[cpu].seen_set == NULL) | |
180 | { | |
181 | puts ("error: Memory allocation failure"); | |
182 | return false; | |
183 | } | |
184 | CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), | |
185 | other_threads[cpu].seen_set); | |
186 | } | |
187 | ||
188 | pthread_attr_t attr; | |
189 | int ret = pthread_attr_init (&attr); | |
190 | if (ret != 0) | |
191 | { | |
192 | printf ("error: pthread_attr_init failed: %s\n", strerror (ret)); | |
193 | return false; | |
194 | } | |
10cf7f52 | 195 | support_set_small_thread_stack_size (&attr); |
2359035a FW |
196 | |
197 | /* Spawn a thread pinned to each available CPU. */ | |
198 | for (int cpu = 0; cpu <= conf->last_cpu; ++cpu) | |
199 | { | |
200 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set)) | |
201 | continue; | |
202 | CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set); | |
203 | CPU_SET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), scratch_set); | |
204 | ret = pthread_attr_setaffinity_np | |
205 | (&attr, CPU_ALLOC_SIZE (conf->set_size), scratch_set); | |
206 | if (ret != 0) | |
207 | { | |
208 | printf ("error: pthread_attr_setaffinity_np for CPU %d failed: %s\n", | |
209 | cpu, strerror (ret)); | |
210 | stop_and_join_threads (conf, initial_set, | |
211 | pinned_threads, pinned_threads + cpu, | |
212 | NULL, NULL); | |
213 | return false; | |
214 | } | |
215 | ret = pthread_create (pinned_threads + cpu, &attr, | |
216 | thread_burn_one_cpu, (void *) (uintptr_t) cpu); | |
217 | if (ret != 0) | |
218 | { | |
219 | printf ("error: pthread_create for CPU %d failed: %s\n", | |
220 | cpu, strerror (ret)); | |
221 | stop_and_join_threads (conf, initial_set, | |
222 | pinned_threads, pinned_threads + cpu, | |
223 | NULL, NULL); | |
224 | return false; | |
225 | } | |
226 | } | |
227 | ||
228 | /* Spawn another set of threads running on all CPUs. */ | |
229 | for (int cpu = 0; cpu <= conf->last_cpu; ++cpu) | |
230 | { | |
231 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set)) | |
232 | continue; | |
10cf7f52 FW |
233 | ret = pthread_create (&other_threads[cpu].self, |
234 | support_small_stack_thread_attribute (), | |
2359035a FW |
235 | thread_burn_any_cpu, other_threads + cpu); |
236 | if (ret != 0) | |
237 | { | |
238 | printf ("error: pthread_create for thread %d failed: %s\n", | |
239 | cpu, strerror (ret)); | |
240 | stop_and_join_threads (conf, initial_set, | |
241 | pinned_threads, | |
242 | pinned_threads + conf->last_cpu + 1, | |
243 | other_threads, other_threads + cpu); | |
244 | return false; | |
245 | } | |
246 | } | |
247 | ||
248 | /* Main thread. */ | |
249 | struct burn_thread main_thread; | |
250 | main_thread.conf = conf; | |
251 | main_thread.initial_set = initial_set; | |
252 | main_thread.seen_set = scratch_set; | |
253 | main_thread.thread = -1; | |
254 | CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), main_thread.seen_set); | |
255 | thread_burn_any_cpu (&main_thread); | |
256 | stop_and_join_threads (conf, initial_set, | |
257 | pinned_threads, | |
258 | pinned_threads + conf->last_cpu + 1, | |
259 | other_threads, other_threads + conf->last_cpu + 1); | |
260 | ||
261 | printf ("info: Main thread ran on %d CPU(s) of %d available CPU(s)\n", | |
262 | CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set), | |
263 | CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), initial_set)); | |
264 | CPU_ZERO_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set); | |
265 | for (int cpu = 0; cpu <= conf->last_cpu; ++cpu) | |
266 | { | |
267 | if (!CPU_ISSET_S (cpu, CPU_ALLOC_SIZE (conf->set_size), initial_set)) | |
268 | continue; | |
269 | CPU_OR_S (CPU_ALLOC_SIZE (conf->set_size), | |
270 | scratch_set, scratch_set, other_threads[cpu].seen_set); | |
271 | CPU_FREE (other_threads[cpu].seen_set); | |
272 | } | |
273 | printf ("info: Other threads ran on %d CPU(s)\n", | |
274 | CPU_COUNT_S (CPU_ALLOC_SIZE (conf->set_size), scratch_set));; | |
275 | ||
276 | ||
277 | pthread_attr_destroy (&attr); | |
278 | CPU_FREE (scratch_set); | |
279 | CPU_FREE (initial_set); | |
280 | free (pinned_threads); | |
281 | free (other_threads); | |
282 | return failed == 0; | |
283 | } |