Annotation of OpenXM_contrib2/asir2000/gc/linux_threads.c, Revision 1.1
1.1 ! noro 1: /*
! 2: * Copyright (c) 1994 by Xerox Corporation. All rights reserved.
! 3: * Copyright (c) 1996 by Silicon Graphics. All rights reserved.
! 4: * Copyright (c) 1998 by Fergus Henderson. 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 LinuxThreads, the clone()-based kernel
! 17: * thread package for Linux which is included in libc6.
! 18: *
! 19: * This code relies on implementation details of LinuxThreads,
! 20: * (i.e. properties not guaranteed by the Pthread standard):
! 21: *
! 22: * - the function GC_linux_thread_top_of_stack(void)
! 23: * relies on the way LinuxThreads lays out thread stacks
! 24: * in the address space.
! 25: *
! 26: * Note that there is a lot of code duplication between linux_threads.c
! 27: * and irix_threads.c; any changes made here may need to be reflected
! 28: * there too.
! 29: */
! 30:
! 31: /* #define DEBUG_THREADS 1 */
! 32:
! 33: /* ANSI C requires that a compilation unit contains something */
! 34: # include "gc_priv.h"
! 35:
! 36: # if defined(LINUX_THREADS)
! 37:
! 38: # include <pthread.h>
! 39: # include <time.h>
! 40: # include <errno.h>
! 41: # include <unistd.h>
! 42: # include <sys/mman.h>
! 43: # include <sys/time.h>
! 44: # include <semaphore.h>
! 45:
! 46: #undef pthread_create
! 47: #undef pthread_sigmask
! 48: #undef pthread_join
! 49:
! 50: void GC_thr_init();
! 51:
! 52: #if 0
! 53: void GC_print_sig_mask()
! 54: {
! 55: sigset_t blocked;
! 56: int i;
! 57:
! 58: if (pthread_sigmask(SIG_BLOCK, NULL, &blocked) != 0)
! 59: ABORT("pthread_sigmask");
! 60: GC_printf0("Blocked: ");
! 61: for (i = 1; i <= MAXSIG; i++) {
! 62: if (sigismember(&blocked, i)) { GC_printf1("%ld ",(long) i); }
! 63: }
! 64: GC_printf0("\n");
! 65: }
! 66: #endif
! 67:
! 68: /* We use the allocation lock to protect thread-related data structures. */
! 69:
! 70: /* The set of all known threads. We intercept thread creation and */
! 71: /* joins. We never actually create detached threads. We allocate all */
! 72: /* new thread stacks ourselves. These allow us to maintain this */
! 73: /* data structure. */
! 74: /* Protected by GC_thr_lock. */
! 75: /* Some of this should be declared volatile, but that's incosnsistent */
! 76: /* with some library routine declarations. */
! 77: typedef struct GC_Thread_Rep {
! 78: struct GC_Thread_Rep * next; /* More recently allocated threads */
! 79: /* with a given pthread id come */
! 80: /* first. (All but the first are */
! 81: /* guaranteed to be dead, but we may */
! 82: /* not yet have registered the join.) */
! 83: pthread_t id;
! 84: word flags;
! 85: # define FINISHED 1 /* Thread has exited. */
! 86: # define DETACHED 2 /* Thread is intended to be detached. */
! 87: # define MAIN_THREAD 4 /* True for the original thread only. */
! 88:
! 89: ptr_t stack_end;
! 90: ptr_t stack_ptr; /* Valid only when stopped. */
! 91: int signal;
! 92: void * status; /* The value returned from the thread. */
! 93: /* Used only to avoid premature */
! 94: /* reclamation of any data it might */
! 95: /* reference. */
! 96: } * GC_thread;
! 97:
! 98: GC_thread GC_lookup_thread(pthread_t id);
! 99:
! 100: /*
! 101: * The only way to suspend threads given the pthread interface is to send
! 102: * signals. We can't use SIGSTOP directly, because we need to get the
! 103: * thread to save its stack pointer in the GC thread table before
! 104: * suspending. So we have to reserve a signal of our own for this.
! 105: * This means we have to intercept client calls to change the signal mask.
! 106: * The linuxthreads package already uses SIGUSR1 and SIGUSR2,
! 107: * so we need to reuse something else. I chose SIGPWR.
! 108: * (Perhaps SIGUNUSED would be a better choice.)
! 109: */
! 110: #define SIG_SUSPEND SIGPWR
! 111:
! 112: #define SIG_RESTART SIGXCPU
! 113:
! 114: sem_t GC_suspend_ack_sem;
! 115:
! 116: /*
! 117: GC_linux_thread_top_of_stack() relies on implementation details of
! 118: LinuxThreads, namely that thread stacks are allocated on 2M boundaries
! 119: and grow to no more than 2M.
! 120: To make sure that we're using LinuxThreads and not some other thread
! 121: package, we generate a dummy reference to `__pthread_kill_other_threads_np'
! 122: (was `__pthread_initial_thread_bos' but that disappeared),
! 123: which is a symbol defined in LinuxThreads, but (hopefully) not in other
! 124: thread packages.
! 125: */
! 126: extern char * __pthread_initial_thread_bos;
! 127: char **dummy_var_to_force_linux_threads = &__pthread_kill_other_threads_np;
! 128:
! 129: #define LINUX_THREADS_STACK_SIZE (2 * 1024 * 1024)
! 130:
! 131: static inline ptr_t GC_linux_thread_top_of_stack(void)
! 132: {
! 133: char *sp = GC_approx_sp();
! 134: ptr_t tos = (ptr_t) (((unsigned long)sp | (LINUX_THREADS_STACK_SIZE - 1)) + 1);
! 135: #if DEBUG_THREADS
! 136: GC_printf1("SP = %lx\n", (unsigned long)sp);
! 137: GC_printf1("TOS = %lx\n", (unsigned long)tos);
! 138: #endif
! 139: return tos;
! 140: }
! 141:
! 142: void GC_suspend_handler(int sig)
! 143: {
! 144: int dummy;
! 145: pthread_t my_thread = pthread_self();
! 146: GC_thread me;
! 147: sigset_t all_sigs;
! 148: sigset_t old_sigs;
! 149: int i;
! 150: sigset_t mask;
! 151:
! 152: if (sig != SIG_SUSPEND) ABORT("Bad signal in suspend_handler");
! 153:
! 154: #if DEBUG_THREADS
! 155: GC_printf1("Suspending 0x%x\n", my_thread);
! 156: #endif
! 157:
! 158: me = GC_lookup_thread(my_thread);
! 159: /* The lookup here is safe, since I'm doing this on behalf */
! 160: /* of a thread which holds the allocation lock in order */
! 161: /* to stop the world. Thus concurrent modification of the */
! 162: /* data structure is impossible. */
! 163: me -> stack_ptr = (ptr_t)(&dummy);
! 164: me -> stack_end = GC_linux_thread_top_of_stack();
! 165:
! 166: /* Tell the thread that wants to stop the world that this */
! 167: /* thread has been stopped. Note that sem_post() is */
! 168: /* the only async-signal-safe primitive in LinuxThreads. */
! 169: sem_post(&GC_suspend_ack_sem);
! 170:
! 171: /* Wait until that thread tells us to restart by sending */
! 172: /* this thread a SIG_RESTART signal. */
! 173: /* SIG_RESTART should be masked at this point. Thus there */
! 174: /* is no race. */
! 175: if (sigfillset(&mask) != 0) ABORT("sigfillset() failed");
! 176: if (sigdelset(&mask, SIG_RESTART) != 0) ABORT("sigdelset() failed");
! 177: do {
! 178: me->signal = 0;
! 179: sigsuspend(&mask); /* Wait for signal */
! 180: } while (me->signal != SIG_RESTART);
! 181:
! 182: #if DEBUG_THREADS
! 183: GC_printf1("Continuing 0x%x\n", my_thread);
! 184: #endif
! 185: }
! 186:
! 187: void GC_restart_handler(int sig)
! 188: {
! 189: GC_thread me;
! 190:
! 191: if (sig != SIG_RESTART) ABORT("Bad signal in suspend_handler");
! 192:
! 193: /* Let the GC_suspend_handler() know that we got a SIG_RESTART. */
! 194: /* The lookup here is safe, since I'm doing this on behalf */
! 195: /* of a thread which holds the allocation lock in order */
! 196: /* to stop the world. Thus concurrent modification of the */
! 197: /* data structure is impossible. */
! 198: me = GC_lookup_thread(pthread_self());
! 199: me->signal = SIG_RESTART;
! 200:
! 201: /*
! 202: ** Note: even if we didn't do anything useful here,
! 203: ** it would still be necessary to have a signal handler,
! 204: ** rather than ignoring the signals, otherwise
! 205: ** the signals will not be delivered at all, and
! 206: ** will thus not interrupt the sigsuspend() above.
! 207: */
! 208:
! 209: #if DEBUG_THREADS
! 210: GC_printf1("In GC_restart_handler for 0x%x\n", pthread_self());
! 211: #endif
! 212: }
! 213:
! 214: GC_bool GC_thr_initialized = FALSE;
! 215:
! 216: # define THREAD_TABLE_SZ 128 /* Must be power of 2 */
! 217: volatile GC_thread GC_threads[THREAD_TABLE_SZ];
! 218:
! 219: /* Add a thread to GC_threads. We assume it wasn't already there. */
! 220: /* Caller holds allocation lock. */
! 221: GC_thread GC_new_thread(pthread_t id)
! 222: {
! 223: int hv = ((word)id) % THREAD_TABLE_SZ;
! 224: GC_thread result;
! 225: static struct GC_Thread_Rep first_thread;
! 226: static GC_bool first_thread_used = FALSE;
! 227:
! 228: if (!first_thread_used) {
! 229: result = &first_thread;
! 230: first_thread_used = TRUE;
! 231: /* Dont acquire allocation lock, since we may already hold it. */
! 232: } else {
! 233: result = (struct GC_Thread_Rep *)
! 234: GC_generic_malloc_inner(sizeof(struct GC_Thread_Rep), NORMAL);
! 235: }
! 236: if (result == 0) return(0);
! 237: result -> id = id;
! 238: result -> next = GC_threads[hv];
! 239: GC_threads[hv] = result;
! 240: /* result -> flags = 0; */
! 241: return(result);
! 242: }
! 243:
! 244: /* Delete a thread from GC_threads. We assume it is there. */
! 245: /* (The code intentionally traps if it wasn't.) */
! 246: /* Caller holds allocation lock. */
! 247: void GC_delete_thread(pthread_t id)
! 248: {
! 249: int hv = ((word)id) % THREAD_TABLE_SZ;
! 250: register GC_thread p = GC_threads[hv];
! 251: register GC_thread prev = 0;
! 252:
! 253: while (!pthread_equal(p -> id, id)) {
! 254: prev = p;
! 255: p = p -> next;
! 256: }
! 257: if (prev == 0) {
! 258: GC_threads[hv] = p -> next;
! 259: } else {
! 260: prev -> next = p -> next;
! 261: }
! 262: }
! 263:
! 264: /* If a thread has been joined, but we have not yet */
! 265: /* been notified, then there may be more than one thread */
! 266: /* in the table with the same pthread id. */
! 267: /* This is OK, but we need a way to delete a specific one. */
! 268: void GC_delete_gc_thread(pthread_t id, GC_thread gc_id)
! 269: {
! 270: int hv = ((word)id) % THREAD_TABLE_SZ;
! 271: register GC_thread p = GC_threads[hv];
! 272: register GC_thread prev = 0;
! 273:
! 274: while (p != gc_id) {
! 275: prev = p;
! 276: p = p -> next;
! 277: }
! 278: if (prev == 0) {
! 279: GC_threads[hv] = p -> next;
! 280: } else {
! 281: prev -> next = p -> next;
! 282: }
! 283: }
! 284:
! 285: /* Return a GC_thread corresponding to a given thread_t. */
! 286: /* Returns 0 if it's not there. */
! 287: /* Caller holds allocation lock or otherwise inhibits */
! 288: /* updates. */
! 289: /* If there is more than one thread with the given id we */
! 290: /* return the most recent one. */
! 291: GC_thread GC_lookup_thread(pthread_t id)
! 292: {
! 293: int hv = ((word)id) % THREAD_TABLE_SZ;
! 294: register GC_thread p = GC_threads[hv];
! 295:
! 296: while (p != 0 && !pthread_equal(p -> id, id)) p = p -> next;
! 297: return(p);
! 298: }
! 299:
! 300: /* Caller holds allocation lock. */
! 301: void GC_stop_world()
! 302: {
! 303: pthread_t my_thread = pthread_self();
! 304: register int i;
! 305: register GC_thread p;
! 306: register int n_live_threads = 0;
! 307: register int result;
! 308:
! 309: for (i = 0; i < THREAD_TABLE_SZ; i++) {
! 310: for (p = GC_threads[i]; p != 0; p = p -> next) {
! 311: if (p -> id != my_thread) {
! 312: if (p -> flags & FINISHED) continue;
! 313: n_live_threads++;
! 314: #if DEBUG_THREADS
! 315: GC_printf1("Sending suspend signal to 0x%x\n", p -> id);
! 316: #endif
! 317: result = pthread_kill(p -> id, SIG_SUSPEND);
! 318: switch(result) {
! 319: case ESRCH:
! 320: /* Not really there anymore. Possible? */
! 321: n_live_threads--;
! 322: break;
! 323: case 0:
! 324: break;
! 325: default:
! 326: ABORT("pthread_kill failed");
! 327: }
! 328: }
! 329: }
! 330: }
! 331: for (i = 0; i < n_live_threads; i++) {
! 332: sem_wait(&GC_suspend_ack_sem);
! 333: }
! 334: #if DEBUG_THREADS
! 335: GC_printf1("World stopped 0x%x\n", pthread_self());
! 336: #endif
! 337: }
! 338:
! 339: /* Caller holds allocation lock. */
! 340: void GC_start_world()
! 341: {
! 342: pthread_t my_thread = pthread_self();
! 343: register int i;
! 344: register GC_thread p;
! 345: register int n_live_threads = 0;
! 346: register int result;
! 347:
! 348: # if DEBUG_THREADS
! 349: GC_printf0("World starting\n");
! 350: # endif
! 351:
! 352: for (i = 0; i < THREAD_TABLE_SZ; i++) {
! 353: for (p = GC_threads[i]; p != 0; p = p -> next) {
! 354: if (p -> id != my_thread) {
! 355: if (p -> flags & FINISHED) continue;
! 356: n_live_threads++;
! 357: #if DEBUG_THREADS
! 358: GC_printf1("Sending restart signal to 0x%x\n", p -> id);
! 359: #endif
! 360: result = pthread_kill(p -> id, SIG_RESTART);
! 361: switch(result) {
! 362: case ESRCH:
! 363: /* Not really there anymore. Possible? */
! 364: n_live_threads--;
! 365: break;
! 366: case 0:
! 367: break;
! 368: default:
! 369: ABORT("pthread_kill failed");
! 370: }
! 371: }
! 372: }
! 373: }
! 374: #if DEBUG_THREADS
! 375: GC_printf0("World started\n");
! 376: #endif
! 377: }
! 378:
! 379: /* We hold allocation lock. We assume the world is stopped. */
! 380: void GC_push_all_stacks()
! 381: {
! 382: register int i;
! 383: register GC_thread p;
! 384: register ptr_t sp = GC_approx_sp();
! 385: register ptr_t lo, hi;
! 386: pthread_t me = pthread_self();
! 387:
! 388: if (!GC_thr_initialized) GC_thr_init();
! 389: #if DEBUG_THREADS
! 390: GC_printf1("Pushing stacks from thread 0x%lx\n", (unsigned long) me);
! 391: #endif
! 392: for (i = 0; i < THREAD_TABLE_SZ; i++) {
! 393: for (p = GC_threads[i]; p != 0; p = p -> next) {
! 394: if (p -> flags & FINISHED) continue;
! 395: if (pthread_equal(p -> id, me)) {
! 396: lo = GC_approx_sp();
! 397: } else {
! 398: lo = p -> stack_ptr;
! 399: }
! 400: if ((p -> flags & MAIN_THREAD) == 0) {
! 401: if (pthread_equal(p -> id, me)) {
! 402: hi = GC_linux_thread_top_of_stack();
! 403: } else {
! 404: hi = p -> stack_end;
! 405: }
! 406: } else {
! 407: /* The original stack. */
! 408: hi = GC_stackbottom;
! 409: }
! 410: #if DEBUG_THREADS
! 411: GC_printf3("Stack for thread 0x%lx = [%lx,%lx)\n",
! 412: (unsigned long) p -> id,
! 413: (unsigned long) lo, (unsigned long) hi);
! 414: #endif
! 415: GC_push_all_stack(lo, hi);
! 416: }
! 417: }
! 418: }
! 419:
! 420:
! 421: /* We hold the allocation lock. */
! 422: void GC_thr_init()
! 423: {
! 424: GC_thread t;
! 425: struct sigaction act;
! 426:
! 427: if (GC_thr_initialized) return;
! 428: GC_thr_initialized = TRUE;
! 429:
! 430: if (sem_init(&GC_suspend_ack_sem, 0, 0) != 0)
! 431: ABORT("sem_init failed");
! 432:
! 433: act.sa_flags = SA_RESTART;
! 434: if (sigfillset(&act.sa_mask) != 0) {
! 435: ABORT("sigfillset() failed");
! 436: }
! 437: /* SIG_RESTART is unmasked by the handler when necessary. */
! 438: act.sa_handler = GC_suspend_handler;
! 439: if (sigaction(SIG_SUSPEND, &act, NULL) != 0) {
! 440: ABORT("Cannot set SIG_SUSPEND handler");
! 441: }
! 442:
! 443: act.sa_handler = GC_restart_handler;
! 444: if (sigaction(SIG_RESTART, &act, NULL) != 0) {
! 445: ABORT("Cannot set SIG_SUSPEND handler");
! 446: }
! 447:
! 448: /* Add the initial thread, so we can stop it. */
! 449: t = GC_new_thread(pthread_self());
! 450: t -> stack_ptr = 0;
! 451: t -> flags = DETACHED | MAIN_THREAD;
! 452: }
! 453:
! 454: int GC_pthread_sigmask(int how, const sigset_t *set, sigset_t *oset)
! 455: {
! 456: sigset_t fudged_set;
! 457:
! 458: if (set != NULL && (how == SIG_BLOCK || how == SIG_SETMASK)) {
! 459: fudged_set = *set;
! 460: sigdelset(&fudged_set, SIG_SUSPEND);
! 461: set = &fudged_set;
! 462: }
! 463: return(pthread_sigmask(how, set, oset));
! 464: }
! 465:
! 466: struct start_info {
! 467: void *(*start_routine)(void *);
! 468: void *arg;
! 469: word flags;
! 470: sem_t registered; /* 1 ==> in our thread table, but */
! 471: /* parent hasn't yet noticed. */
! 472: };
! 473:
! 474:
! 475: void GC_thread_exit_proc(void *arg)
! 476: {
! 477: GC_thread me;
! 478: struct start_info * si = arg;
! 479:
! 480: LOCK();
! 481: me = GC_lookup_thread(pthread_self());
! 482: if (me -> flags & DETACHED) {
! 483: GC_delete_thread(pthread_self());
! 484: } else {
! 485: me -> flags |= FINISHED;
! 486: }
! 487: UNLOCK();
! 488: }
! 489:
! 490: int GC_pthread_join(pthread_t thread, void **retval)
! 491: {
! 492: int result;
! 493: GC_thread thread_gc_id;
! 494:
! 495: LOCK();
! 496: thread_gc_id = GC_lookup_thread(thread);
! 497: /* This is guaranteed to be the intended one, since the thread id */
! 498: /* cant have been recycled by pthreads. */
! 499: UNLOCK();
! 500: result = pthread_join(thread, retval);
! 501: LOCK();
! 502: /* Here the pthread thread id may have been recycled. */
! 503: GC_delete_gc_thread(thread, thread_gc_id);
! 504: UNLOCK();
! 505: return result;
! 506: }
! 507:
! 508: void * GC_start_routine(void * arg)
! 509: {
! 510: struct start_info * si = arg;
! 511: void * result;
! 512: GC_thread me;
! 513: pthread_t my_pthread;
! 514: void *(*start)(void *);
! 515: void *start_arg;
! 516:
! 517: my_pthread = pthread_self();
! 518: LOCK();
! 519: me = GC_new_thread(my_pthread);
! 520: me -> flags = si -> flags;
! 521: me -> stack_ptr = 0;
! 522: me -> stack_end = 0;
! 523: UNLOCK();
! 524: start = si -> start_routine;
! 525: start_arg = si -> arg;
! 526: sem_post(&(si -> registered));
! 527: pthread_cleanup_push(GC_thread_exit_proc, si);
! 528: # ifdef DEBUG_THREADS
! 529: GC_printf1("Starting thread 0x%lx\n", pthread_self());
! 530: GC_printf1("pid = %ld\n", (long) getpid());
! 531: GC_printf1("sp = 0x%lx\n", (long) &arg);
! 532: GC_printf1("start_routine = 0x%lx\n", start);
! 533: # endif
! 534: result = (*start)(start_arg);
! 535: #if DEBUG_THREADS
! 536: GC_printf1("Finishing thread 0x%x\n", pthread_self());
! 537: #endif
! 538: me -> status = result;
! 539: me -> flags |= FINISHED;
! 540: pthread_cleanup_pop(1);
! 541: /* Cleanup acquires lock, ensuring that we can't exit */
! 542: /* while a collection that thinks we're alive is trying to stop */
! 543: /* us. */
! 544: return(result);
! 545: }
! 546:
! 547: int
! 548: GC_pthread_create(pthread_t *new_thread,
! 549: const pthread_attr_t *attr,
! 550: void *(*start_routine)(void *), void *arg)
! 551: {
! 552: int result;
! 553: GC_thread t;
! 554: pthread_t my_new_thread;
! 555: void * stack;
! 556: size_t stacksize;
! 557: pthread_attr_t new_attr;
! 558: int detachstate;
! 559: word my_flags = 0;
! 560: struct start_info * si = GC_malloc(sizeof(struct start_info));
! 561: /* This is otherwise saved only in an area mmapped by the thread */
! 562: /* library, which isn't visible to the collector. */
! 563:
! 564: if (0 == si) return(ENOMEM);
! 565: sem_init(&(si -> registered), 0, 0);
! 566: si -> start_routine = start_routine;
! 567: si -> arg = arg;
! 568: LOCK();
! 569: if (!GC_thr_initialized) GC_thr_init();
! 570: if (NULL == attr) {
! 571: stack = 0;
! 572: (void) pthread_attr_init(&new_attr);
! 573: } else {
! 574: new_attr = *attr;
! 575: }
! 576: pthread_attr_getdetachstate(&new_attr, &detachstate);
! 577: if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED;
! 578: si -> flags = my_flags;
! 579: UNLOCK();
! 580: result = pthread_create(new_thread, &new_attr, GC_start_routine, si);
! 581: /* Wait until child has been added to the thread table. */
! 582: /* This also ensures that we hold onto si until the child is done */
! 583: /* with it. Thus it doesn't matter whether it is otherwise */
! 584: /* visible to the collector. */
! 585: if (0 != sem_wait(&(si -> registered))) ABORT("sem_wait failed");
! 586: sem_destroy(&(si -> registered));
! 587: /* pthread_attr_destroy(&new_attr); */
! 588: /* pthread_attr_destroy(&new_attr); */
! 589: return(result);
! 590: }
! 591:
! 592: GC_bool GC_collecting = 0;
! 593: /* A hint that we're in the collector and */
! 594: /* holding the allocation lock for an */
! 595: /* extended period. */
! 596:
! 597: /* Reasonably fast spin locks. Basically the same implementation */
! 598: /* as STL alloc.h. This isn't really the right way to do this. */
! 599: /* but until the POSIX scheduling mess gets straightened out ... */
! 600:
! 601: volatile unsigned int GC_allocate_lock = 0;
! 602:
! 603:
! 604: void GC_lock()
! 605: {
! 606: # define low_spin_max 30 /* spin cycles if we suspect uniprocessor */
! 607: # define high_spin_max 1000 /* spin cycles for multiprocessor */
! 608: static unsigned spin_max = low_spin_max;
! 609: unsigned my_spin_max;
! 610: static unsigned last_spins = 0;
! 611: unsigned my_last_spins;
! 612: volatile unsigned junk;
! 613: # define PAUSE junk *= junk; junk *= junk; junk *= junk; junk *= junk
! 614: int i;
! 615:
! 616: if (!GC_test_and_set(&GC_allocate_lock)) {
! 617: return;
! 618: }
! 619: junk = 0;
! 620: my_spin_max = spin_max;
! 621: my_last_spins = last_spins;
! 622: for (i = 0; i < my_spin_max; i++) {
! 623: if (GC_collecting) goto yield;
! 624: if (i < my_last_spins/2 || GC_allocate_lock) {
! 625: PAUSE;
! 626: continue;
! 627: }
! 628: if (!GC_test_and_set(&GC_allocate_lock)) {
! 629: /*
! 630: * got it!
! 631: * Spinning worked. Thus we're probably not being scheduled
! 632: * against the other process with which we were contending.
! 633: * Thus it makes sense to spin longer the next time.
! 634: */
! 635: last_spins = i;
! 636: spin_max = high_spin_max;
! 637: return;
! 638: }
! 639: }
! 640: /* We are probably being scheduled against the other process. Sleep. */
! 641: spin_max = low_spin_max;
! 642: yield:
! 643: for (i = 0;; ++i) {
! 644: if (!GC_test_and_set(&GC_allocate_lock)) {
! 645: return;
! 646: }
! 647: # define SLEEP_THRESHOLD 12
! 648: /* nanosleep(<= 2ms) just spins under Linux. We */
! 649: /* want to be careful to avoid that behavior. */
! 650: if (i < SLEEP_THRESHOLD) {
! 651: sched_yield();
! 652: } else {
! 653: struct timespec ts;
! 654:
! 655: if (i > 26) i = 26;
! 656: /* Don't wait for more than about 60msecs, even */
! 657: /* under extreme contention. */
! 658: ts.tv_sec = 0;
! 659: ts.tv_nsec = 1 << i;
! 660: nanosleep(&ts, 0);
! 661: }
! 662: }
! 663: }
! 664:
! 665: # endif /* LINUX_THREADS */
! 666:
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>