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