📄 win32_threads.c
字号:
#include "private/gc_priv.h"#if defined(GC_WIN32_THREADS)#include <windows.h>#ifdef THREAD_LOCAL_ALLOC# include "private/thread_local_alloc.h"#endif /* THREAD_LOCAL_ALLOC *//* Allocation lock declarations. */#if !defined(USE_PTHREAD_LOCKS)# if defined(GC_DLL) __declspec(dllexport) CRITICAL_SECTION GC_allocate_ml;# else CRITICAL_SECTION GC_allocate_ml;# endif DWORD GC_lock_holder = NO_THREAD; /* Thread id for current holder of allocation lock */#else pthread_mutex_t GC_allocate_ml = PTHREAD_MUTEX_INITIALIZER; unsigned long GC_lock_holder = NO_THREAD;#endif#ifdef GC_PTHREADS# include <errno.h>/* GC_DLL should not normally be defined, especially since we often do turn *//* on THREAD_LOCAL_ALLOC, which is currently incompatible. *//* It might be possible to get GC_DLL and DllMain-based thread registration *//* to work with Cygwin, but if you try you are on your own. */#ifdef GC_DLL# error GC_DLL untested with Cygwin#endif /* Cygwin-specific forward decls */# undef pthread_create # undef pthread_sigmask # undef pthread_join # undef pthread_detach# undef dlopen # ifdef DEBUG_THREADS# ifdef CYGWIN32# define DEBUG_CYGWIN_THREADS 1# define DEBUG_WIN32_PTHREADS 0# else# define DEBUG_WIN32_PTHREADS 1# define DEBUG_CYGWIN_THREADS 0# endif# else# define DEBUG_CYGWIN_THREADS 0# define DEBUG_WIN32_PTHREADS 0# endif void * GC_pthread_start(void * arg); void GC_thread_exit_proc(void *arg);# include <pthread.h>#else# ifdef DEBUG_THREADS# define DEBUG_WIN32_THREADS 1# else# define DEBUG_WIN32_THREADS 0# endif# undef CreateThread# undef ExitThread# undef _beginthreadex# undef _endthreadex# undef _beginthread# ifdef DEBUG_THREADS# define DEBUG_WIN32_THREADS 1# else# define DEBUG_WIN32_THREADS 0# endif# include <process.h> /* For _beginthreadex, _endthreadex */#endif#if defined(GC_DLL) && !defined(MSWINCE) static GC_bool GC_win32_dll_threads = FALSE; /* This code operates in two distinct modes, depending on */ /* the setting of GC_win32_dll_threads. If */ /* GC_win32_dll_threads is set, all threads in the process */ /* are implicitly registered with the GC by DllMain. */ /* No explicit registration is required, and attempts at */ /* explicit registration are ignored. This mode is */ /* very different from the Posix operation of the collector. */ /* In this mode access to the thread table is lock-free. */ /* Hence there is a static limit on the number of threads. */ /* If GC_win32_dll_threads is FALSE, or the collector is */ /* built without GC_DLL defined, things operate in a way */ /* that is very similar to Posix platforms, and new threads */ /* must be registered with the collector, e.g. by using */ /* preprocessor-based interception of the thread primitives. */ /* In this case, we use a real data structure for the thread */ /* table. Note that there is no equivalent of linker-based */ /* call interception, since we don't have ELF-like */ /* facilities. The Windows analog appears to be "API */ /* hooking", which really seems to be a standard way to */ /* do minor binary rewriting (?). I'd prefer not to have */ /* the basic collector rely on such facilities, but an */ /* optional package that intercepts thread calls this way */ /* would probably be nice. */ /* GC_win32_dll_threads must be set at initialization time, */ /* i.e. before any collector or thread calls. We make it a */ /* "dynamic" option only to avoid multiple library versions. */#else# define GC_win32_dll_threads FALSE#endif/* We have two versions of the thread table. Which one *//* we us depends on whether or not GC_win32_dll_threads *//* is set. Note that before initialization, we don't *//* add any entries to either table, even if DllMain is *//* called. The main thread will be added on *//* initialization. *//* The type of the first argument to InterlockedExchange. *//* Documented to be LONG volatile *, but at least gcc likes *//* this better. */typedef LONG * IE_t;GC_bool GC_thr_initialized = FALSE;GC_bool GC_need_to_lock = FALSE;static GC_bool parallel_initialized = FALSE;void GC_init_parallel(void);#ifdef GC_DLL /* Turn on GC_win32_dll_threads */ GC_API void GC_use_DllMain(void) {# ifdef THREAD_LOCAL_ALLOC ABORT("Cannot use thread local allocation with DllMain-based " "thread registration."); /* Thread-local allocation really wants to lock at thread */ /* entry and exit. */# endif GC_ASSERT(!parallel_initialized); GC_win32_dll_threads = TRUE; }#else GC_API void GC_use_DllMain(void) { ABORT("GC not configured as DLL"); }#endifDWORD GC_main_thread = 0;struct GC_Thread_Rep { union { AO_t tm_in_use; /* Updated without lock. */ /* We assert that unused */ /* entries have invalid ids of */ /* zero and zero stack fields. */ /* Used only with GC_win32_dll_threads. */ struct GC_Thread_Rep * tm_next; /* Hash table link without */ /* GC_win32_dll_threads. */ /* More recently allocated threads */ /* with a given pthread id come */ /* first. (All but the first are */ /* guaranteed to be dead, but we may */ /* not yet have registered the join.) */ } table_management;# define in_use table_management.tm_in_use# define next table_management.tm_next DWORD id; HANDLE handle; ptr_t stack_base; /* The cold end of the stack. */ /* 0 ==> entry not valid. */ /* !in_use ==> stack_base == 0 */ GC_bool suspended;# ifdef GC_PTHREADS void *status; /* hold exit value until join in case it's a pointer */ pthread_t pthread_id; short flags; /* Protected by GC lock. */# define FINISHED 1 /* Thread has exited. */# define DETACHED 2 /* Thread is intended to be detached. */# define KNOWN_FINISHED(t) (((t) -> flags) & FINISHED)# else# define KNOWN_FINISHED(t) 0# endif# ifdef THREAD_LOCAL_ALLOC struct thread_local_freelists tlfs;# endif};typedef struct GC_Thread_Rep * GC_thread;typedef volatile struct GC_Thread_Rep * GC_vthread;/* * We assumed that volatile ==> memory ordering, at least among * volatiles. This code should consistently use atomic_ops. */volatile GC_bool GC_please_stop = FALSE;/* * We track thread attachments while the world is supposed to be stopped. * Unfortunately, we can't stop them from starting, since blocking in * DllMain seems to cause the world to deadlock. Thus we have to recover * If we notice this in the middle of marking. */AO_t GC_attached_thread = FALSE;/* Return TRUE if an thread was attached since we last asked or *//* since GC_attached_thread was explicitly reset. */GC_bool GC_started_thread_while_stopped(void){ AO_t result; if (GC_win32_dll_threads) { AO_nop_full(); /* Prior heap reads need to complete earlier. */ result = AO_load(&GC_attached_thread); if (result) { AO_store(&GC_attached_thread, FALSE); } return ((GC_bool)result); } else { return FALSE; }}/* Thread table used if GC_win32_dll_threads is set. *//* This is a fixed size array. *//* Since we use runtime conditionals, both versions *//* are always defined. */# ifndef MAX_THREADS# define MAX_THREADS 512# endif /* Things may get quite slow for large numbers of threads, */ /* since we look them up with sequential search. */ volatile struct GC_Thread_Rep dll_thread_table[MAX_THREADS]; volatile LONG GC_max_thread_index = 0; /* Largest index in dll_thread_table */ /* that was ever used. *//* And now the version used if GC_win32_dll_threads is not set. *//* This is a chained hash table, with much of the code borrowed *//* From the Posix implementation. */# define THREAD_TABLE_SZ 256 /* Must be power of 2 */ GC_thread GC_threads[THREAD_TABLE_SZ]; /* Add a thread to GC_threads. We assume it wasn't already there. *//* Caller holds allocation lock. *//* Unlike the pthreads version, the id field is set by the caller. */GC_thread GC_new_thread(DWORD id){ word hv = ((word)id) % THREAD_TABLE_SZ; GC_thread result; /* It may not be safe to allocate when we register the first thread. */ static struct GC_Thread_Rep first_thread; static GC_bool first_thread_used = FALSE; GC_ASSERT(I_HOLD_LOCK()); if (!first_thread_used) { result = &first_thread; first_thread_used = TRUE; } else { GC_ASSERT(!GC_win32_dll_threads); result = (struct GC_Thread_Rep *) GC_INTERNAL_MALLOC(sizeof(struct GC_Thread_Rep), NORMAL);# ifdef GC_PTHREADS /* result can be NULL -> segfault */ GC_ASSERT(result -> flags == 0);# endif } if (result == 0) return(0); /* result -> id = id; Done by caller. */ result -> next = GC_threads[hv]; GC_threads[hv] = result;# ifdef GC_PTHREADS GC_ASSERT(result -> flags == 0 /* && result -> thread_blocked == 0 */);# endif return(result);}extern LONG WINAPI GC_write_fault_handler(struct _EXCEPTION_POINTERS *exc_info);#if defined(GWW_VDB) && defined(MPROTECT_VDB) extern GC_bool GC_gww_dirty_init(void); /* Defined in os_dep.c. Returns TRUE if GetWriteWatch is available. */ /* may be called repeatedly. */#endifGC_bool GC_in_thread_creation = FALSE; /* Protected by allocation lock. *//* * This may be called from DllMain, and hence operates under unusual * constraints. In particular, it must be lock-free if GC_win32_dll_threads * is set. Always called from the thread being added. * If GC_win32_dll_threads is not set, we already hold the allocation lock, * except possibly during single-threaded start-up code. */static GC_thread GC_register_my_thread_inner(struct GC_stack_base *sb, DWORD thread_id){ GC_vthread me; /* The following should be a noop according to the win32 */ /* documentation. There is empirical evidence that it */ /* isn't. - HB */# if defined(MPROTECT_VDB)# if defined(GWW_VDB) if (GC_incremental && !GC_gww_dirty_init()) SetUnhandledExceptionFilter(GC_write_fault_handler);# else if (GC_incremental) SetUnhandledExceptionFilter(GC_write_fault_handler);# endif# endif if (GC_win32_dll_threads) { int i; /* It appears to be unsafe to acquire a lock here, since this */ /* code is apparently not preeemptible on some systems. */ /* (This is based on complaints, not on Microsoft's official */ /* documentation, which says this should perform "only simple */ /* initialization tasks".) */ /* Hence we make do with nonblocking synchronization. */ /* It has been claimed that DllMain is really only executed with */ /* a particular system lock held, and thus careful use of locking */ /* around code that doesn't call back into the system libraries */ /* might be OK. But this hasn't been tested across all win32 */ /* variants. */ /* cast away volatile qualifier */ for (i = 0; InterlockedExchange((IE_t)&dll_thread_table[i].in_use,1) != 0; i++) { /* Compare-and-swap would make this cleaner, but that's not */ /* supported before Windows 98 and NT 4.0. In Windows 2000, */ /* InterlockedExchange is supposed to be replaced by */ /* InterlockedExchangePointer, but that's not really what I */ /* want here. */ /* FIXME: We should eventually declare Win95 dead and use AO_ */ /* primitives here. */ if (i == MAX_THREADS - 1) ABORT("too many threads"); } /* Update GC_max_thread_index if necessary. The following is safe, */ /* and unlike CompareExchange-based solutions seems to work on all */ /* Windows95 and later platforms. */ /* Unfortunately, GC_max_thread_index may be temporarily out of */ /* bounds, so readers have to compensate. */ while (i > GC_max_thread_index) { InterlockedIncrement((IE_t)&GC_max_thread_index); } if (GC_max_thread_index >= MAX_THREADS) { /* We overshot due to simultaneous increments. */ /* Setting it to MAX_THREADS-1 is always safe. */ GC_max_thread_index = MAX_THREADS - 1; } me = dll_thread_table + i; } else /* Not using DllMain */ { GC_ASSERT(I_HOLD_LOCK()); GC_in_thread_creation = TRUE; /* OK to collect from unknown thread. */ me = GC_new_thread(thread_id); GC_in_thread_creation = FALSE; }# ifdef GC_PTHREADS /* me can be NULL -> segfault */ me -> pthread_id = pthread_self();# endif if (!DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), (HANDLE*)&(me -> handle), 0, 0, DUPLICATE_SAME_ACCESS)) { DWORD last_error = GetLastError(); GC_err_printf("Last error code: %d\n", last_error); ABORT("DuplicateHandle failed"); } me -> stack_base = sb -> mem_base; /* Up until this point, GC_push_all_stacks considers this thread */ /* invalid. */ /* Up until this point, this entry is viewed as reserved but invalid */ /* by GC_delete_thread. */
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -