-
Notifications
You must be signed in to change notification settings - Fork 63
/
susp.c
373 lines (325 loc) · 9.86 KB
/
susp.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
/*
* susp.c
*
* Demonstrate an implementation of thread suspend and resume (similar
* to the Solaris thr_suspend/thr_continue functions) using portable
* POSIX functions.
*
* Note 1: Use of suspend and resume requires extreme care. You can
* easily deadlock your application by suspending a thread that holds
* some resource -- for example, a thread calling printf to print a
* message may have libc mutexes locked, and no other thread will be
* able to return from printf until the suspended thread is resumed.
*
* Note 2: This program is called "susp" rather than "suspend" to
* avoid confusion (by your shell) with the suspend command.
*
* Note 3: This simple program will fail if any thread terminates
* during the test. The value of ITERATIONS must be adjusted to a
* value sufficiently large that the main thread can complete its two
* suspend/continue loops before any thread_routine threads terminate.
* */
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <semaphore.h>
#include "errors.h"
#define THREAD_COUNT 20
#define ITERATIONS 80000
typedef struct {
int inuse; /* 1 if in use, else 0 */
pthread_t id; /* Thread ID */
} Victim_t;
sem_t sem;
int thread_count = THREAD_COUNT;
int iterations = ITERATIONS;
pthread_mutex_t mut = PTHREAD_MUTEX_INITIALIZER;
pthread_once_t once = PTHREAD_ONCE_INIT;
Victim_t *array = NULL;
int bottom = 0;
/*
* Handle SIGUSR1 in the target thread, to suspend it until receiving
* SIGUSR2 (resume). Note that this is run with both SIGUSR1 and
* SIGUSR2 blocked. Having SIGUSR2 blocked prevents a resume before we
* can finish the suspend protocol.
*/
void
suspend_signal_handler (int sig)
{
sigset_t signal_set;
/*
* Block all signals except SIGUSR2 while suspended.
*/
sigfillset (&signal_set);
sigdelset (&signal_set, SIGUSR2);
sem_post (&sem);
sigsuspend (&signal_set);
/*
* Once here, the resume signal handler has run to completion.
*/
return;
}
/*
* Handle SIGUSR2 in the target thread, to resume it. Note that
* the signal handler does nothing. It exists only because we need
* to cause sigsuspend() to return.
*/
void
resume_signal_handler (int sig)
{
return;
}
/*
* Dynamically initialize the "suspend package" when first used
* (called by pthread_once).
*/
void
suspend_init_routine (void)
{
int status;
struct sigaction sigusr1, sigusr2;
/*
* Initialize a semaphore, to be used by the signal handler to
* confirm suspension. We only need one, because suspend & resume
* are fully serialized by a mutex.
*/
status = sem_init (&sem, 0, 1);
if (status == -1)
errno_abort ("Initializing semaphore");
/*
* Allocate the suspended threads array. This array is used to guarantee
* idempotency.
*/
bottom = 10;
array = (Victim_t*) calloc (bottom, sizeof (Victim_t));
/*
* Install the signal handlers for suspend/resume.
*
* We add SIGUSR2 to the sa_mask field for the SIGUSR1 handler. That
* avoids a race if one thread suspends the target while another resumes
* that same target. (The SIGUSR2 signal cannot be delivered before the
* target thread calls sigsuspend.)
*/
sigusr1.sa_flags = 0;
sigusr1.sa_handler = suspend_signal_handler;
sigemptyset (&sigusr1.sa_mask);
sigaddset (&sigusr1.sa_mask, SIGUSR2);
sigusr2.sa_flags = 0;
sigusr2.sa_handler = resume_signal_handler;
sigemptyset (&sigusr2.sa_mask);
status = sigaction (SIGUSR1, &sigusr1, NULL);
if (status == -1)
errno_abort ("Installing suspend handler");
status = sigaction (SIGUSR2, &sigusr2, NULL);
if (status == -1)
errno_abort ("Installing resume handler");
return;
}
/*
* Suspend a thread by sending it a signal (SIGUSR1), which will
* block the thread until another signal (SIGUSR2) arrives.
*
* Multiple calls to thd_suspend for a single thread have no
* additional effect on the thread -- a single thd_continue
* call will cause it to resume execution.
*/
int
thd_suspend (pthread_t target_thread)
{
int status;
int i;
/*
* The first call to thd_suspend will initialize the
* package.
*/
status = pthread_once (&once, suspend_init_routine);
if (status != 0)
return status;
/*
* Serialize access to suspend, makes life easier
*/
status = pthread_mutex_lock (&mut);
if (status != 0)
return status;
/*
* Threads that are suspended are added to the target_array; a request to
* suspend a thread already listed in the array is ignored.
*/
for (i = 0; i < bottom; i++) {
if (array[i].inuse
&& pthread_equal (array[i].id, target_thread)) {
status = pthread_mutex_unlock (&mut);
return status;
}
}
/*
* Ok, we really need to suspend this thread. So, lets find the location
* in the array that we'll use. If we run off the end, realloc the array
* for more space. We allocate 2 new array slots; we'll use one, and leave
* the other for the next time.
*/
i = 0;
while (i < bottom && array[i].inuse)
i++;
if (i >= bottom) {
bottom += 2;
array = (Victim_t*)realloc (
array, (bottom * sizeof (Victim_t)));
if (array == NULL) {
pthread_mutex_unlock (&mut);
return errno;
}
array[bottom-1].inuse = 0; /* Clear new last entry */
}
/*
* Initialize the target's data. We initialize a semaphore to synchronize
* with the signal handler. After sending the signal, we wait until the
* signal handler posts the semaphore. Then we can destroy the semaphore,
* because we won't need it again.
*/
array[i].id = target_thread;
array[i].inuse = 1;
status = pthread_kill (target_thread, SIGUSR1);
if (status != 0) {
pthread_mutex_unlock (&mut);
return status;
}
/*
* Wait for the victim to acknowledge suspension.
*/
while ((status = sem_wait (&sem)) != 0) {
if (errno != EINTR) {
pthread_mutex_unlock (&mut);
return errno;
}
}
status = pthread_mutex_unlock (&mut);
return status;
}
/*
* Resume a suspended thread by sending it SIGUSR2 to break
* it out of the sigsuspend() in which it's waiting. If the
* target thread isn't suspended, return with success.
*/
int
thd_continue (pthread_t target_thread)
{
int status;
int i = 0;
/*
* The first call to thd_suspend will initialize the package.
*/
status = pthread_once (&once, suspend_init_routine);
if (status != 0)
return status;
/*
* Serialize access to suspend, makes life easier.
*/
status = pthread_mutex_lock (&mut);
if (status != 0)
return status;
/*
* Make sure the thread is in the suspend array. If not, it hasn't been
* suspended (or it has already been resumed) and we can just carry on.
*/
while (i < bottom && array[i].inuse
&& pthread_equal (array[i].id, target_thread))
i++;
if (i >= bottom) {
pthread_mutex_unlock (&mut);
return 0;
}
/*
* Signal the thread to continue, and remove the thread from
* the suspended array.
*/
status = pthread_kill (target_thread, SIGUSR2);
if (status != 0) {
pthread_mutex_unlock (&mut);
return status;
}
array[i].inuse = 0; /* Clear array element */
status = pthread_mutex_unlock (&mut);
return status;
}
static void *
thread_routine (void *arg)
{
int number = (int)arg;
int status;
int i;
char buffer[128];
for (i = 1; i <= iterations; i++) {
/*
* Every time each thread does 2000 interations, print a progress
* report.
*/
if (i % 2000 == 0) {
sprintf (
buffer, "Thread %02d: %d\n",
number, i);
write (1, buffer, strlen (buffer));
}
sched_yield ();
}
return (void *)0;
}
int
main (int argc, char *argv[])
{
pthread_t threads[THREAD_COUNT];
pthread_attr_t detach;
int status;
void *result;
int i;
status = pthread_attr_init (&detach);
if (status != 0)
err_abort (status, "Init attributes object");
status = pthread_attr_setdetachstate (
&detach, PTHREAD_CREATE_DETACHED);
if (status != 0)
err_abort (status, "Set create-detached");
for (i = 0; i< THREAD_COUNT; i++) {
status = pthread_create (
&threads[i], &detach, thread_routine, (void *)i);
if (status != 0)
err_abort (status, "Create thread");
}
sleep (1);
for (i = 0; i < THREAD_COUNT/2; i++) {
printf ("Suspending thread %d.\n", i);
status = thd_suspend (threads[i]);
if (status != 0)
err_abort (status, "Suspend thread");
}
printf ("Sleeping ...\n");
sleep (1);
for (i = 0; i < THREAD_COUNT/2; i++) {
printf ("Continuing thread %d.\n", i);
status = thd_continue (threads[i]);
if (status != 0)
err_abort (status, "Suspend thread");
}
for (i = THREAD_COUNT/2; i < THREAD_COUNT; i++) {
printf ("Suspending thread %d.\n", i);
status = thd_suspend (threads[i]);
if (status != 0)
err_abort (status, "Suspend thread");
}
printf ("Sleeping ...\n");
sleep (1);
for (i = THREAD_COUNT/2; i < THREAD_COUNT; i++) {
printf ("Continuing thread %d.\n", i);
status = thd_continue (threads[i]);
if (status != 0)
err_abort (status, "Continue thread");
}
/*
* Request that each thread terminate. We don't bother waiting for them;
* just trust that they will terminate in "reasonable time". When the last
* thread exits, the process will exit.
*/
for (i = 0; i < THREAD_COUNT; i++)
pthread_cancel (threads[i]);
pthread_exit (NULL); /* Let threads finish */
}