Mercurial > emacs
comparison gc/irix_threads.c @ 51488:5de98dce4bd1
*** empty log message ***
author | Dave Love <fx@gnu.org> |
---|---|
date | Thu, 05 Jun 2003 17:49:22 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
51487:01d68b199093 | 51488:5de98dce4bd1 |
---|---|
1 /* | |
2 * Copyright (c) 1991-1995 by Xerox Corporation. All rights reserved. | |
3 * Copyright (c) 1996-1999 by Silicon Graphics. All rights reserved. | |
4 * Copyright (c) 1999 by Hewlett-Packard Company. All rights reserved. | |
5 * | |
6 * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED | |
7 * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. | |
8 * | |
9 * Permission is hereby granted to use or copy this program | |
10 * for any purpose, provided the above notices are retained on all copies. | |
11 * Permission to modify the code and to distribute modified code is granted, | |
12 * provided the above notices are retained, and a notice that the code was | |
13 * modified is included with the above copyright notice. | |
14 */ | |
15 /* | |
16 * Support code for Irix (>=6.2) Pthreads. This relies on properties | |
17 * not guaranteed by the Pthread standard. It may or may not be portable | |
18 * to other implementations. | |
19 * | |
20 * This now also includes an initial attempt at thread support for | |
21 * HP/UX 11. | |
22 * | |
23 * Note that there is a lot of code duplication between linux_threads.c | |
24 * and irix_threads.c; any changes made here may need to be reflected | |
25 * there too. | |
26 */ | |
27 | |
28 # if defined(GC_IRIX_THREADS) | |
29 | |
30 # include "private/gc_priv.h" | |
31 # include <pthread.h> | |
32 # include <semaphore.h> | |
33 # include <time.h> | |
34 # include <errno.h> | |
35 # include <unistd.h> | |
36 # include <sys/mman.h> | |
37 # include <sys/time.h> | |
38 | |
39 #undef pthread_create | |
40 #undef pthread_sigmask | |
41 #undef pthread_join | |
42 #undef pthread_detach | |
43 | |
44 #ifdef HANDLE_FORK | |
45 --> Not yet supported. Try porting the code from linux_threads.c. | |
46 #endif | |
47 | |
48 void GC_thr_init(); | |
49 | |
50 #if 0 | |
51 void GC_print_sig_mask() | |
52 { | |
53 sigset_t blocked; | |
54 int i; | |
55 | |
56 if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0) | |
57 ABORT("pthread_sigmask"); | |
58 GC_printf0("Blocked: "); | |
59 for (i = 1; i <= MAXSIG; i++) { | |
60 if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); } | |
61 } | |
62 GC_printf0("\n"); | |
63 } | |
64 #endif | |
65 | |
66 /* We use the allocation lock to protect thread-related data structures. */ | |
67 | |
68 /* The set of all known threads. We intercept thread creation and */ | |
69 /* joins. We never actually create detached threads. We allocate all */ | |
70 /* new thread stacks ourselves. These allow us to maintain this */ | |
71 /* data structure. */ | |
72 /* Protected by GC_thr_lock. */ | |
73 /* Some of this should be declared volatile, but that's incosnsistent */ | |
74 /* with some library routine declarations. */ | |
75 typedef struct GC_Thread_Rep { | |
76 struct GC_Thread_Rep * next; /* More recently allocated threads */ | |
77 /* with a given pthread id come */ | |
78 /* first. (All but the first are */ | |
79 /* guaranteed to be dead, but we may */ | |
80 /* not yet have registered the join.) */ | |
81 pthread_t id; | |
82 word stop; | |
83 # define NOT_STOPPED 0 | |
84 # define PLEASE_STOP 1 | |
85 # define STOPPED 2 | |
86 word flags; | |
87 # define FINISHED 1 /* Thread has exited. */ | |
88 # define DETACHED 2 /* Thread is intended to be detached. */ | |
89 # define CLIENT_OWNS_STACK 4 | |
90 /* Stack was supplied by client. */ | |
91 ptr_t stack; | |
92 ptr_t stack_ptr; /* Valid only when stopped. */ | |
93 /* But must be within stack region at */ | |
94 /* all times. */ | |
95 size_t stack_size; /* 0 for original thread. */ | |
96 void * status; /* Used only to avoid premature */ | |
97 /* reclamation of any data it might */ | |
98 /* reference. */ | |
99 } * GC_thread; | |
100 | |
101 GC_thread GC_lookup_thread(pthread_t id); | |
102 | |
103 /* | |
104 * The only way to suspend threads given the pthread interface is to send | |
105 * signals. Unfortunately, this means we have to reserve | |
106 * a signal, and intercept client calls to change the signal mask. | |
107 * We use SIG_SUSPEND, defined in gc_priv.h. | |
108 */ | |
109 | |
110 pthread_mutex_t GC_suspend_lock = PTHREAD_MUTEX_INITIALIZER; | |
111 /* Number of threads stopped so far */ | |
112 pthread_cond_t GC_suspend_ack_cv = PTHREAD_COND_INITIALIZER; | |
113 pthread_cond_t GC_continue_cv = PTHREAD_COND_INITIALIZER; | |
114 | |
115 void GC_suspend_handler(int sig) | |
116 { | |
117 int dummy; | |
118 GC_thread me; | |
119 sigset_t all_sigs; | |
120 sigset_t old_sigs; | |
121 int i; | |
122 | |
123 if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler"); | |
124 me = GC_lookup_thread(pthread_self()); | |
125 /* The lookup here is safe, since I'm doing this on behalf */ | |
126 /* of a thread which holds the allocation lock in order */ | |
127 /* to stop the world. Thus concurrent modification of the */ | |
128 /* data structure is impossible. */ | |
129 if (PLEASE_STOP != me -> stop) { | |
130 /* Misdirected signal. */ | |
131 pthread_mutex_unlock(&GC_suspend_lock); | |
132 return; | |
133 } | |
134 pthread_mutex_lock(&GC_suspend_lock); | |
135 me -> stack_ptr = (ptr_t)(&dummy); | |
136 me -> stop = STOPPED; | |
137 pthread_cond_signal(&GC_suspend_ack_cv); | |
138 pthread_cond_wait(&GC_continue_cv, &GC_suspend_lock); | |
139 pthread_mutex_unlock(&GC_suspend_lock); | |
140 /* GC_printf1("Continuing 0x%x\n", pthread_self()); */ | |
141 } | |
142 | |
143 | |
144 GC_bool GC_thr_initialized = FALSE; | |
145 | |
146 size_t GC_min_stack_sz; | |
147 | |
148 # define N_FREE_LISTS 25 | |
149 ptr_t GC_stack_free_lists[N_FREE_LISTS] = { 0 }; | |
150 /* GC_stack_free_lists[i] is free list for stacks of */ | |
151 /* size GC_min_stack_sz*2**i. */ | |
152 /* Free lists are linked through first word. */ | |
153 | |
154 /* Return a stack of size at least *stack_size. *stack_size is */ | |
155 /* replaced by the actual stack size. */ | |
156 /* Caller holds allocation lock. */ | |
157 ptr_t GC_stack_alloc(size_t * stack_size) | |
158 { | |
159 register size_t requested_sz = *stack_size; | |
160 register size_t search_sz = GC_min_stack_sz; | |
161 register int index = 0; /* = log2(search_sz/GC_min_stack_sz) */ | |
162 register ptr_t result; | |
163 | |
164 while (search_sz < requested_sz) { | |
165 search_sz *= 2; | |
166 index++; | |
167 } | |
168 if ((result = GC_stack_free_lists[index]) == 0 | |
169 && (result = GC_stack_free_lists[index+1]) != 0) { | |
170 /* Try next size up. */ | |
171 search_sz *= 2; index++; | |
172 } | |
173 if (result != 0) { | |
174 GC_stack_free_lists[index] = *(ptr_t *)result; | |
175 } else { | |
176 result = (ptr_t) GC_scratch_alloc(search_sz + 2*GC_page_size); | |
177 result = (ptr_t)(((word)result + GC_page_size) & ~(GC_page_size - 1)); | |
178 /* Protect hottest page to detect overflow. */ | |
179 # ifdef STACK_GROWS_UP | |
180 /* mprotect(result + search_sz, GC_page_size, PROT_NONE); */ | |
181 # else | |
182 /* mprotect(result, GC_page_size, PROT_NONE); */ | |
183 result += GC_page_size; | |
184 # endif | |
185 } | |
186 *stack_size = search_sz; | |
187 return(result); | |
188 } | |
189 | |
190 /* Caller holds allocation lock. */ | |
191 void GC_stack_free(ptr_t stack, size_t size) | |
192 { | |
193 register int index = 0; | |
194 register size_t search_sz = GC_min_stack_sz; | |
195 | |
196 while (search_sz < size) { | |
197 search_sz *= 2; | |
198 index++; | |
199 } | |
200 if (search_sz != size) ABORT("Bad stack size"); | |
201 *(ptr_t *)stack = GC_stack_free_lists[index]; | |
202 GC_stack_free_lists[index] = stack; | |
203 } | |
204 | |
205 | |
206 | |
207 # define THREAD_TABLE_SZ 128 /* Must be power of 2 */ | |
208 volatile GC_thread GC_threads[THREAD_TABLE_SZ]; | |
209 | |
210 void GC_push_thread_structures GC_PROTO((void)) | |
211 { | |
212 GC_push_all((ptr_t)(GC_threads), (ptr_t)(GC_threads)+sizeof(GC_threads)); | |
213 } | |
214 | |
215 /* Add a thread to GC_threads. We assume it wasn't already there. */ | |
216 /* Caller holds allocation lock. */ | |
217 GC_thread GC_new_thread(pthread_t id) | |
218 { | |
219 int hv = ((word)id) % THREAD_TABLE_SZ; | |
220 GC_thread result; | |
221 static struct GC_Thread_Rep first_thread; | |
222 static GC_bool first_thread_used = FALSE; | |
223 | |
224 if (!first_thread_used) { | |
225 result = &first_thread; | |
226 first_thread_used = TRUE; | |
227 /* Dont acquire allocation lock, since we may already hold it. */ | |
228 } else { | |
229 result = (struct GC_Thread_Rep *) | |
230 GC_INTERNAL_MALLOC(sizeof(struct GC_Thread_Rep), NORMAL); | |
231 } | |
232 if (result == 0) return(0); | |
233 result -> id = id; | |
234 result -> next = GC_threads[hv]; | |
235 GC_threads[hv] = result; | |
236 /* result -> flags = 0; */ | |
237 /* result -> stop = 0; */ | |
238 return(result); | |
239 } | |
240 | |
241 /* Delete a thread from GC_threads. We assume it is there. */ | |
242 /* (The code intentionally traps if it wasn't.) */ | |
243 /* Caller holds allocation lock. */ | |
244 void GC_delete_thread(pthread_t id) | |
245 { | |
246 int hv = ((word)id) % THREAD_TABLE_SZ; | |
247 register GC_thread p = GC_threads[hv]; | |
248 register GC_thread prev = 0; | |
249 | |
250 while (!pthread_equal(p -> id, id)) { | |
251 prev = p; | |
252 p = p -> next; | |
253 } | |
254 if (prev == 0) { | |
255 GC_threads[hv] = p -> next; | |
256 } else { | |
257 prev -> next = p -> next; | |
258 } | |
259 } | |
260 | |
261 /* If a thread has been joined, but we have not yet */ | |
262 /* been notified, then there may be more than one thread */ | |
263 /* in the table with the same pthread id. */ | |
264 /* This is OK, but we need a way to delete a specific one. */ | |
265 void GC_delete_gc_thread(pthread_t id, GC_thread gc_id) | |
266 { | |
267 int hv = ((word)id) % THREAD_TABLE_SZ; | |
268 register GC_thread p = GC_threads[hv]; | |
269 register GC_thread prev = 0; | |
270 | |
271 while (p != gc_id) { | |
272 prev = p; | |
273 p = p -> next; | |
274 } | |
275 if (prev == 0) { | |
276 GC_threads[hv] = p -> next; | |
277 } else { | |
278 prev -> next = p -> next; | |
279 } | |
280 } | |
281 | |
282 /* Return a GC_thread corresponding to a given thread_t. */ | |
283 /* Returns 0 if it's not there. */ | |
284 /* Caller holds allocation lock or otherwise inhibits */ | |
285 /* updates. */ | |
286 /* If there is more than one thread with the given id we */ | |
287 /* return the most recent one. */ | |
288 GC_thread GC_lookup_thread(pthread_t id) | |
289 { | |
290 int hv = ((word)id) % THREAD_TABLE_SZ; | |
291 register GC_thread p = GC_threads[hv]; | |
292 | |
293 while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next; | |
294 return(p); | |
295 } | |
296 | |
297 | |
298 /* Caller holds allocation lock. */ | |
299 void GC_stop_world() | |
300 { | |
301 pthread_t my_thread = pthread_self(); | |
302 register int i; | |
303 register GC_thread p; | |
304 register int result; | |
305 struct timespec timeout; | |
306 | |
307 for (i = 0; i < THREAD_TABLE_SZ; i++) { | |
308 for (p = GC_threads[i]; p != 0; p = p -> next) { | |
309 if (p -> id != my_thread) { | |
310 if (p -> flags & FINISHED) { | |
311 p -> stop = STOPPED; | |
312 continue; | |
313 } | |
314 p -> stop = PLEASE_STOP; | |
315 result = pthread_kill(p -> id, SIG_SUSPEND); | |
316 /* GC_printf1("Sent signal to 0x%x\n", p -> id); */ | |
317 switch(result) { | |
318 case ESRCH: | |
319 /* Not really there anymore. Possible? */ | |
320 p -> stop = STOPPED; | |
321 break; | |
322 case 0: | |
323 break; | |
324 default: | |
325 ABORT("pthread_kill failed"); | |
326 } | |
327 } | |
328 } | |
329 } | |
330 pthread_mutex_lock(&GC_suspend_lock); | |
331 for (i = 0; i < THREAD_TABLE_SZ; i++) { | |
332 for (p = GC_threads[i]; p != 0; p = p -> next) { | |
333 while (p -> id != my_thread && p -> stop != STOPPED) { | |
334 clock_gettime(CLOCK_REALTIME, &timeout); | |
335 timeout.tv_nsec += 50000000; /* 50 msecs */ | |
336 if (timeout.tv_nsec >= 1000000000) { | |
337 timeout.tv_nsec -= 1000000000; | |
338 ++timeout.tv_sec; | |
339 } | |
340 result = pthread_cond_timedwait(&GC_suspend_ack_cv, | |
341 &GC_suspend_lock, | |
342 &timeout); | |
343 if (result == ETIMEDOUT) { | |
344 /* Signal was lost or misdirected. Try again. */ | |
345 /* Duplicate signals should be benign. */ | |
346 result = pthread_kill(p -> id, SIG_SUSPEND); | |
347 } | |
348 } | |
349 } | |
350 } | |
351 pthread_mutex_unlock(&GC_suspend_lock); | |
352 /* GC_printf1("World stopped 0x%x\n", pthread_self()); */ | |
353 } | |
354 | |
355 /* Caller holds allocation lock. */ | |
356 void GC_start_world() | |
357 { | |
358 GC_thread p; | |
359 unsigned i; | |
360 | |
361 /* GC_printf0("World starting\n"); */ | |
362 for (i = 0; i < THREAD_TABLE_SZ; i++) { | |
363 for (p = GC_threads[i]; p != 0; p = p -> next) { | |
364 p -> stop = NOT_STOPPED; | |
365 } | |
366 } | |
367 pthread_mutex_lock(&GC_suspend_lock); | |
368 /* All other threads are at pthread_cond_wait in signal handler. */ | |
369 /* Otherwise we couldn't have acquired the lock. */ | |
370 pthread_mutex_unlock(&GC_suspend_lock); | |
371 pthread_cond_broadcast(&GC_continue_cv); | |
372 } | |
373 | |
374 # ifdef MMAP_STACKS | |
375 --> not really supported yet. | |
376 int GC_is_thread_stack(ptr_t addr) | |
377 { | |
378 register int i; | |
379 register GC_thread p; | |
380 | |
381 for (i = 0; i < THREAD_TABLE_SZ; i++) { | |
382 for (p = GC_threads[i]; p != 0; p = p -> next) { | |
383 if (p -> stack_size != 0) { | |
384 if (p -> stack <= addr && | |
385 addr < p -> stack + p -> stack_size) | |
386 return 1; | |
387 } | |
388 } | |
389 } | |
390 return 0; | |
391 } | |
392 # endif | |
393 | |
394 /* We hold allocation lock. Should do exactly the right thing if the */ | |
395 /* world is stopped. Should not fail if it isn't. */ | |
396 void GC_push_all_stacks() | |
397 { | |
398 register int i; | |
399 register GC_thread p; | |
400 register ptr_t sp = GC_approx_sp(); | |
401 register ptr_t hot, cold; | |
402 pthread_t me = pthread_self(); | |
403 | |
404 if (!GC_thr_initialized) GC_thr_init(); | |
405 /* GC_printf1("Pushing stacks from thread 0x%x\n", me); */ | |
406 for (i = 0; i < THREAD_TABLE_SZ; i++) { | |
407 for (p = GC_threads[i]; p != 0; p = p -> next) { | |
408 if (p -> flags & FINISHED) continue; | |
409 if (pthread_equal(p -> id, me)) { | |
410 hot = GC_approx_sp(); | |
411 } else { | |
412 hot = p -> stack_ptr; | |
413 } | |
414 if (p -> stack_size != 0) { | |
415 # ifdef STACK_GROWS_UP | |
416 cold = p -> stack; | |
417 # else | |
418 cold = p -> stack + p -> stack_size; | |
419 # endif | |
420 } else { | |
421 /* The original stack. */ | |
422 cold = GC_stackbottom; | |
423 } | |
424 # ifdef STACK_GROWS_UP | |
425 GC_push_all_stack(cold, hot); | |
426 # else | |
427 GC_push_all_stack(hot, cold); | |
428 # endif | |
429 } | |
430 } | |
431 } | |
432 | |
433 | |
434 /* We hold the allocation lock. */ | |
435 void GC_thr_init() | |
436 { | |
437 GC_thread t; | |
438 struct sigaction act; | |
439 | |
440 if (GC_thr_initialized) return; | |
441 GC_thr_initialized = TRUE; | |
442 GC_min_stack_sz = HBLKSIZE; | |
443 (void) sigaction(SIG_SUSPEND, 0, &act); | |
444 if (act.sa_handler != SIG_DFL) | |
445 ABORT("Previously installed SIG_SUSPEND handler"); | |
446 /* Install handler. */ | |
447 act.sa_handler = GC_suspend_handler; | |
448 act.sa_flags = SA_RESTART; | |
449 (void) sigemptyset(&act.sa_mask); | |
450 if (0 != sigaction(SIG_SUSPEND, &act, 0)) | |
451 ABORT("Failed to install SIG_SUSPEND handler"); | |
452 /* Add the initial thread, so we can stop it. */ | |
453 t = GC_new_thread(pthread_self()); | |
454 t -> stack_size = 0; | |
455 t -> stack_ptr = (ptr_t)(&t); | |
456 t -> flags = DETACHED; | |
457 } | |
458 | |
459 int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset) | |
460 { | |
461 sigset_t fudged_set; | |
462 | |
463 if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) { | |
464 fudged_set = *set; | |
465 sigdelset(&fudged_set, SIG_SUSPEND); | |
466 set = &fudged_set; | |
467 } | |
468 return(pthread_sigmask(how, set, oset)); | |
469 } | |
470 | |
471 struct start_info { | |
472 void *(*start_routine)(void *); | |
473 void *arg; | |
474 word flags; | |
475 ptr_t stack; | |
476 size_t stack_size; | |
477 sem_t registered; /* 1 ==> in our thread table, but */ | |
478 /* parent hasn't yet noticed. */ | |
479 }; | |
480 | |
481 void GC_thread_exit_proc(void *arg) | |
482 { | |
483 GC_thread me; | |
484 | |
485 LOCK(); | |
486 me = GC_lookup_thread(pthread_self()); | |
487 if (me -> flags & DETACHED) { | |
488 GC_delete_thread(pthread_self()); | |
489 } else { | |
490 me -> flags |= FINISHED; | |
491 } | |
492 UNLOCK(); | |
493 } | |
494 | |
495 int GC_pthread_join(pthread_t thread, void **retval) | |
496 { | |
497 int result; | |
498 GC_thread thread_gc_id; | |
499 | |
500 LOCK(); | |
501 thread_gc_id = GC_lookup_thread(thread); | |
502 /* This is guaranteed to be the intended one, since the thread id */ | |
503 /* cant have been recycled by pthreads. */ | |
504 UNLOCK(); | |
505 result = pthread_join(thread, retval); | |
506 /* Some versions of the Irix pthreads library can erroneously */ | |
507 /* return EINTR when the call succeeds. */ | |
508 if (EINTR == result) result = 0; | |
509 if (result == 0) { | |
510 LOCK(); | |
511 /* Here the pthread thread id may have been recycled. */ | |
512 GC_delete_gc_thread(thread, thread_gc_id); | |
513 UNLOCK(); | |
514 } | |
515 return result; | |
516 } | |
517 | |
518 int GC_pthread_detach(pthread_t thread) | |
519 { | |
520 int result; | |
521 GC_thread thread_gc_id; | |
522 | |
523 LOCK(); | |
524 thread_gc_id = GC_lookup_thread(thread); | |
525 UNLOCK(); | |
526 result = pthread_detach(thread); | |
527 if (result == 0) { | |
528 LOCK(); | |
529 thread_gc_id -> flags |= DETACHED; | |
530 /* Here the pthread thread id may have been recycled. */ | |
531 if (thread_gc_id -> flags & FINISHED) { | |
532 GC_delete_gc_thread(thread, thread_gc_id); | |
533 } | |
534 UNLOCK(); | |
535 } | |
536 return result; | |
537 } | |
538 | |
539 void * GC_start_routine(void * arg) | |
540 { | |
541 struct start_info * si = arg; | |
542 void * result; | |
543 GC_thread me; | |
544 pthread_t my_pthread; | |
545 void *(*start)(void *); | |
546 void *start_arg; | |
547 | |
548 my_pthread = pthread_self(); | |
549 /* If a GC occurs before the thread is registered, that GC will */ | |
550 /* ignore this thread. That's fine, since it will block trying to */ | |
551 /* acquire the allocation lock, and won't yet hold interesting */ | |
552 /* pointers. */ | |
553 LOCK(); | |
554 /* We register the thread here instead of in the parent, so that */ | |
555 /* we don't need to hold the allocation lock during pthread_create. */ | |
556 /* Holding the allocation lock there would make REDIRECT_MALLOC */ | |
557 /* impossible. It probably still doesn't work, but we're a little */ | |
558 /* closer ... */ | |
559 /* This unfortunately means that we have to be careful the parent */ | |
560 /* doesn't try to do a pthread_join before we're registered. */ | |
561 me = GC_new_thread(my_pthread); | |
562 me -> flags = si -> flags; | |
563 me -> stack = si -> stack; | |
564 me -> stack_size = si -> stack_size; | |
565 me -> stack_ptr = (ptr_t)si -> stack + si -> stack_size - sizeof(word); | |
566 UNLOCK(); | |
567 start = si -> start_routine; | |
568 start_arg = si -> arg; | |
569 sem_post(&(si -> registered)); | |
570 pthread_cleanup_push(GC_thread_exit_proc, 0); | |
571 result = (*start)(start_arg); | |
572 me -> status = result; | |
573 me -> flags |= FINISHED; | |
574 pthread_cleanup_pop(1); | |
575 /* This involves acquiring the lock, ensuring that we can't exit */ | |
576 /* while a collection that thinks we're alive is trying to stop */ | |
577 /* us. */ | |
578 return(result); | |
579 } | |
580 | |
581 # define copy_attr(pa_ptr, source) *(pa_ptr) = *(source) | |
582 | |
583 int | |
584 GC_pthread_create(pthread_t *new_thread, | |
585 const pthread_attr_t *attr, | |
586 void *(*start_routine)(void *), void *arg) | |
587 { | |
588 int result; | |
589 GC_thread t; | |
590 void * stack; | |
591 size_t stacksize; | |
592 pthread_attr_t new_attr; | |
593 int detachstate; | |
594 word my_flags = 0; | |
595 struct start_info * si = GC_malloc(sizeof(struct start_info)); | |
596 /* This is otherwise saved only in an area mmapped by the thread */ | |
597 /* library, which isn't visible to the collector. */ | |
598 | |
599 if (0 == si) return(ENOMEM); | |
600 if (0 != sem_init(&(si -> registered), 0, 0)) { | |
601 ABORT("sem_init failed"); | |
602 } | |
603 si -> start_routine = start_routine; | |
604 si -> arg = arg; | |
605 LOCK(); | |
606 if (!GC_is_initialized) GC_init(); | |
607 if (NULL == attr) { | |
608 stack = 0; | |
609 (void) pthread_attr_init(&new_attr); | |
610 } else { | |
611 copy_attr(&new_attr, attr); | |
612 pthread_attr_getstackaddr(&new_attr, &stack); | |
613 } | |
614 pthread_attr_getstacksize(&new_attr, &stacksize); | |
615 pthread_attr_getdetachstate(&new_attr, &detachstate); | |
616 if (stacksize < GC_min_stack_sz) ABORT("Stack too small"); | |
617 if (0 == stack) { | |
618 stack = (void *)GC_stack_alloc(&stacksize); | |
619 if (0 == stack) { | |
620 UNLOCK(); | |
621 return(ENOMEM); | |
622 } | |
623 pthread_attr_setstackaddr(&new_attr, stack); | |
624 } else { | |
625 my_flags |= CLIENT_OWNS_STACK; | |
626 } | |
627 if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED; | |
628 si -> flags = my_flags; | |
629 si -> stack = stack; | |
630 si -> stack_size = stacksize; | |
631 result = pthread_create(new_thread, &new_attr, GC_start_routine, si); | |
632 if (0 == new_thread && !(my_flags & CLIENT_OWNS_STACK)) { | |
633 GC_stack_free(stack, stacksize); | |
634 } | |
635 UNLOCK(); | |
636 /* Wait until child has been added to the thread table. */ | |
637 /* This also ensures that we hold onto si until the child is done */ | |
638 /* with it. Thus it doesn't matter whether it is otherwise */ | |
639 /* visible to the collector. */ | |
640 while (0 != sem_wait(&(si -> registered))) { | |
641 if (errno != EINTR) { | |
642 GC_printf1("Sem_wait: errno = %ld\n", (unsigned long) errno); | |
643 ABORT("sem_wait failed"); | |
644 } | |
645 } | |
646 sem_destroy(&(si -> registered)); | |
647 pthread_attr_destroy(&new_attr); /* Probably unnecessary under Irix */ | |
648 return(result); | |
649 } | |
650 | |
651 VOLATILE GC_bool GC_collecting = 0; | |
652 /* A hint that we're in the collector and */ | |
653 /* holding the allocation lock for an */ | |
654 /* extended period. */ | |
655 | |
656 /* Reasonably fast spin locks. Basically the same implementation */ | |
657 /* as STL alloc.h. */ | |
658 | |
659 #define SLEEP_THRESHOLD 3 | |
660 | |
661 unsigned long GC_allocate_lock = 0; | |
662 # define GC_TRY_LOCK() !GC_test_and_set(&GC_allocate_lock) | |
663 # define GC_LOCK_TAKEN GC_allocate_lock | |
664 | |
665 void GC_lock() | |
666 { | |
667 # define low_spin_max 30 /* spin cycles if we suspect uniprocessor */ | |
668 # define high_spin_max 1000 /* spin cycles for multiprocessor */ | |
669 static unsigned spin_max = low_spin_max; | |
670 unsigned my_spin_max; | |
671 static unsigned last_spins = 0; | |
672 unsigned my_last_spins; | |
673 volatile unsigned junk; | |
674 # define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk | |
675 int i; | |
676 | |
677 if (GC_TRY_LOCK()) { | |
678 return; | |
679 } | |
680 junk = 0; | |
681 my_spin_max = spin_max; | |
682 my_last_spins = last_spins; | |
683 for (i = 0; i < my_spin_max; i++) { | |
684 if (GC_collecting) goto yield; | |
685 if (i < my_last_spins/2 || GC_LOCK_TAKEN) { | |
686 PAUSE; | |
687 continue; | |
688 } | |
689 if (GC_TRY_LOCK()) { | |
690 /* | |
691 * got it! | |
692 * Spinning worked. Thus we're probably not being scheduled | |
693 * against the other process with which we were contending. | |
694 * Thus it makes sense to spin longer the next time. | |
695 */ | |
696 last_spins = i; | |
697 spin_max = high_spin_max; | |
698 return; | |
699 } | |
700 } | |
701 /* We are probably being scheduled against the other process. Sleep. */ | |
702 spin_max = low_spin_max; | |
703 yield: | |
704 for (i = 0;; ++i) { | |
705 if (GC_TRY_LOCK()) { | |
706 return; | |
707 } | |
708 if (i < SLEEP_THRESHOLD) { | |
709 sched_yield(); | |
710 } else { | |
711 struct timespec ts; | |
712 | |
713 if (i > 26) i = 26; | |
714 /* Don't wait for more than about 60msecs, even */ | |
715 /* under extreme contention. */ | |
716 ts.tv_sec = 0; | |
717 ts.tv_nsec = 1 << i; | |
718 nanosleep(&ts, 0); | |
719 } | |
720 } | |
721 } | |
722 | |
723 # else | |
724 | |
725 #ifndef LINT | |
726 int GC_no_Irix_threads; | |
727 #endif | |
728 | |
729 # endif /* GC_IRIX_THREADS */ | |
730 |