Garbage collection is done in a separate thread to assure a predictable amount of stack space.
Before the main GC work is started all mutator threads must be suspended. The SIGUSR2 signal is sent to all threads. The signal handler determines whether a thread is running JIT code or native code. In the former case the handler returns to let the thread reach a safepoint. In the latter case it is put into safepoint immediately. When thread is suspended in safepoint it waits for another SIGUSR2 signal which is sent by the GC thread to wake threads up. We must not use mutexes from signal handlers because pthread_mutex_lock() is not reentrant. Using it from a signal handler may result in a deadlock. This approach uses semaphores to notify GC when a thread enters or leaves a safepoint. According to the manual sem_post() is the only async-signal safe pthreads method. Signed-off-by: Tomek Grabiec <tgrab...@gmail.com> --- arch/x86/include/arch/memory.h | 8 ++ include/vm/gc.h | 4 +- include/vm/thread.h | 7 +- regression/jvm/GcTortureTest.java | 2 +- vm/gc.c | 212 ++++++++++++++++++++++--------------- vm/signal.c | 9 +-- vm/thread.c | 50 ++------- 7 files changed, 154 insertions(+), 138 deletions(-) diff --git a/arch/x86/include/arch/memory.h b/arch/x86/include/arch/memory.h index e27c31b..89232d2 100644 --- a/arch/x86/include/arch/memory.h +++ b/arch/x86/include/arch/memory.h @@ -3,6 +3,14 @@ #include <stdint.h> +static inline void mbarrier(void) +{ + __asm__ volatile ( + "lock \n" + "addl $0,0(%%esp)\n" + ::: "memory"); +} + static inline void cpu_write_u32(unsigned char *p, uint32_t val) { *((uint32_t*)p) = val; diff --git a/include/vm/gc.h b/include/vm/gc.h index 06bebc6..7d23f5b 100644 --- a/include/vm/gc.h +++ b/include/vm/gc.h @@ -13,9 +13,7 @@ void gc_init(void); void *gc_alloc(size_t size); -void gc_attach_thread(void); -void gc_detach_thread(void); - void gc_safepoint(struct register_state *); +void suspend_handler(int, siginfo_t *, void *); #endif diff --git a/include/vm/thread.h b/include/vm/thread.h index e4ac546..97f672e 100644 --- a/include/vm/thread.h +++ b/include/vm/thread.h @@ -36,7 +36,7 @@ struct vm_exec_env { struct vm_thread *thread; }; -unsigned int vm_nr_threads_running(void); +unsigned int vm_nr_threads(void); extern __thread struct vm_exec_env current_exec_env; @@ -62,4 +62,9 @@ void vm_thread_interrupt(struct vm_thread *thread); void vm_lock_thread_count(void); void vm_unlock_thread_count(void); +extern struct list_head thread_list; +extern pthread_mutex_t threads_mutex; + +#define vm_thread_for_each(this) list_for_each_entry(this, &thread_list, list_node) + #endif diff --git a/regression/jvm/GcTortureTest.java b/regression/jvm/GcTortureTest.java index 33eef15..9f00232 100644 --- a/regression/jvm/GcTortureTest.java +++ b/regression/jvm/GcTortureTest.java @@ -7,7 +7,7 @@ public class GcTortureTest { for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(new Runnable() { public void run() { - for (int i = 0; i < 1000000; i++) + for (int i = 0; i < 100000; i++) new Object(); } }); diff --git a/vm/gc.c b/vm/gc.c index 0815d59..305470f 100644 --- a/vm/gc.c +++ b/vm/gc.c @@ -1,45 +1,37 @@ #include <assert.h> +#include <pthread.h> +#include <semaphore.h> #include <stdbool.h> #include <stdio.h> -#include <pthread.h> +#include "arch/memory.h" #include "arch/registers.h" +#include "arch/signal.h" #include "lib/guard-page.h" #include "vm/thread.h" #include "vm/stdlib.h" #include "vm/die.h" #include "vm/gc.h" +#include "vm/trace.h" void *gc_safepoint_page; -static pthread_mutex_t safepoint_mutex = PTHREAD_MUTEX_INITIALIZER; - -/* Protected by safepoint_mutex */ -static pthread_cond_t everyone_in_cond = PTHREAD_COND_INITIALIZER; -static pthread_cond_t everyone_out_cond = PTHREAD_COND_INITIALIZER; -static unsigned int nr_exiting_safepoint; -static unsigned int nr_in_safepoint; +static pthread_mutex_t gc_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t gc_cond = PTHREAD_COND_INITIALIZER; -static pthread_cond_t can_continue_cond = PTHREAD_COND_INITIALIZER; -static bool can_continue = true; +/* Protected by gc_mutex */ +static unsigned int nr_threads; +static bool gc_started; -bool verbose_gc; -bool gc_enabled; +static sem_t safepoint_sem; -void gc_init(void) -{ - gc_safepoint_page = alloc_guard_page(false); - if (!gc_safepoint_page) - die("Couldn't allocate GC safepoint guard page"); -} +static volatile sig_atomic_t can_continue; +static __thread sig_atomic_t in_safepoint; -void gc_attach_thread(void) -{ -} +static pthread_t gc_thread_id; -void gc_detach_thread(void) -{ -} +bool verbose_gc; +bool gc_enabled; static void hide_safepoint_guard_page(void) { @@ -59,98 +51,146 @@ static void do_gc_reclaim(void) /* TODO: Do main GC work here. */ } -/* Callers must hold safepoint_mutex */ -static void gc_safepoint_exit(void) +void gc_safepoint(struct register_state *regs) +{ + sigset_t mask; + int sig; + + in_safepoint = true; + sem_post(&safepoint_sem); + + sigemptyset(&mask); + sigaddset(&mask, SIGUSR2); + + while (!can_continue) + sigwait(&mask, &sig); + + in_safepoint = false; + sem_post(&safepoint_sem); +} + +void suspend_handler(int sig, siginfo_t *si, void *ctx) { - assert(nr_exiting_safepoint > 0); + /* + * Ignore this signal if we are already in safepoint. This + * happens when a thread executes a safepoint poll in JIT + * before the GC thread sends suspend signals. + */ + if (in_safepoint) + return; /* - * Don't let threads stop the world until everyone is out of their - * safepoint. + * Force native code to enter a safepoint and restart non-native + * threads. The latter will suspend themselves once they reach a GC + * point. */ - if (--nr_exiting_safepoint == 0) - pthread_cond_broadcast(&everyone_out_cond); + + if (signal_from_native(ctx)) { + struct register_state thread_register_state; + ucontext_t *uc = ctx; + + save_signal_registers(&thread_register_state, uc->uc_mcontext.gregs); + gc_safepoint(&thread_register_state); + } } -/* Callers must hold safepoint_mutex */ -static void do_gc_safepoint(void) +static void gc_suspend_rest(void) { - /* Only the GC thread will be waiting for this. */ - if (++nr_in_safepoint == vm_nr_threads_running()) - pthread_cond_signal(&everyone_in_cond); + struct vm_thread *thread; - /* Block until GC has finished */ - while (!can_continue) - pthread_cond_wait(&can_continue_cond, &safepoint_mutex); + sem_init(&safepoint_sem, true, 1 - nr_threads); + can_continue = false; + mbarrier(); + + vm_thread_for_each(thread) { + if (pthread_kill(thread->posix_id, SIGUSR2) != 0) + die("pthread_kill"); + } - assert(nr_in_safepoint > 0); - --nr_in_safepoint; + /* Wait for all threads to enter a safepoint. */ + sem_wait(&safepoint_sem); } -void gc_safepoint(struct register_state *regs) +static void gc_resume_rest(void) { - pthread_mutex_lock(&safepoint_mutex); + struct vm_thread *thread; - do_gc_safepoint(); + sem_init(&safepoint_sem, true, 1 - nr_threads); + can_continue = true; + mbarrier(); - gc_safepoint_exit(); + vm_thread_for_each(thread) { + if (pthread_kill(thread->posix_id, SIGUSR2) != 0) + die("pthread_kill"); + } - pthread_mutex_unlock(&safepoint_mutex); + /* Wait for all threads to leave a safepoint. */ + sem_wait(&safepoint_sem); } -/* - * This is the main entrypoint to the stop-the-world GC. - */ -static void gc_start(struct register_state *regs) +static void do_gc(void) { - pthread_mutex_lock(&safepoint_mutex); + vm_lock_thread_count(); + + nr_threads = vm_nr_threads(); /* Don't deadlock during early boostrap. */ - if (vm_nr_threads_running() == 0) - goto out_unlock; + if (nr_threads == 0) + goto out; - /* - * Wait until all threads have exited their safepoint before stopping - * the world again. - */ - while (nr_exiting_safepoint > 0) - pthread_cond_wait(&everyone_out_cond, &safepoint_mutex); + hide_safepoint_guard_page(); + gc_suspend_rest(); + unhide_safepoint_guard_page(); - /* - * If someone stopped the world before us, put the current thread in a - * safepoint. - */ - if (nr_in_safepoint != 0) { - do_gc_safepoint(); - goto out_exit_safepoint; + do_gc_reclaim(); + gc_resume_rest(); + +out: + vm_unlock_thread_count(); +} + +static void *gc_thread(void *arg) +{ + pthread_mutex_lock(&gc_mutex); + + for (;;) { + while (!gc_started) + pthread_cond_wait(&gc_cond, &gc_mutex); + + do_gc(); + + gc_started = false; + pthread_cond_broadcast(&gc_cond); } - /* Only one thread can stop the world at a time. */ - assert(can_continue); + pthread_mutex_unlock(&gc_mutex); + return NULL; +} - ++nr_in_safepoint; - can_continue = false; - hide_safepoint_guard_page(); +/* + * This wakes up the GC thread and suspends until garbage collection is done. + */ +static void gc_start(struct register_state *regs) +{ + pthread_mutex_lock(&gc_mutex); - /* Wait for all other threads to enter a safepoint. */ - while (nr_in_safepoint != vm_nr_threads_running()) - pthread_cond_wait(&everyone_in_cond, &safepoint_mutex); + gc_started = true; + pthread_cond_broadcast(&gc_cond); - /* At this point, we know that everyone is in the safepoint. */ - unhide_safepoint_guard_page(); + while (gc_started) + pthread_cond_wait(&gc_cond, &gc_mutex); - do_gc_reclaim(); + pthread_mutex_unlock(&gc_mutex); +} - /* Resume other threads */ - assert(nr_in_safepoint > 0); - nr_exiting_safepoint = nr_in_safepoint--; - can_continue = true; - pthread_cond_broadcast(&can_continue_cond); +void gc_init(void) +{ + gc_safepoint_page = alloc_guard_page(false); + if (!gc_safepoint_page) + die("Couldn't allocate GC safepoint guard page"); -out_exit_safepoint: - gc_safepoint_exit(); -out_unlock: - pthread_mutex_unlock(&safepoint_mutex); + if (pthread_create(&gc_thread_id, NULL, &gc_thread, NULL)) + die("Couldn't create GC thread"); } void *gc_alloc(size_t size) diff --git a/vm/signal.c b/vm/signal.c index 63fe0d9..e3dc112 100644 --- a/vm/signal.c +++ b/vm/signal.c @@ -166,11 +166,6 @@ static void sigsegv_handler(int sig, siginfo_t *si, void *ctx) print_backtrace_and_die(sig, si, ctx); } -static void signal_handler(int sig, siginfo_t *si, void *ctx) -{ - print_backtrace_and_die(sig, si, ctx); -} - void setup_signal_handlers(void) { struct sigaction sa; @@ -184,6 +179,6 @@ void setup_signal_handlers(void) sa.sa_sigaction = sigfpe_handler; sigaction(SIGFPE, &sa, NULL); - sa.sa_sigaction = signal_handler; - sigaction(SIGUSR1, &sa, NULL); + sa.sa_sigaction = suspend_handler; + sigaction(SIGUSR2, &sa, NULL); } diff --git a/vm/thread.c b/vm/thread.c index 76e4f2e..8cc1fd3 100644 --- a/vm/thread.c +++ b/vm/thread.c @@ -45,18 +45,15 @@ __thread struct vm_exec_env current_exec_env; static struct vm_object *main_thread_group; /* This mutex protects global operations on thread structures. */ -static pthread_mutex_t threads_mutex = PTHREAD_MUTEX_INITIALIZER; +pthread_mutex_t threads_mutex = PTHREAD_MUTEX_INITIALIZER; /* Condition variable used for waiting on any thread's death. */ static pthread_cond_t thread_terminate_cond = PTHREAD_COND_INITIALIZER; +static unsigned int nr_threads; static int nr_non_daemons; -static unsigned int nr_threads_running; - -static struct list_head thread_list; - -#define thread_for_each(this) list_for_each_entry(this, &thread_list, list_node) +struct list_head thread_list; static bool thread_count_locked; static pthread_cond_t thread_count_lock_cond = PTHREAD_COND_INITIALIZER; @@ -110,36 +107,10 @@ static bool vm_thread_is_daemon(struct vm_thread *thread) return field_get_int(jthread, vm_java_lang_Thread_daemon) != 0; } -unsigned int vm_nr_threads_running(void) -{ - unsigned int nr; - - pthread_mutex_lock(&threads_mutex); - nr = nr_threads_running; - pthread_mutex_unlock(&threads_mutex); - - return nr; -} - -static void update_thread_count(enum vm_thread_state state) +/* Must hold threads_mutex */ +unsigned int vm_nr_threads(void) { - pthread_mutex_lock(&threads_mutex); - - switch (state) { - case VM_THREAD_STATE_NEW: - break; - case VM_THREAD_STATE_RUNNABLE: - nr_threads_running++; - break; - case VM_THREAD_STATE_BLOCKED: - case VM_THREAD_STATE_TERMINATED: - case VM_THREAD_STATE_TIMED_WAITING: - case VM_THREAD_STATE_WAITING: - nr_threads_running--; - break; - } - - pthread_mutex_unlock(&threads_mutex); + return nr_threads; } void vm_thread_set_state(struct vm_thread *thread, enum vm_thread_state state) @@ -147,15 +118,12 @@ void vm_thread_set_state(struct vm_thread *thread, enum vm_thread_state state) pthread_mutex_lock(&thread->mutex); thread->state = state; pthread_mutex_unlock(&thread->mutex); - - update_thread_count(state); } static void vm_thread_attach_thread(struct vm_thread *thread) { list_add(&thread->list_node, &thread_list); - nr_threads_running++; - gc_attach_thread(); + nr_threads++; } /* The caller must hold threads_mutex */ @@ -163,12 +131,13 @@ static void vm_thread_detach_thread(struct vm_thread *thread) { vm_thread_set_state(thread, VM_THREAD_STATE_TERMINATED); - gc_detach_thread(); list_del(&thread->list_node); if (!vm_thread_is_daemon(thread)) nr_non_daemons--; + nr_threads--; + pthread_cond_broadcast(&thread_terminate_cond); } @@ -176,6 +145,7 @@ int init_threading(void) { INIT_LIST_HEAD(&thread_list); nr_non_daemons = 0; + nr_threads = 0; main_thread_group = vm_object_alloc(vm_java_lang_ThreadGroup); if (!main_thread_group) -- 1.6.0.4 ------------------------------------------------------------------------------ Return on Information: Google Enterprise Search pays you back Get the facts. http://p.sf.net/sfu/google-dev2dev _______________________________________________ Jatovm-devel mailing list Jatovm-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/jatovm-devel