/* * %CopyrightBegin% * * Copyright Ericsson AB 2010-2011. All Rights Reserved. * * The contents of this file are subject to the Erlang Public License, * Version 1.1, (the "License"); you may not use this file except in * compliance with the License. You should have received a copy of the * Erlang Public License along with this software. If not, it can be * retrieved online at http://www.erlang.org/. * * Software distributed under the License is distributed on an "AS IS" * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See * the License for the specific language governing rights and limitations * under the License. * * %CopyrightEnd% */ /* * Description: Pthread implementation of the ethread library * Author: Rickard Green */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #define ETHR_CHILD_WAIT_SPIN_COUNT 4000 #include <stdio.h> #ifdef ETHR_TIME_WITH_SYS_TIME # include <time.h> # include <sys/time.h> #else # ifdef ETHR_HAVE_SYS_TIME_H # include <sys/time.h> # else # include <time.h> # endif #endif #include <sys/types.h> #include <unistd.h> #include <signal.h> #include <string.h> #include <limits.h> #define ETHR_INLINE_FUNC_NAME_(X) X ## __ #define ETHREAD_IMPL__ #include "ethread.h" #undef ETHR_INCLUDE_MONOTONIC_CLOCK__ #define ETHR_INCLUDE_MONOTONIC_CLOCK__ #include "ethr_internal.h" #ifndef ETHR_HAVE_ETHREAD_DEFINES #error Missing configure defines #endif pthread_key_t ethr_ts_event_key__; static int child_wait_spin_count; /* * -------------------------------------------------------------------------- * Static functions * -------------------------------------------------------------------------- */ static void thr_exit_cleanup(void) { ethr_run_exit_handlers__(); } /* Argument passed to thr_wrapper() */ typedef struct { ethr_atomic32_t result; ethr_ts_event *tse; void *(*thr_func)(void *); void *arg; void *prep_func_res; char *name; char name_buff[16]; } ethr_thr_wrap_data__; static void *thr_wrapper(void *vtwd) { ethr_sint32_t result; void *res; ethr_thr_wrap_data__ *twd = (ethr_thr_wrap_data__ *) vtwd; void *(*thr_func)(void *) = twd->thr_func; void *arg = twd->arg; ethr_ts_event *tsep = NULL; result = (ethr_sint32_t) ethr_make_ts_event__(&tsep); if (result == 0) { tsep->iflgs |= ETHR_TS_EV_ETHREAD; if (ethr_thr_child_func__) ethr_thr_child_func__(twd->prep_func_res); } tsep = twd->tse; /* We aren't allowed to follow twd after result has been set! */ if (twd->name) ethr_setname(twd->name); ethr_atomic32_set(&twd->result, result); ethr_event_set(&tsep->event); res = result == 0 ? (*thr_func)(arg) : NULL; thr_exit_cleanup(); return res; } /* internal exports */ int ethr_set_tse__(ethr_ts_event *tsep) { return pthread_setspecific(ethr_ts_event_key__, (void *) tsep); } ethr_ts_event *ethr_get_tse__(void) { return pthread_getspecific(ethr_ts_event_key__); } #if defined(ETHR_PPC_RUNTIME_CONF__) #include <sys/wait.h> static void handle_lwsync_sigill(int signum) { _exit(1); } static int ppc_init__(void) { int pid; /* If anything what so ever failes we assume no lwsync for safety */ ethr_runtime__.conf.have_lwsync = 0; /* * We perform the lwsync test (which might cause an illegal * instruction signal) in a separate process in order to be * completely certain that we do not mess up our own state. */ pid = fork(); if (pid == 0) { struct sigaction act, oact; sigemptyset(&act.sa_mask); act.sa_flags = SA_RESETHAND; act.sa_handler = handle_lwsync_sigill; if (sigaction(SIGILL, &act, &oact) != 0) _exit(2); __asm__ __volatile__ ("lwsync\n\t" : : : "memory"); _exit(0); } if (pid != -1) { while (1) { int status, res; res = waitpid(pid, &status, 0); if (res == pid) { if (WIFEXITED(status) && WEXITSTATUS(status) == 0) ethr_runtime__.conf.have_lwsync = 1; break; } } } return 0; } #endif #if defined(ETHR_X86_RUNTIME_CONF__) void ethr_x86_cpuid__(int *eax, int *ebx, int *ecx, int *edx) { #if ETHR_SIZEOF_PTR == 4 int have_cpuid; /* * If it is possible to toggle eflags bit 21, * we have the cpuid instruction. */ __asm__ ("pushf\n\t" "popl %%eax\n\t" "movl %%eax, %%ecx\n\t" "xorl $0x200000, %%eax\n\t" "pushl %%eax\n\t" "popf\n\t" "pushf\n\t" "popl %%eax\n\t" "movl $0x0, %0\n\t" "xorl %%ecx, %%eax\n\t" "jz no_cpuid\n\t" "movl $0x1, %0\n\t" "no_cpuid:\n\t" : "=r"(have_cpuid) : : "%eax", "%ecx", "cc"); if (!have_cpuid) { *eax = *ebx = *ecx = *edx = 0; return; } #endif #if ETHR_SIZEOF_PTR == 4 && defined(__PIC__) && __PIC__ /* * When position independet code is used in 32-bit mode, the B register * is used for storage of global offset table address, and we may not * use it as input or output in an asm. We need to save and restore the * B register explicitly (for some reason gcc doesn't provide this * service to us). */ __asm__ ("pushl %%ebx\n\t" "cpuid\n\t" "movl %%ebx, %1\n\t" "popl %%ebx\n\t" : "=a"(*eax), "=r"(*ebx), "=c"(*ecx), "=d"(*edx) : "0"(*eax) : "cc"); #else __asm__ ("cpuid\n\t" : "=a"(*eax), "=b"(*ebx), "=c"(*ecx), "=d"(*edx) : "0"(*eax) : "cc"); #endif } #endif /* ETHR_X86_RUNTIME_CONF__ */ #ifdef ETHR_HAVE_ETHR_GET_MONOTONIC_TIME static void init_get_monotonic_time(void); #endif /* * -------------------------------------------------------------------------- * Exported functions * -------------------------------------------------------------------------- */ int ethr_init(ethr_init_data *id) { int res; if (!ethr_not_inited__) return EINVAL; ethr_not_inited__ = 0; #if defined(ETHR_PPC_RUNTIME_CONF__) res = ppc_init__(); if (res != 0) goto error; #endif #ifdef ETHR_HAVE_ETHR_GET_MONOTONIC_TIME init_get_monotonic_time(); #endif res = ethr_init_common__(id); if (res != 0) goto error; child_wait_spin_count = ETHR_CHILD_WAIT_SPIN_COUNT; if (erts_get_cpu_configured(ethr_cpu_info__) == 1) child_wait_spin_count = 0; res = pthread_key_create(ðr_ts_event_key__, ethr_ts_event_destructor__); if (res != 0) goto error; return 0; error: ethr_not_inited__ = 1; return res; } int ethr_late_init(ethr_late_init_data *id) { int res = ethr_late_init_common__(id); if (res != 0) return res; ethr_not_completely_inited__ = 0; return res; } int ethr_thr_create(ethr_tid *tid, void * (*func)(void *), void *arg, ethr_thr_opts *opts) { ethr_thr_wrap_data__ twd; pthread_attr_t attr; int res, dres; int use_stack_size = (opts && opts->suggested_stack_size >= 0 ? opts->suggested_stack_size : -1 /* Use system default */); #ifdef ETHR_MODIFIED_DEFAULT_STACK_SIZE if (use_stack_size < 0) use_stack_size = ETHR_MODIFIED_DEFAULT_STACK_SIZE; #endif #if ETHR_XCHK if (ethr_not_completely_inited__) { ETHR_ASSERT(0); return EACCES; } if (!tid || !func) { ETHR_ASSERT(0); return EINVAL; } #endif ethr_atomic32_init(&twd.result, (ethr_sint32_t) -1); twd.tse = ethr_get_ts_event(); twd.thr_func = func; twd.arg = arg; if (opts && opts->name) { snprintf(twd.name_buff, 16, "%s", opts->name); twd.name = twd.name_buff; } else twd.name = NULL; res = pthread_attr_init(&attr); if (res != 0) return res; /* Error cleanup needed after this point */ /* Schedule child thread in system scope (if possible) ... */ res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); if (res != 0 && res != ENOTSUP) goto error; if (use_stack_size >= 0) { size_t suggested_stack_size = (size_t) use_stack_size; size_t stack_size; #ifdef ETHR_DEBUG suggested_stack_size /= 2; /* Make sure we got margin */ #endif #ifdef ETHR_STACK_GUARD_SIZE /* The guard is at least on some platforms included in the stack size passed when creating threads */ suggested_stack_size += ETHR_B2KW(ETHR_STACK_GUARD_SIZE); #endif if (suggested_stack_size < ethr_min_stack_size__) stack_size = ETHR_KW2B(ethr_min_stack_size__); else if (suggested_stack_size > ethr_max_stack_size__) stack_size = ETHR_KW2B(ethr_max_stack_size__); else stack_size = ETHR_PAGE_ALIGN(ETHR_KW2B(suggested_stack_size)); (void) pthread_attr_setstacksize(&attr, stack_size); } #ifdef ETHR_STACK_GUARD_SIZE (void) pthread_attr_setguardsize(&attr, ETHR_STACK_GUARD_SIZE); #endif /* Detached or joinable... */ res = pthread_attr_setdetachstate(&attr, (opts && opts->detached ? PTHREAD_CREATE_DETACHED : PTHREAD_CREATE_JOINABLE)); if (res != 0) goto error; /* Call prepare func if it exist */ if (ethr_thr_prepare_func__) twd.prep_func_res = ethr_thr_prepare_func__(); else twd.prep_func_res = NULL; res = pthread_create((pthread_t *) tid, &attr, thr_wrapper, (void*) &twd); if (res == 0) { int spin_count = child_wait_spin_count; /* Wait for child to initialize... */ while (1) { ethr_sint32_t result; ethr_event_reset(&twd.tse->event); result = ethr_atomic32_read(&twd.result); if (result == 0) break; if (result > 0) { res = (int) result; goto error; } res = ethr_event_swait(&twd.tse->event, spin_count); if (res != 0 && res != EINTR) goto error; spin_count = 0; } } /* Cleanup... */ error: dres = pthread_attr_destroy(&attr); if (res == 0) res = dres; if (ethr_thr_parent_func__) ethr_thr_parent_func__(twd.prep_func_res); return res; } int ethr_thr_join(ethr_tid tid, void **res) { #if ETHR_XCHK if (ethr_not_inited__) { ETHR_ASSERT(0); return EACCES; } #endif return pthread_join((pthread_t) tid, res); } int ethr_thr_detach(ethr_tid tid) { #if ETHR_XCHK if (ethr_not_inited__) { ETHR_ASSERT(0); return EACCES; } #endif return pthread_detach((pthread_t) tid); } void ethr_thr_exit(void *res) { #if ETHR_XCHK if (ethr_not_inited__) { ETHR_ASSERT(0); return; } #endif thr_exit_cleanup(); pthread_exit(res); } ethr_tid ethr_self(void) { return (ethr_tid) pthread_self(); } int ethr_getname(ethr_tid tid, char *buf, size_t len) { #if defined(ETHR_HAVE_PTHREAD_GETNAME_NP_3) return pthread_getname_np((pthread_t) tid, buf, len); #elif defined(ETHR_HAVE_PTHREAD_GETNAME_NP_2) return pthread_getname_np((pthread_t) tid, buf); #else return ENOSYS; #endif } void ethr_setname(char *name) { #if defined(ETHR_HAVE_PTHREAD_SETNAME_NP_2) pthread_setname_np(ethr_self(), name); #elif defined(ETHR_HAVE_PTHREAD_SET_NAME_NP_2) pthread_set_name_np(ethr_self(), name); #elif defined(ETHR_HAVE_PTHREAD_SETNAME_NP_1) pthread_setname_np(name); #endif } int ethr_equal_tids(ethr_tid tid1, ethr_tid tid2) { return pthread_equal((pthread_t) tid1, (pthread_t) tid2); } /* * Thread specific events */ ethr_ts_event * ethr_get_ts_event(void) { return ethr_get_ts_event__(); } void ethr_leave_ts_event(ethr_ts_event *tsep) { ethr_leave_ts_event__(tsep); } /* * Thread specific data */ int ethr_tsd_key_create(ethr_tsd_key *keyp, char *keyname) { #if ETHR_XCHK if (ethr_not_inited__) { ETHR_ASSERT(0); return EACCES; } if (!keyp) { ETHR_ASSERT(0); return EINVAL; } #endif return pthread_key_create((pthread_key_t *) keyp, NULL); } int ethr_tsd_key_delete(ethr_tsd_key key) { #if ETHR_XCHK if (ethr_not_inited__) { ETHR_ASSERT(0); return EACCES; } #endif return pthread_key_delete((pthread_key_t) key); } int ethr_tsd_set(ethr_tsd_key key, void *value) { #if ETHR_XCHK if (ethr_not_inited__) { ETHR_ASSERT(0); return EACCES; } #endif return pthread_setspecific((pthread_key_t) key, value); } void * ethr_tsd_get(ethr_tsd_key key) { #if ETHR_XCHK if (ethr_not_inited__) { ETHR_ASSERT(0); return NULL; } #endif return pthread_getspecific((pthread_key_t) key); } /* * Signal functions */ #if ETHR_HAVE_ETHR_SIG_FUNCS int ethr_sigmask(int how, const sigset_t *set, sigset_t *oset) { #if ETHR_XCHK if (ethr_not_inited__) { ETHR_ASSERT(0); return EACCES; } if (!set && !oset) { ETHR_ASSERT(0); return EINVAL; } #endif #if defined(__ANDROID__) return sigprocmask(how, set, oset); #else return pthread_sigmask(how, set, oset); #endif } int ethr_sigwait(const sigset_t *set, int *sig) { #if ETHR_XCHK if (ethr_not_inited__) { ETHR_ASSERT(0); return EACCES; } if (!set || !sig) { ETHR_ASSERT(0); return EINVAL; } #endif if (sigwait(set, sig) < 0) return errno; return 0; } int ethr_kill(const ethr_tid tid, const int sig) { #if ETHR_XCHK if (ethr_not_inited__) { ETHR_ASSERT(0); return EACCES; } #endif return pthread_kill((const pthread_t)tid, sig); } #endif /* #if ETHR_HAVE_ETHR_SIG_FUNCS */ #ifdef ETHR_HAVE_ETHR_GET_MONOTONIC_TIME static int broken_get_monotonic_time; #if defined(ETHR_HAVE_CLOCK_GETTIME_MONOTONIC) # ifndef ETHR_MONOTONIC_CLOCK_ID # error ETHR_MONOTONIC_CLOCK_ID should have been defined # endif ethr_sint64_t ethr_get_monotonic_time(void) { ethr_sint64_t time; struct timespec ts; if (broken_get_monotonic_time) return (ethr_sint64_t) 0; if (0 != clock_gettime(ETHR_MONOTONIC_CLOCK_ID, &ts)) ETHR_FATAL_ERROR__(errno); time = (ethr_sint64_t) ts.tv_sec; time *= (ethr_sint64_t) 1000*1000*1000; time += (ethr_sint64_t) ts.tv_nsec; return time; } #elif defined(ETHR_HAVE_MACH_CLOCK_GET_TIME) # ifndef ETHR_MONOTONIC_CLOCK_ID # error ETHR_MONOTONIC_CLOCK_ID should have been defined # endif ethr_sint64_t ethr_get_monotonic_time(void) { ethr_sint64_t time; kern_return_t res; clock_serv_t clk_srv; mach_timespec_t time_spec; if (broken_get_monotonic_time) return (ethr_sint64_t) 0; errno = EFAULT; host_get_clock_service(mach_host_self(), ETHR_MONOTONIC_CLOCK_ID, &clk_srv); res = clock_get_time(clk_srv, &time_spec); if (res != KERN_SUCCESS) ETHR_FATAL_ERROR__(errno); mach_port_deallocate(mach_task_self(), clk_srv); time = (ethr_sint64_t) time_spec.tv_sec; time *= (ethr_sint64_t) 1000*1000*1000; time += (ethr_sint64_t) time_spec.tv_nsec; return time; } #elif defined(ETHR_HAVE_GETHRTIME) ethr_sint64_t ethr_get_monotonic_time(void) { if (broken_get_monotonic_time) return (ethr_sint64_t) 0; return (ethr_sint64_t) gethrtime(); } #else #error missing monotonic clock #endif int ethr_get_monotonic_time_is_broken(void) { return broken_get_monotonic_time; } #include <string.h> #include <ctype.h> #include <sys/utsname.h> static void init_get_monotonic_time(void) { struct utsname uts; int vsn[3]; int i; char *c; broken_get_monotonic_time = 0; (void) uname(&uts); for (c = uts.sysname; *c; c++) { if (isupper((int) *c)) *c = tolower((int) *c); } c = uts.release; for (i = 0; i < sizeof(vsn)/sizeof(int); i++) { if (!isdigit((int) *c)) vsn[i] = 0; else { char *c2 = c; do { c2++; } while (isdigit((int) *c2)); *c2 = '\0'; vsn[i] = atoi(c); c = c2; c++; } } if (strcmp("linux", uts.sysname) == 0) { if (vsn[0] < 2 || (vsn[0] == 2 && vsn[1] < 6) || (vsn[0] == 2 && vsn[1] == 6 && vsn[2] < 33)) { broken_get_monotonic_time = 1; } } else if (strcmp("sunos", uts.sysname) == 0) { if ((vsn[0] < 5 || (vsn[0] == 5 && vsn[1] < 8)) #if defined(HAVE_SYSCONF) && defined(_SC_NPROCESSORS_CONF) && sysconf(_SC_NPROCESSORS_CONF) > 1 #endif ) { broken_get_monotonic_time = 1; } } } #endif /* ETHR_HAVE_ETHR_GET_MONOTONIC_TIME */ ETHR_IMPL_NORETURN__ ethr_abort__(void) { abort(); }