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