diff options
Diffstat (limited to 'gc/linux_threads.c')
-rw-r--r-- | gc/linux_threads.c | 262 |
1 files changed, 215 insertions, 47 deletions
diff --git a/gc/linux_threads.c b/gc/linux_threads.c index b63dfb7..98a0e90 100644 --- a/gc/linux_threads.c +++ b/gc/linux_threads.c @@ -26,6 +26,7 @@ * and thread support for some of the other Posix platforms; any changes * made here may need to be reflected there too. */ + /* DG/UX ix86 support <takis@xfree86.org> */ /* * Linux_threads.c now also includes some code to support HPUX and * OSF1 (Compaq Tru64 Unix, really). The OSF1 support is not yet @@ -53,7 +54,7 @@ # include "gc.h" # if defined(GC_PTHREADS) && !defined(GC_SOLARIS_THREADS) \ - && !defined(GC_IRIX_THREADS) + && !defined(GC_IRIX_THREADS) && !defined(GC_WIN32_THREADS) # include "private/gc_priv.h" @@ -62,6 +63,18 @@ # define USE_HPUX_TLS # endif +# if defined(GC_DGUX386_THREADS) && !defined(USE_PTHREAD_SPECIFIC) +# define USE_PTHREAD_SPECIFIC +# endif + +# if defined(GC_DGUX386_THREADS) && !defined(_POSIX4A_DRAFT10_SOURCE) +# define _POSIX4A_DRAFT10_SOURCE 1 +# endif + +# if defined(GC_DGUX386_THREADS) && !defined(_USING_POSIX4A_DRAFT10) +# define _USING_POSIX4A_DRAFT10 1 +# endif + # ifdef THREAD_LOCAL_ALLOC # if !defined(USE_PTHREAD_SPECIFIC) && !defined(USE_HPUX_TLS) # include "private/specific.h" @@ -93,6 +106,13 @@ # include <sys/stat.h> # include <fcntl.h> +#if defined(GC_DGUX386_THREADS) +# include <sys/dg_sys_info.h> +# include <sys/_int_psem.h> + /* sem_t is an uint in DG/UX */ + typedef unsigned int sem_t; +#endif /* GC_DGUX386_THREADS */ + #ifndef __GNUC__ # define __inline__ #endif @@ -102,7 +122,11 @@ # define REAL_FUNC(f) __real_##f #else # define WRAP_FUNC(f) GC_##f -# define REAL_FUNC(f) f +# if !defined(GC_DGUX386_THREADS) +# define REAL_FUNC(f) f +# else /* GC_DGUX386_THREADS */ +# define REAL_FUNC(f) __d10_##f +# endif /* GC_DGUX386_THREADS */ # undef pthread_create # undef pthread_sigmask # undef pthread_join @@ -204,11 +228,7 @@ GC_thread GC_lookup_thread(pthread_t id); static GC_bool parallel_initialized = FALSE; -# if defined(__GNUC__) - void GC_init_parallel() __attribute__ ((constructor)); -# else - void GC_init_parallel(); -# endif +void GC_init_parallel(); # if defined(THREAD_LOCAL_ALLOC) && !defined(DBG_HDRS_ALL) @@ -235,15 +255,16 @@ static void return_freelists(ptr_t *fl, ptr_t *gfl) nwords = i * (GRANULARITY/sizeof(word)); qptr = fl + i; q = *qptr; - if ((word)q < HBLKSIZE) continue; - if (gfl[nwords] == 0) { + if ((word)q >= HBLKSIZE) { + if (gfl[nwords] == 0) { gfl[nwords] = q; - } else { + } else { /* Concatenate: */ for (; (word)q >= HBLKSIZE; qptr = &(obj_link(q)), q = *qptr); GC_ASSERT(0 == q); *qptr = gfl[nwords]; gfl[nwords] = fl[i]; + } } /* Clear fl[i], since the thread structure may hang around. */ /* Do it in a way that is likely to trap if we access it. */ @@ -295,8 +316,11 @@ void GC_init_thread_local(GC_thread p) /* We hold the allocator lock. */ void GC_destroy_thread_local(GC_thread p) { - /* We currently only do this from the thread itself. */ - GC_ASSERT(GC_getspecific(GC_thread_key) == (void *)p); + /* We currently only do this from the thread itself or from */ + /* the fork handler for a child process. */ +# ifndef HANDLE_FORK + GC_ASSERT(GC_getspecific(GC_thread_key) == (void *)p); +# endif return_freelists(p -> ptrfree_freelists, GC_aobjfreelist); return_freelists(p -> normal_freelists, GC_objfreelist); # ifdef GC_GCJ_SUPPORT @@ -317,8 +341,7 @@ GC_PTR GC_local_malloc(size_t bytes) GC_key_t k = GC_thread_key; void * tsd; -# if defined(REDIRECT_MALLOC) && !defined(USE_PTHREAD_SPECIFIC) \ - || !defined(__GNUC__) +# if defined(REDIRECT_MALLOC) && !defined(USE_PTHREAD_SPECIFIC) if (EXPECT(0 == k, 0)) { /* This can happen if we get called when the world is */ /* being initialized. Whether we can actually complete */ @@ -416,10 +439,13 @@ GC_PTR GC_local_gcj_malloc(size_t bytes, /* A memory barrier is probably never needed, since the */ /* action of stopping this thread will cause prior writes */ /* to complete. */ + GC_ASSERT(((void * volatile *)result)[1] == 0); *(void * volatile *)result = ptr_to_struct_containing_descr; return result; } else if ((word)my_entry - 1 < DIRECT_GRANULES) { - *my_fl = my_entry + index + 1; + if (!GC_incremental) *my_fl = my_entry + index + 1; + /* In the incremental case, we always have to take this */ + /* path. Thus we leave the counter alone. */ return GC_gcj_malloc(bytes, ptr_to_struct_containing_descr); } else { GC_generic_malloc_many(BYTES_FROM_INDEX(index), GC_gcj_kind, my_fl); @@ -535,7 +561,7 @@ static void start_mark_threads() if (0 != pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) ABORT("pthread_attr_setdetachstate failed"); -# ifdef HPUX +# if defined(HPUX) || defined(GC_DGUX386_THREADS) /* Default stack size is usually too small: fix it. */ /* Otherwise marker threads or GC may run out of */ /* space. */ @@ -548,10 +574,10 @@ static void start_mark_threads() ABORT("pthread_attr_getstacksize failed\n"); if (old_size < MIN_STACK_SIZE) { if (pthread_attr_setstacksize(&attr, MIN_STACK_SIZE) != 0) - ABORT("pthread_attr_getstacksize failed\n"); + ABORT("pthread_attr_setstacksize failed\n"); } } -# endif /* HPUX */ +# endif /* HPUX || GC_DGUX386_THREADS */ # ifdef CONDPRINT if (GC_print_stats) { GC_printf1("Starting %ld marker threads\n", GC_markers - 1); @@ -714,13 +740,14 @@ void GC_mark_thread_local_free_lists(void) } #endif /* THREAD_LOCAL_ALLOC */ +static struct GC_Thread_Rep first_thread; + /* Add a thread to GC_threads. We assume it wasn't already there. */ /* Caller holds allocation lock. */ GC_thread GC_new_thread(pthread_t id) { int hv = ((word)id) % THREAD_TABLE_SZ; GC_thread result; - static struct GC_Thread_Rep first_thread; static GC_bool first_thread_used = FALSE; if (!first_thread_used) { @@ -796,6 +823,38 @@ GC_thread GC_lookup_thread(pthread_t id) return(p); } +#ifdef HANDLE_FORK +/* Remove all entries from the GC_threads table, except the */ +/* one for the current thread. We need to do this in the child */ +/* process after a fork(), since only the current thread */ +/* survives in the child. */ +void GC_remove_all_threads_but_me(void) +{ + pthread_t self = pthread_self(); + int hv; + GC_thread p, next, me; + + for (hv = 0; hv < THREAD_TABLE_SZ; ++hv) { + me = 0; + for (p = GC_threads[hv]; 0 != p; p = next) { + next = p -> next; + if (p -> id == self) { + me = p; + p -> next = 0; + } else { +# ifdef THREAD_LOCAL_ALLOC + if (!(p -> flags & FINISHED)) { + GC_destroy_thread_local(p); + } +# endif /* THREAD_LOCAL_ALLOC */ + if (p != &first_thread) GC_INTERNAL_FREE(p); + } + } + GC_threads[hv] = me; + } +} +#endif /* HANDLE_FORK */ + /* There seems to be a very rare thread stopping problem. To help us */ /* debug that, we save the ids of the stopping thread. */ pthread_t GC_stopping_thread; @@ -1025,10 +1084,116 @@ int GC_get_nprocs() if (cpu_no >= result) result = cpu_no + 1; } } + close(f); return result; } #endif /* GC_LINUX_THREADS */ +/* We hold the GC lock. Wait until an in-progress GC has finished. */ +/* Repeatedly RELEASES GC LOCK in order to wait. */ +/* If wait_for_all is true, then we exit with the GC lock held and no */ +/* collection in progress; otherwise we just wait for the current GC */ +/* to finish. */ +void GC_wait_for_gc_completion(GC_bool wait_for_all) +{ + if (GC_incremental && GC_collection_in_progress()) { + int old_gc_no = GC_gc_no; + + /* Make sure that no part of our stack is still on the mark stack, */ + /* since it's about to be unmapped. */ + while (GC_incremental && GC_collection_in_progress() + && (wait_for_all || old_gc_no == GC_gc_no)) { + ENTER_GC(); + GC_collect_a_little_inner(1); + EXIT_GC(); + UNLOCK(); + sched_yield(); + LOCK(); + } + } +} + +#ifdef HANDLE_FORK +/* Procedures called before and after a fork. The goal here is to make */ +/* it safe to call GC_malloc() in a forked child. It's unclear that is */ +/* attainable, since the single UNIX spec seems to imply that one */ +/* should only call async-signal-safe functions, and we probably can't */ +/* quite guarantee that. But we give it our best shot. (That same */ +/* spec also implies that it's not safe to call the system malloc */ +/* between fork() and exec(). Thus we're doing no worse than it. */ + +/* Called before a fork() */ +void GC_fork_prepare_proc(void) +{ + /* Acquire all relevant locks, so that after releasing the locks */ + /* the child will see a consistent state in which monitor */ + /* invariants hold. Unfortunately, we can't acquire libc locks */ + /* we might need, and there seems to be no guarantee that libc */ + /* must install a suitable fork handler. */ + /* Wait for an ongoing GC to finish, since we can't finish it in */ + /* the (one remaining thread in) the child. */ + LOCK(); +# if defined(PARALLEL_MARK) || defined(THREAD_LOCAL_ALLOC) + GC_wait_for_reclaim(); +# endif + GC_wait_for_gc_completion(TRUE); +# if defined(PARALLEL_MARK) || defined(THREAD_LOCAL_ALLOC) + GC_acquire_mark_lock(); +# endif +} + +/* Called in parent after a fork() */ +void GC_fork_parent_proc(void) +{ +# if defined(PARALLEL_MARK) || defined(THREAD_LOCAL_ALLOC) + GC_release_mark_lock(); +# endif + UNLOCK(); +} + +/* Called in child after a fork() */ +void GC_fork_child_proc(void) +{ + /* Clean up the thread table, so that just our thread is left. */ +# if defined(PARALLEL_MARK) || defined(THREAD_LOCAL_ALLOC) + GC_release_mark_lock(); +# endif + GC_remove_all_threads_but_me(); +# ifdef PARALLEL_MARK + /* Turn off parallel marking in the child, since we are probably */ + /* just going to exec, and we would have to restart mark threads. */ + GC_markers = 1; + GC_parallel = FALSE; +# endif /* PARALLEL_MARK */ + UNLOCK(); +} +#endif /* HANDLE_FORK */ + +#if defined(GC_DGUX386_THREADS) +/* Return the number of processors, or i<= 0 if it can't be determined. */ +int GC_get_nprocs() +{ + /* <takis@XFree86.Org> */ + int numCpus; + struct dg_sys_info_pm_info pm_sysinfo; + int status =0; + + status = dg_sys_info((long int *) &pm_sysinfo, + DG_SYS_INFO_PM_INFO_TYPE, DG_SYS_INFO_PM_CURRENT_VERSION); + if (status < 0) + /* set -1 for error */ + numCpus = -1; + else + /* Active CPUs */ + numCpus = pm_sysinfo.idle_vp_count; + +# ifdef DEBUG_THREADS + GC_printf1("Number of active CPUs in this system: %d\n", numCpus); +# endif + return(numCpus); +} +#endif /* GC_DGUX386_THREADS */ + /* We hold the allocation lock. */ void GC_thr_init() { @@ -1065,14 +1230,11 @@ void GC_thr_init() if (sigaction(SIG_THR_RESTART, &act, NULL) != 0) { ABORT("Cannot set SIG_THR_RESTART handler"); } -# ifdef INSTALL_LOOPING_SEGV_HANDLER - act.sa_handler = GC_looping_handler; - if (sigaction(SIGSEGV, &act, NULL) != 0 - || sigaction(SIGBUS, &act, NULL) != 0) { - ABORT("Cannot set SIGSEGV or SIGBUS looping handler"); - } -# endif /* INSTALL_LOOPING_SEGV_HANDLER */ - +# ifdef HANDLE_FORK + /* Prepare for a possible fork. */ + pthread_atfork(GC_fork_prepare_proc, GC_fork_parent_proc, + GC_fork_child_proc); +# endif /* HANDLE_FORK */ /* Add the initial thread, so we can stop it. */ t = GC_new_thread(pthread_self()); t -> stack_ptr = (ptr_t)(&dummy); @@ -1091,7 +1253,7 @@ void GC_thr_init() # if defined(GC_OSF1_THREADS) || defined(GC_FREEBSD_THREADS) GC_nprocs = 1; # endif -# if defined(GC_LINUX_THREADS) +# if defined(GC_LINUX_THREADS) || defined(GC_DGUX386_THREADS) GC_nprocs = GC_get_nprocs(); # endif } @@ -1103,7 +1265,14 @@ void GC_thr_init() # endif } else { # ifdef PARALLEL_MARK - GC_markers = GC_nprocs; + { + char * markers_string = GETENV("GC_MARKERS"); + if (markers_string != NULL) { + GC_markers = atoi(markers_string); + } else { + GC_markers = GC_nprocs; + } + } # endif } # ifdef PARALLEL_MARK @@ -1122,6 +1291,8 @@ void GC_thr_init() # endif } else { GC_parallel = TRUE; + /* Disable true incremental collection, but generational is OK. */ + GC_time_limit = GC_TIME_UNLIMITED; } # endif } @@ -1129,7 +1300,7 @@ void GC_thr_init() /* Perform all initializations, including those that */ /* may require allocation. */ -/* Called as constructor without allocation lock. */ +/* Called without allocation lock. */ /* Must be called before a second thread is created. */ /* Called without allocation lock. */ void GC_init_parallel() @@ -1202,6 +1373,10 @@ GC_end_blocking(void) { UNLOCK(); } +#if defined(GC_DGUX386_THREADS) +#define __d10_sleep sleep +#endif /* GC_DGUX386_THREADS */ + /* A wrapper for the standard C sleep function */ int WRAP_FUNC(sleep) (unsigned int seconds) { @@ -1242,21 +1417,7 @@ void GC_thread_exit_proc(void *arg) && !defined(USE_HPUX_TLS) && !defined(DBG_HDRS_ALL) GC_remove_specific(GC_thread_key); # endif - if (GC_incremental && GC_collection_in_progress()) { - int old_gc_no = GC_gc_no; - - /* Make sure that no part of our stack is still on the mark stack, */ - /* since it's about to be unmapped. */ - while (GC_incremental && GC_collection_in_progress() - && old_gc_no == GC_gc_no) { - ENTER_GC(); - GC_collect_a_little_inner(1); - EXIT_GC(); - UNLOCK(); - sched_yield(); - LOCK(); - } - } + GC_wait_for_gc_completion(FALSE); UNLOCK(); } @@ -1396,8 +1557,13 @@ WRAP_FUNC(pthread_create)(pthread_t *new_thread, /* This is otherwise saved only in an area mmapped by the thread */ /* library, which isn't visible to the collector. */ + /* We resist the temptation to muck with the stack size here, */ + /* even if the default is unreasonably small. That's the client's */ + /* responsibility. */ + LOCK(); - si = (struct start_info *)GC_INTERNAL_MALLOC(sizeof(struct start_info), NORMAL); + si = (struct start_info *)GC_INTERNAL_MALLOC(sizeof(struct start_info), + NORMAL); UNLOCK(); if (!parallel_initialized) GC_init_parallel(); if (0 == si) return(ENOMEM); @@ -1408,7 +1574,7 @@ WRAP_FUNC(pthread_create)(pthread_t *new_thread, if (!GC_thr_initialized) GC_thr_init(); if (NULL == attr) { detachstate = PTHREAD_CREATE_JOINABLE; - } else { + } else { pthread_attr_getdetachstate(attr, &detachstate); } if (PTHREAD_CREATE_DETACHED == detachstate) my_flags |= DETACHED; @@ -1418,6 +1584,7 @@ WRAP_FUNC(pthread_create)(pthread_t *new_thread, GC_printf1("About to start new thread from thread 0x%X\n", pthread_self()); # endif + result = REAL_FUNC(pthread_create)(new_thread, attr, GC_start_routine, si); # ifdef DEBUG_THREADS GC_printf1("Started thread 0x%X\n", *new_thread); @@ -1433,6 +1600,7 @@ WRAP_FUNC(pthread_create)(pthread_t *new_thread, LOCK(); GC_INTERNAL_FREE(si); UNLOCK(); + return(result); } |