mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-07-13 22:49:11 +00:00
Make improvements
- Every unit test now passes on Apple Silicon. The final piece of this puzzle was porting our POSIX threads cancelation support, since that works differently on ARM64 XNU vs. AMD64. Our semaphore support on Apple Silicon is also superior now compared to AMD64, thanks to the grand central dispatch library which lets *NSYNC locks go faster. - The Cosmopolitan runtime is now more stable, particularly on Windows. To do this, thread local storage is mandatory at all runtime levels, and the innermost packages of the C library is no longer being built using ASAN. TLS is being bootstrapped with a 128-byte TIB during the process startup phase, and then later on the runtime re-allocates it either statically or dynamically to support code using _Thread_local. fork() and execve() now do a better job cooperating with threads. We can now check how much stack memory is left in the process or thread when functions like kprintf() / execve() etc. call alloca(), so that ENOMEM can be raised, reduce a buffer size, or just print a warning. - POSIX signal emulation is now implemented the same way kernels do it with pthread_kill() and raise(). Any thread can interrupt any other thread, regardless of what it's doing. If it's blocked on read/write then the killer thread will cancel its i/o operation so that EINTR can be returned in the mark thread immediately. If it's doing a tight CPU bound operation, then that's also interrupted by the signal delivery. Signal delivery works now by suspending a thread and pushing context data structures onto its stack, and redirecting its execution to a trampoline function, which calls SetThreadContext(GetCurrentThread()) when it's done. - We're now doing a better job managing locks and handles. On NetBSD we now close semaphore file descriptors in forked children. Semaphores on Windows can now be canceled immediately, which means mutexes/condition variables will now go faster. Apple Silicon semaphores can be canceled too. We're now using Apple's pthread_yield() funciton. Apple _nocancel syscalls are now used on XNU when appropriate to ensure pthread_cancel requests aren't lost. The MbedTLS library has been updated to support POSIX thread cancelations. See tool/build/runitd.c for an example of how it can be used for production multi-threaded tls servers. Handles on Windows now leak less often across processes. All i/o operations on Windows are now overlapped, which means file pointers can no longer be inherited across dup() and fork() for the time being. - We now spawn a thread on Windows to deliver SIGCHLD and wakeup wait4() which means, for example, that posix_spawn() now goes 3x faster. POSIX spawn is also now more correct. Like Musl, it's now able to report the failure code of execve() via a pipe although our approach favors using shared memory to do that on systems that have a true vfork() function. - We now spawn a thread to deliver SIGALRM to threads when setitimer() is used. This enables the most precise wakeups the OS makes possible. - The Cosmopolitan runtime now uses less memory. On NetBSD for example, it turned out the kernel would actually commit the PT_GNU_STACK size which caused RSS to be 6mb for every process. Now it's down to ~4kb. On Apple Silicon, we reduce the mandatory upstream thread size to the smallest possible size to reduce the memory overhead of Cosmo threads. The examples directory has a program called greenbean which can spawn a web server on Linux with 10,000 worker threads and have the memory usage of the process be ~77mb. The 1024 byte overhead of POSIX-style thread-local storage is now optional; it won't be allocated until the pthread_setspecific/getspecific functions are called. On Windows, the threads that get spawned which are internal to the libc implementation use reserve rather than commit memory, which shaves a few hundred kb. - sigaltstack() is now supported on Windows, however it's currently not able to be used to handle stack overflows, since crash signals are still generated by WIN32. However the crash handler will still switch to the alt stack, which is helpful in environments with tiny threads. - Test binaries are now smaller. Many of the mandatory dependencies of the test runner have been removed. This ensures many programs can do a better job only linking the the thing they're testing. This caused the test binaries for LIBC_FMT for example, to decrease from 200kb to 50kb - long double is no longer used in the implementation details of libc, except in the APIs that define it. The old code that used long double for time (instead of struct timespec) has now been thoroughly removed. - ShowCrashReports() is now much tinier in MODE=tiny. Instead of doing backtraces itself, it'll just print a command you can run on the shell using our new `cosmoaddr2line` program to view the backtrace. - Crash report signal handling now works in a much better way. Instead of terminating the process, it now relies on SA_RESETHAND so that the default SIG_IGN behavior can terminate the process if necessary. - Our pledge() functionality has now been fully ported to AARCH64 Linux.
This commit is contained in:
parent
c4eb838516
commit
ec480f5aa0
638 changed files with 7925 additions and 8282 deletions
41
libc/thread/alarm.c
Normal file
41
libc/thread/alarm.c
Normal file
|
@ -0,0 +1,41 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/itimerval.h"
|
||||
#include "libc/calls/struct/timeval.h"
|
||||
#include "libc/sysv/consts/itimer.h"
|
||||
|
||||
/**
|
||||
* Asks for single-shot SIGALRM to be raise()'d after interval.
|
||||
*
|
||||
* @param seconds is how long to wait before raising SIGALRM (which will
|
||||
* only happen once) or zero to clear any previously scheduled alarm
|
||||
* @return seconds that were remaining on the previously scheduled
|
||||
* alarm, or zero if there wasn't one (failure isn't possible)
|
||||
* @see setitimer() for a more powerful api
|
||||
* @asyncsignalsafe
|
||||
*/
|
||||
unsigned alarm(unsigned seconds) {
|
||||
struct itimerval it, old;
|
||||
it.it_value = timeval_fromseconds(seconds);
|
||||
it.it_interval = timeval_zero;
|
||||
npassert(!setitimer(ITIMER_REAL, &it, &old));
|
||||
return timeval_toseconds(old.it_value);
|
||||
}
|
48
libc/thread/getitimer.c
Normal file
48
libc/thread/getitimer.c
Normal file
|
@ -0,0 +1,48 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/itimerval.h"
|
||||
#include "libc/calls/struct/itimerval.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/intrin/asan.internal.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
|
||||
/**
|
||||
* Retrieves last setitimer() value, correcting for remaining time.
|
||||
*
|
||||
* @param which can be ITIMER_REAL, ITIMER_VIRTUAL, etc.
|
||||
* @return 0 on success or -1 w/ errno
|
||||
*/
|
||||
int getitimer(int which, struct itimerval *curvalue) {
|
||||
int rc;
|
||||
if (IsAsan() && !__asan_is_valid(curvalue, sizeof(*curvalue))) {
|
||||
rc = efault();
|
||||
} else if (!IsWindows()) {
|
||||
rc = sys_getitimer(which, curvalue);
|
||||
} else if (!curvalue) {
|
||||
rc = efault();
|
||||
} else {
|
||||
rc = sys_setitimer_nt(which, 0, curvalue);
|
||||
}
|
||||
STRACE("getitimer(%s, [%s]) → %d% m", DescribeItimer(which),
|
||||
DescribeItimerval(rc, curvalue), rc);
|
||||
return rc;
|
||||
}
|
118
libc/thread/itimer.c
Normal file
118
libc/thread/itimer.c
Normal file
|
@ -0,0 +1,118 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/sysv/consts/itimer.h"
|
||||
#include "libc/calls/sig.internal.h"
|
||||
#include "libc/calls/struct/itimerval.h"
|
||||
#include "libc/calls/struct/itimerval.internal.h"
|
||||
#include "libc/calls/struct/timeval.h"
|
||||
#include "libc/cosmo.h"
|
||||
#include "libc/nt/enum/processcreationflags.h"
|
||||
#include "libc/nt/thread.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/sicode.h"
|
||||
#include "libc/sysv/consts/sig.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/thread/itimer.internal.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
|
||||
#ifdef __x86_64__
|
||||
|
||||
struct IntervalTimer __itimer;
|
||||
|
||||
static textwindows dontinstrument uint32_t __itimer_worker(void *arg) {
|
||||
struct CosmoTib tls;
|
||||
__bootstrap_tls(&tls, __builtin_frame_address(0));
|
||||
for (;;) {
|
||||
bool dosignal = false;
|
||||
struct timeval now, waituntil;
|
||||
nsync_mu_lock(&__itimer.lock);
|
||||
now = timeval_real();
|
||||
if (timeval_iszero(__itimer.it.it_value)) {
|
||||
waituntil = timeval_max;
|
||||
} else {
|
||||
if (timeval_cmp(now, __itimer.it.it_value) < 0) {
|
||||
waituntil = __itimer.it.it_value;
|
||||
} else {
|
||||
if (timeval_iszero(__itimer.it.it_interval)) {
|
||||
__itimer.it.it_value = timeval_zero;
|
||||
waituntil = timeval_max;
|
||||
} else {
|
||||
do {
|
||||
__itimer.it.it_value =
|
||||
timeval_add(__itimer.it.it_value, __itimer.it.it_interval);
|
||||
} while (timeval_cmp(now, __itimer.it.it_value) > 0);
|
||||
waituntil = __itimer.it.it_value;
|
||||
}
|
||||
dosignal = true;
|
||||
}
|
||||
}
|
||||
nsync_mu_unlock(&__itimer.lock);
|
||||
if (dosignal) {
|
||||
__sig_generate(SIGALRM, SI_TIMER);
|
||||
}
|
||||
nsync_mu_lock(&__itimer.lock);
|
||||
nsync_cv_wait_with_deadline(&__itimer.cond, &__itimer.lock,
|
||||
timeval_totimespec(waituntil), 0);
|
||||
nsync_mu_unlock(&__itimer.lock);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static textwindows void __itimer_setup(void) {
|
||||
__itimer.thread = CreateThread(0, 65536, __itimer_worker, 0,
|
||||
kNtStackSizeParamIsAReservation, 0);
|
||||
}
|
||||
|
||||
textwindows void __itimer_reset(void) {
|
||||
// this function is called by fork(), because
|
||||
// timers aren't inherited by forked subprocesses
|
||||
bzero(&__itimer, sizeof(__itimer));
|
||||
}
|
||||
|
||||
textwindows int sys_setitimer_nt(int which, const struct itimerval *neu,
|
||||
struct itimerval *old) {
|
||||
struct itimerval config;
|
||||
cosmo_once(&__itimer.once, __itimer_setup);
|
||||
if (which != ITIMER_REAL || (neu && (!timeval_isvalid(neu->it_value) ||
|
||||
!timeval_isvalid(neu->it_interval)))) {
|
||||
return einval();
|
||||
}
|
||||
if (neu) {
|
||||
// POSIX defines setitimer() with the restrict keyword but let's
|
||||
// accommodate the usage setitimer(ITIMER_REAL, &it, &it) anyway
|
||||
config = *neu;
|
||||
}
|
||||
nsync_mu_lock(&__itimer.lock);
|
||||
if (old) {
|
||||
old->it_interval = __itimer.it.it_interval;
|
||||
old->it_value = timeval_subz(__itimer.it.it_value, timeval_real());
|
||||
}
|
||||
if (neu) {
|
||||
if (!timeval_iszero(config.it_value)) {
|
||||
config.it_value = timeval_add(config.it_value, timeval_real());
|
||||
}
|
||||
__itimer.it = config;
|
||||
nsync_cv_signal(&__itimer.cond);
|
||||
}
|
||||
nsync_mu_unlock(&__itimer.lock);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif /* __x86_64__ */
|
24
libc/thread/itimer.internal.h
Normal file
24
libc/thread/itimer.internal.h
Normal file
|
@ -0,0 +1,24 @@
|
|||
#ifndef COSMOPOLITAN_LIBC_ITIMER_H_
|
||||
#define COSMOPOLITAN_LIBC_ITIMER_H_
|
||||
#include "libc/atomic.h"
|
||||
#include "libc/calls/struct/itimerval.h"
|
||||
#include "third_party/nsync/cv.h"
|
||||
#include "third_party/nsync/mu.h"
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
struct IntervalTimer {
|
||||
atomic_uint once;
|
||||
intptr_t thread;
|
||||
nsync_mu lock;
|
||||
nsync_cv cond;
|
||||
struct itimerval it;
|
||||
};
|
||||
|
||||
extern struct IntervalTimer __itimer;
|
||||
|
||||
void __itimer_reset(void);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_LIBC_ITIMER_H_ */
|
|
@ -27,7 +27,6 @@
|
|||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/str/locale.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/thread/spawn.h"
|
||||
#include "libc/thread/tls.h"
|
||||
|
||||
#define I(x) ((uintptr_t)x)
|
||||
|
@ -132,7 +131,6 @@ static char *_mktls_above(struct CosmoTib **out_tib) {
|
|||
* @return buffer that must be released with free()
|
||||
*/
|
||||
char *_mktls(struct CosmoTib **out_tib) {
|
||||
__require_tls();
|
||||
#ifdef __x86_64__
|
||||
return _mktls_below(out_tib);
|
||||
#else
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#define COSMOPOLITAN_LIBC_THREAD_POSIXTHREAD_INTERNAL_H_
|
||||
#include "libc/calls/struct/sched_param.h"
|
||||
#include "libc/calls/struct/sigaltstack.h"
|
||||
#include "libc/calls/struct/sigset.h"
|
||||
#include "libc/intrin/dll.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
@ -14,9 +13,9 @@
|
|||
#define PT_NOCANCEL 8
|
||||
#define PT_MASKED 16
|
||||
#define PT_INCANCEL 32
|
||||
#define PT_BLOCKED 64
|
||||
#define PT_EXITING 128
|
||||
#define PT_OPENBSD_KLUDGE 256
|
||||
#define PT_POLLING 64 // windows only
|
||||
#define PT_INSEMAPHORE 128 // windows only
|
||||
#define PT_OPENBSD_KLUDGE 128 // openbsd only
|
||||
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
@ -36,6 +35,9 @@ enum PosixThreadStatus {
|
|||
//
|
||||
// - kPosixThreadJoinable -> kPosixThreadDetached if pthread_detach()
|
||||
// is called on this thread.
|
||||
//
|
||||
// - kPosixThreadJoinable -> kPosixThreadZombie if another thread
|
||||
// calls fork().
|
||||
kPosixThreadJoinable,
|
||||
|
||||
// this is a managed thread that'll be cleaned up by the library.
|
||||
|
@ -45,6 +47,9 @@ enum PosixThreadStatus {
|
|||
// - kPosixThreadDetached -> kPosixThreadZombie if start_routine()
|
||||
// returns, or is longjmp'd out of by pthread_exit(), and the thread
|
||||
// is waiting to be joined.
|
||||
//
|
||||
// - kPosixThreadDetached -> kPosixThreadZombie if another thread
|
||||
// calls fork().
|
||||
kPosixThreadDetached,
|
||||
|
||||
// this is a joinable thread that terminated.
|
||||
|
@ -55,6 +60,8 @@ enum PosixThreadStatus {
|
|||
// pthread_join() is called by the user.
|
||||
// - kPosixThreadTerminated -> kPosixThreadZombie will happen when
|
||||
// pthread_detach() is called by the user.
|
||||
// - kPosixThreadTerminated -> kPosixThreadZombie if another thread
|
||||
// calls fork().
|
||||
kPosixThreadTerminated,
|
||||
|
||||
// this is a detached thread that terminated.
|
||||
|
@ -69,7 +76,7 @@ enum PosixThreadStatus {
|
|||
#define POSIXTHREAD_CONTAINER(e) DLL_CONTAINER(struct PosixThread, list, e)
|
||||
|
||||
struct PosixThread {
|
||||
int flags; // 0x00: see PT_* constants
|
||||
int pt_flags; // 0x00: see PT_* constants
|
||||
_Atomic(int) cancelled; // 0x04: thread has bad beliefs
|
||||
_Atomic(enum PosixThreadStatus) status;
|
||||
_Atomic(int) ptid; // transitions 0 → tid
|
||||
|
@ -79,9 +86,13 @@ struct PosixThread {
|
|||
char *tls; // bottom of tls allocation
|
||||
struct CosmoTib *tib; // middle of tls allocation
|
||||
struct Dll list; // list of threads
|
||||
pthread_t next; // for xnu silicon
|
||||
jmp_buf exiter; // for pthread_exit
|
||||
_Atomic(_Atomic(int) *) pt_futex;
|
||||
intptr_t semaphore;
|
||||
intptr_t iohandle;
|
||||
void *ioverlap;
|
||||
jmp_buf exiter;
|
||||
pthread_attr_t attr;
|
||||
int abort_errno;
|
||||
struct _pthread_cleanup_buffer *cleanup;
|
||||
};
|
||||
|
||||
|
@ -89,20 +100,29 @@ typedef void (*atfork_f)(void);
|
|||
|
||||
extern struct Dll *_pthread_list;
|
||||
extern pthread_spinlock_t _pthread_lock;
|
||||
extern struct PosixThread _pthread_static;
|
||||
extern _Atomic(pthread_key_dtor) _pthread_key_dtor[PTHREAD_KEYS_MAX];
|
||||
|
||||
int _pthread_atfork(atfork_f, atfork_f, atfork_f);
|
||||
void _pthread_decimate(void);
|
||||
int _pthread_tid(struct PosixThread *);
|
||||
void _pthread_unkey(struct CosmoTib *);
|
||||
void _pthread_unwind(struct PosixThread *);
|
||||
int _pthread_reschedule(struct PosixThread *);
|
||||
intptr_t _pthread_syshand(struct PosixThread *);
|
||||
int _pthread_atfork(atfork_f, atfork_f, atfork_f);
|
||||
int _pthread_setschedparam_freebsd(int, int, const struct sched_param *);
|
||||
int _pthread_signal(struct PosixThread *, int, int);
|
||||
void _pthread_free(struct PosixThread *, bool);
|
||||
void _pthread_zombify(struct PosixThread *);
|
||||
void _pthread_free(struct PosixThread *);
|
||||
void _pthread_onfork_prepare(void);
|
||||
void _pthread_onfork_parent(void);
|
||||
void _pthread_onfork_child(void);
|
||||
long _pthread_cancel_sys(void);
|
||||
long _pthread_cancel_ack(void);
|
||||
void _pthread_ungarbage(void);
|
||||
|
||||
__funline pureconst struct PosixThread *_pthread_self(void) {
|
||||
return (struct PosixThread *)__get_tls()->tib_pthread;
|
||||
}
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_LIBC_THREAD_POSIXTHREAD_INTERNAL_H_ */
|
||||
|
|
|
@ -26,7 +26,9 @@
|
|||
#include "libc/intrin/dll.h"
|
||||
#include "libc/intrin/handlock.internal.h"
|
||||
#include "libc/intrin/leaky.internal.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/proc/proc.internal.h"
|
||||
#include "libc/runtime/memtrack.internal.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/str/str.h"
|
||||
|
@ -34,22 +36,18 @@
|
|||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
|
||||
struct AtFork {
|
||||
struct AtFork *p[2];
|
||||
atfork_f f[3];
|
||||
};
|
||||
|
||||
static struct AtForks {
|
||||
pthread_spinlock_t lock;
|
||||
struct AtFork {
|
||||
struct AtFork *p[2];
|
||||
atfork_f f[3];
|
||||
} * list;
|
||||
struct AtFork *list;
|
||||
struct AtFork pool[8];
|
||||
atomic_int allocated;
|
||||
} _atforks;
|
||||
|
||||
static void _pthread_purge(void) {
|
||||
struct Dll *e;
|
||||
while ((e = dll_first(_pthread_list))) {
|
||||
dll_remove(&_pthread_list, e);
|
||||
_pthread_free(POSIXTHREAD_CONTAINER(e));
|
||||
}
|
||||
}
|
||||
|
||||
static void _pthread_onfork(int i) {
|
||||
struct AtFork *a;
|
||||
unassert(0 <= i && i <= 2);
|
||||
|
@ -65,52 +63,48 @@ void _pthread_onfork_prepare(void) {
|
|||
_pthread_onfork(0);
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
__fds_lock();
|
||||
__hand_lock();
|
||||
if (IsWindows()) {
|
||||
__hand_lock();
|
||||
}
|
||||
__mmi_lock();
|
||||
}
|
||||
|
||||
void _pthread_onfork_parent(void) {
|
||||
__mmi_unlock();
|
||||
__hand_unlock();
|
||||
if (IsWindows()) {
|
||||
__hand_unlock();
|
||||
}
|
||||
__fds_unlock();
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
_pthread_onfork(1);
|
||||
}
|
||||
|
||||
void _pthread_onfork_child(void) {
|
||||
struct CosmoTib *tib;
|
||||
struct PosixThread *pt;
|
||||
if (IsWindows()) __hand_wipe();
|
||||
pthread_mutexattr_t attr;
|
||||
extern pthread_mutex_t __mmi_lock_obj;
|
||||
tib = __get_tls();
|
||||
pt = (struct PosixThread *)tib->tib_pthread;
|
||||
|
||||
// let's choose to let the new process live.
|
||||
// even though it's unclear what to do with this kind of race.
|
||||
atomic_store_explicit(&pt->cancelled, false, memory_order_relaxed);
|
||||
|
||||
// wipe core runtime locks
|
||||
__hand_init();
|
||||
pthread_mutexattr_init(&attr);
|
||||
pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
|
||||
extern pthread_mutex_t __mmi_lock_obj;
|
||||
pthread_mutex_init(&__mmi_lock_obj, &attr);
|
||||
pthread_mutex_init(&__fds_lock_obj, &attr);
|
||||
(void)pthread_spin_init(&_pthread_lock, 0);
|
||||
|
||||
// call user-supplied forked child callbacks
|
||||
_pthread_onfork(2);
|
||||
}
|
||||
|
||||
// delete other threads that existed before forking
|
||||
// this must come after onfork, since it calls free
|
||||
dll_remove(&_pthread_list, &pt->list);
|
||||
_pthread_purge();
|
||||
dll_make_first(&_pthread_list, &pt->list);
|
||||
static struct AtFork *_pthread_atfork_alloc(void) {
|
||||
int i, n = ARRAYLEN(_atforks.pool);
|
||||
if (atomic_load_explicit(&_atforks.allocated, memory_order_relaxed) < n &&
|
||||
(i = atomic_fetch_add(&_atforks.allocated, 1)) < n) {
|
||||
return _atforks.pool + i;
|
||||
} else {
|
||||
return malloc(sizeof(struct AtFork));
|
||||
}
|
||||
}
|
||||
|
||||
int _pthread_atfork(atfork_f prepare, atfork_f parent, atfork_f child) {
|
||||
int rc;
|
||||
struct AtFork *a;
|
||||
if (!(a = malloc(sizeof(struct AtFork)))) return ENOMEM;
|
||||
if (!(a = _pthread_atfork_alloc())) return ENOMEM;
|
||||
a->f[0] = prepare;
|
||||
a->f[1] = parent;
|
||||
a->f[2] = child;
|
||||
|
|
|
@ -16,15 +16,27 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/errno.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
/**
|
||||
* Sets size of unmapped pages at bottom of stack.
|
||||
*
|
||||
* This value will be rounded up to the host microprocessor page size,
|
||||
* which is usually 4096 or 16384. It's important to write code in such
|
||||
* a way that that code can't skip over the guard area. GCC has warnings
|
||||
* like `-Wframe-larger-than=4096 -Walloca-larger-than=4096` which help
|
||||
* guarantee your code is safe in this regard. It should be assumed the
|
||||
* guard pages exist beneath the stack pointer, rather than the bottom
|
||||
* of the stack, since guard pages are also used to grow down commit,
|
||||
* which can be poked using CheckLargeStackAllocation().
|
||||
*
|
||||
* @param guardsize contains guard size in bytes
|
||||
* @return 0 on success, or errno on error
|
||||
* @raise EINVAL if `guardsize` is zero
|
||||
*/
|
||||
errno_t pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize) {
|
||||
if (!guardsize) return EINVAL;
|
||||
attr->__guardsize = guardsize;
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include "libc/errno.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
__static_yoink("_pthread_reschedule");
|
||||
|
||||
/**
|
||||
* Sets thread scheduler inheritance attribute, e.g.
|
||||
*
|
||||
|
|
|
@ -18,19 +18,29 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/sig.internal.h"
|
||||
#include "libc/calls/struct/sigaction.h"
|
||||
#include "libc/calls/struct/siginfo.h"
|
||||
#include "libc/calls/struct/sigset.h"
|
||||
#include "libc/calls/struct/ucontext.internal.h"
|
||||
#include "libc/calls/syscall-sysv.internal.h"
|
||||
#include "libc/calls/syscall_support-sysv.internal.h"
|
||||
#include "libc/calls/ucontext.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/nt/enum/context.h"
|
||||
#include "libc/nt/enum/threadaccess.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/nt/struct/context.h"
|
||||
#include "libc/nt/thread.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/runtime/syslib.internal.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/sa.h"
|
||||
#include "libc/sysv/consts/sicode.h"
|
||||
#include "libc/sysv/consts/sig.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
|
@ -42,42 +52,135 @@ int systemfive_cancel(void);
|
|||
extern const char systemfive_cancellable[];
|
||||
extern const char systemfive_cancellable_end[];
|
||||
|
||||
long _pthread_cancel_sys(void) {
|
||||
struct PosixThread *pt;
|
||||
pt = (struct PosixThread *)__get_tls()->tib_pthread;
|
||||
if (!(pt->flags & (PT_NOCANCEL | PT_MASKED)) || (pt->flags & PT_ASYNC)) {
|
||||
long _pthread_cancel_ack(void) {
|
||||
struct PosixThread *pt = _pthread_self();
|
||||
if (!(pt->pt_flags & (PT_NOCANCEL | PT_MASKED)) ||
|
||||
(pt->pt_flags & PT_ASYNC)) {
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
}
|
||||
pt->flags |= PT_NOCANCEL | PT_OPENBSD_KLUDGE;
|
||||
pt->pt_flags |= PT_NOCANCEL | PT_OPENBSD_KLUDGE;
|
||||
return ecanceled();
|
||||
}
|
||||
|
||||
static void OnSigThr(int sig, siginfo_t *si, void *ctx) {
|
||||
ucontext_t *uc = ctx;
|
||||
struct CosmoTib *t;
|
||||
static void _pthread_cancel_sig(int sig, siginfo_t *si, void *arg) {
|
||||
ucontext_t *ctx = arg;
|
||||
|
||||
// check thread runtime state is initialized and cancelled
|
||||
struct PosixThread *pt;
|
||||
if ((t = __get_tls()) && // TODO: why can it be null on freebsd?
|
||||
(pt = (struct PosixThread *)t->tib_pthread) &&
|
||||
!(pt->flags & PT_NOCANCEL) &&
|
||||
atomic_load_explicit(&pt->cancelled, memory_order_acquire)) {
|
||||
sigaddset(&uc->uc_sigmask, sig);
|
||||
if (systemfive_cancellable <= (char *)uc->uc_mcontext.PC &&
|
||||
(char *)uc->uc_mcontext.PC < systemfive_cancellable_end) {
|
||||
uc->uc_mcontext.PC = (intptr_t)systemfive_cancel;
|
||||
} else if (pt->flags & PT_ASYNC) {
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
} else {
|
||||
__tkill(atomic_load_explicit(&t->tib_tid, memory_order_relaxed), sig, t);
|
||||
if (!__tls_enabled) return;
|
||||
if (!(pt = _pthread_self())) return;
|
||||
if (pt->pt_flags & PT_NOCANCEL) return;
|
||||
if (!atomic_load_explicit(&pt->cancelled, memory_order_acquire)) return;
|
||||
|
||||
// in asynchronous mode we'll just the exit asynchronously
|
||||
if (pt->pt_flags & PT_ASYNC) {
|
||||
sigaddset(&ctx->uc_sigmask, SIGTHR);
|
||||
pthread_sigmask(SIG_SETMASK, &ctx->uc_sigmask, 0);
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
}
|
||||
|
||||
// prevent this handler from being called again by thread
|
||||
sigaddset(&ctx->uc_sigmask, SIGTHR);
|
||||
|
||||
// check for race condition between pre-check and syscall
|
||||
// rewrite the thread's execution state to acknowledge it
|
||||
if (systemfive_cancellable <= (char *)ctx->uc_mcontext.PC &&
|
||||
(char *)ctx->uc_mcontext.PC < systemfive_cancellable_end) {
|
||||
ctx->uc_mcontext.PC = (intptr_t)systemfive_cancel;
|
||||
return;
|
||||
}
|
||||
|
||||
// punts cancellation to start of next cancellation point
|
||||
// we ensure sigthr is a pending signal in case unblocked
|
||||
if (IsXnuSilicon()) {
|
||||
__syslib->__pthread_kill(_pthread_syshand(pt), sig);
|
||||
} else {
|
||||
sys_tkill(_pthread_tid(pt), sig, __get_tls());
|
||||
}
|
||||
}
|
||||
|
||||
static void _pthread_cancel_listen(void) {
|
||||
struct sigaction sa;
|
||||
if (!IsWindows()) {
|
||||
sa.sa_sigaction = _pthread_cancel_sig;
|
||||
sa.sa_flags = SA_SIGINFO | SA_RESTART;
|
||||
memset(&sa.sa_mask, -1, sizeof(sa.sa_mask));
|
||||
npassert(!sigaction(SIGTHR, &sa, 0));
|
||||
}
|
||||
}
|
||||
|
||||
static void pthread_cancel_nt(struct PosixThread *pt, intptr_t hThread) {
|
||||
uint32_t old_suspend_count;
|
||||
if ((pt->pt_flags & PT_ASYNC) && !(pt->pt_flags & PT_NOCANCEL)) {
|
||||
if ((old_suspend_count = SuspendThread(hThread)) != -1u) {
|
||||
if (!old_suspend_count) {
|
||||
struct NtContext cpu;
|
||||
cpu.ContextFlags = kNtContextControl | kNtContextInteger;
|
||||
if (GetThreadContext(hThread, &cpu)) {
|
||||
pt->pt_flags |= PT_NOCANCEL;
|
||||
cpu.Rip = (uintptr_t)pthread_exit;
|
||||
cpu.Rdi = (uintptr_t)PTHREAD_CANCELED;
|
||||
cpu.Rsp &= -16;
|
||||
*(uintptr_t *)(cpu.Rsp -= sizeof(uintptr_t)) = cpu.Rip;
|
||||
unassert(SetThreadContext(hThread, &cpu));
|
||||
__sig_cancel(pt, 0);
|
||||
}
|
||||
}
|
||||
ResumeThread(hThread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ListenForSigThr(void) {
|
||||
struct sigaction sa;
|
||||
sa.sa_sigaction = OnSigThr;
|
||||
sa.sa_flags = SA_SIGINFO | SA_RESTART | SA_ONSTACK;
|
||||
memset(&sa.sa_mask, -1, sizeof(sa.sa_mask));
|
||||
npassert(!sigaction(SIGTHR, &sa, 0));
|
||||
static errno_t _pthread_cancel_impl(struct PosixThread *pt) {
|
||||
|
||||
// install our special signal handler
|
||||
static bool once;
|
||||
if (!once) {
|
||||
_pthread_cancel_listen();
|
||||
once = true;
|
||||
}
|
||||
|
||||
// check if thread is already dead
|
||||
switch (atomic_load_explicit(&pt->status, memory_order_acquire)) {
|
||||
case kPosixThreadZombie:
|
||||
case kPosixThreadTerminated:
|
||||
return ESRCH;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// flip the bit indicating that this thread is cancelled
|
||||
atomic_store_explicit(&pt->cancelled, 1, memory_order_release);
|
||||
|
||||
// does this thread want to cancel itself?
|
||||
if (pt == _pthread_self()) {
|
||||
unassert(!(pt->pt_flags & PT_NOCANCEL));
|
||||
if (!(pt->pt_flags & (PT_NOCANCEL | PT_MASKED)) &&
|
||||
(pt->pt_flags & PT_ASYNC)) {
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
errno_t err;
|
||||
if (IsWindows()) {
|
||||
pthread_cancel_nt(pt, _pthread_syshand(pt));
|
||||
err = 0;
|
||||
} else if (IsXnuSilicon()) {
|
||||
err = __syslib->__pthread_kill(_pthread_syshand(pt), SIGTHR);
|
||||
} else {
|
||||
int e = errno;
|
||||
if (!sys_tkill(_pthread_tid(pt), SIGTHR, pt->tib)) {
|
||||
err = 0;
|
||||
} else {
|
||||
err = errno;
|
||||
errno = e;
|
||||
}
|
||||
}
|
||||
if (err == ESRCH) {
|
||||
err = 0; // we already reported this
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -153,6 +256,7 @@ static void ListenForSigThr(void) {
|
|||
* - `nsync_cv_wait_with_deadline`
|
||||
* - `nsync_cv_wait`
|
||||
* - `opendir`
|
||||
* - `openatemp`, 'mkstemp', etc.
|
||||
* - `pclose`
|
||||
* - `popen`
|
||||
* - `fwrite`, `printf`, `fprintf`, `putc`, etc.
|
||||
|
@ -174,7 +278,7 @@ static void ListenForSigThr(void) {
|
|||
* - `INFOF()`, `WARNF()`, etc.
|
||||
* - `getentropy`
|
||||
* - `gmtime_r`
|
||||
* - `kprintf`
|
||||
* - `kprintf` (by virtue of asm(syscall) and write_nocancel() on xnu)
|
||||
* - `localtime_r`
|
||||
* - `nsync_mu_lock`
|
||||
* - `nsync_mu_unlock`
|
||||
|
@ -185,6 +289,7 @@ static void ListenForSigThr(void) {
|
|||
* - `pthread_setname_np`
|
||||
* - `sem_open`
|
||||
* - `system`
|
||||
* - `openatemp`, 'mkstemp', etc.
|
||||
* - `timespec_sleep`
|
||||
* - `touch`
|
||||
*
|
||||
|
@ -254,53 +359,38 @@ static void ListenForSigThr(void) {
|
|||
*
|
||||
* Isn't safe to use in masked mode. That's because if a cancellation
|
||||
* occurs during the write() operation then cancellations are blocked
|
||||
* while running read(). Masked mode doesn't have second chances. You
|
||||
* while running read(). MASKED MODE DOESN'T HAVE SECOND CHANCES. You
|
||||
* must rigorously check the results of each cancellation point call.
|
||||
*
|
||||
* Unit tests should be able to safely ignore the return value, or at
|
||||
* the very least be programmed to consider ESRCH a successful status
|
||||
*
|
||||
* @param thread may be 0 to cancel all threads except self
|
||||
* @return 0 on success, or errno on error
|
||||
* @raise ESRCH if system thread wasn't alive or we lost a race
|
||||
*/
|
||||
errno_t pthread_cancel(pthread_t thread) {
|
||||
int e, rc, tid;
|
||||
static bool once;
|
||||
struct PosixThread *pt;
|
||||
__require_tls();
|
||||
if (!once) {
|
||||
ListenForSigThr();
|
||||
once = true;
|
||||
}
|
||||
pt = (struct PosixThread *)thread;
|
||||
switch (atomic_load_explicit(&pt->status, memory_order_acquire)) {
|
||||
case kPosixThreadZombie:
|
||||
case kPosixThreadTerminated:
|
||||
return ESRCH;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
atomic_store_explicit(&pt->cancelled, 1, memory_order_release);
|
||||
if (thread == __get_tls()->tib_pthread) {
|
||||
if (!(pt->flags & (PT_NOCANCEL | PT_MASKED)) && (pt->flags & PT_ASYNC)) {
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (!(rc = pthread_getunique_np(thread, &tid))) {
|
||||
if (!IsWindows()) {
|
||||
e = errno;
|
||||
if (!__tkill(tid, SIGTHR, pt->tib)) {
|
||||
rc = 0;
|
||||
} else {
|
||||
rc = errno;
|
||||
errno = e;
|
||||
errno_t err;
|
||||
struct Dll *e;
|
||||
struct PosixThread *arg, *other;
|
||||
if ((arg = (struct PosixThread *)thread)) {
|
||||
err = _pthread_cancel_impl(arg);
|
||||
} else {
|
||||
err = ESRCH;
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
|
||||
other = POSIXTHREAD_CONTAINER(e);
|
||||
if (other != _pthread_self() &&
|
||||
atomic_load_explicit(&other->status, memory_order_acquire) <
|
||||
kPosixThreadTerminated) {
|
||||
_pthread_cancel_impl(other);
|
||||
err = 0;
|
||||
}
|
||||
} else {
|
||||
rc = 0;
|
||||
}
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
}
|
||||
return rc;
|
||||
STRACE("pthread_cancel(%d) → %s", _pthread_tid(arg), DescribeErrno(err));
|
||||
return err;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -317,9 +407,9 @@ errno_t pthread_cancel(pthread_t thread) {
|
|||
void pthread_testcancel(void) {
|
||||
struct PosixThread *pt;
|
||||
if (!__tls_enabled) return;
|
||||
if (!(pt = (struct PosixThread *)__get_tls()->tib_pthread)) return;
|
||||
if (pt->flags & PT_NOCANCEL) return;
|
||||
if ((!(pt->flags & PT_MASKED) || (pt->flags & PT_ASYNC)) &&
|
||||
if (!(pt = _pthread_self())) return;
|
||||
if (pt->pt_flags & PT_NOCANCEL) return;
|
||||
if ((!(pt->pt_flags & PT_MASKED) || (pt->pt_flags & PT_ASYNC)) &&
|
||||
atomic_load_explicit(&pt->cancelled, memory_order_acquire)) {
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
}
|
||||
|
@ -344,13 +434,13 @@ void pthread_testcancel(void) {
|
|||
errno_t pthread_testcancel_np(void) {
|
||||
struct PosixThread *pt;
|
||||
if (!__tls_enabled) return 0;
|
||||
if (!(pt = (struct PosixThread *)__get_tls()->tib_pthread)) return 0;
|
||||
if (pt->flags & PT_NOCANCEL) return 0;
|
||||
if (!(pt = _pthread_self())) return 0;
|
||||
if (pt->pt_flags & PT_NOCANCEL) return 0;
|
||||
if (!atomic_load_explicit(&pt->cancelled, memory_order_acquire)) return 0;
|
||||
if (!(pt->flags & PT_MASKED) || (pt->flags & PT_ASYNC)) {
|
||||
if (!(pt->pt_flags & PT_MASKED) || (pt->pt_flags & PT_ASYNC)) {
|
||||
pthread_exit(PTHREAD_CANCELED);
|
||||
} else {
|
||||
pt->flags |= PT_NOCANCEL;
|
||||
pt->pt_flags |= PT_NOCANCEL;
|
||||
return ECANCELED;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,20 +20,28 @@
|
|||
#include "libc/atomic.h"
|
||||
#include "libc/calls/blocksigs.internal.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/sigset.h"
|
||||
#include "libc/calls/syscall-sysv.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/fmt/itoa.h"
|
||||
#include "libc/intrin/asan.internal.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/bits.h"
|
||||
#include "libc/intrin/bsr.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/dll.h"
|
||||
#include "libc/intrin/kprintf.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/intrin/weaken.h"
|
||||
#include "libc/log/internal.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/mem/alloca.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/nexgen32e/crc32.h"
|
||||
#include "libc/nt/runtime.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/runtime/stack.h"
|
||||
#include "libc/runtime/syslib.internal.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/auxv.h"
|
||||
#include "libc/sysv/consts/clone.h"
|
||||
|
@ -42,10 +50,8 @@
|
|||
#include "libc/sysv/consts/sig.h"
|
||||
#include "libc/sysv/consts/ss.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/spawn.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "libc/thread/wait0.internal.h"
|
||||
|
||||
__static_yoink("nsync_mu_lock");
|
||||
__static_yoink("nsync_mu_unlock");
|
||||
|
@ -56,39 +62,44 @@ __static_yoink("_pthread_atfork");
|
|||
#define MAP_ANON_OPENBSD 0x1000
|
||||
#define MAP_STACK_OPENBSD 0x4000
|
||||
|
||||
static unsigned long roundup2pow(unsigned long x) {
|
||||
return x > 1 ? 2ul << _bsrl(x - 1) : x ? 1 : 0;
|
||||
}
|
||||
|
||||
void _pthread_free(struct PosixThread *pt) {
|
||||
if (pt->flags & PT_STATIC) return;
|
||||
free(pt->tls);
|
||||
if ((pt->flags & PT_OWNSTACK) && //
|
||||
pt->attr.__stackaddr && //
|
||||
pt->attr.__stackaddr != MAP_FAILED) {
|
||||
void _pthread_free(struct PosixThread *pt, bool isfork) {
|
||||
if (pt->pt_flags & PT_STATIC) return;
|
||||
if (pt->pt_flags & PT_OWNSTACK) {
|
||||
unassert(!munmap(pt->attr.__stackaddr, pt->attr.__stacksize));
|
||||
}
|
||||
if (!isfork) {
|
||||
if (IsWindows()) {
|
||||
if (pt->tib->tib_syshand) {
|
||||
unassert(CloseHandle(pt->tib->tib_syshand));
|
||||
}
|
||||
} else if (IsXnuSilicon()) {
|
||||
if (pt->tib->tib_syshand) {
|
||||
__syslib->__pthread_join(pt->tib->tib_syshand, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
free(pt->tls);
|
||||
free(pt);
|
||||
}
|
||||
|
||||
static int PosixThread(void *arg, int tid) {
|
||||
void *rc;
|
||||
struct PosixThread *pt = arg;
|
||||
unassert(__get_tls()->tib_tid > 0);
|
||||
if (pt->attr.__inheritsched == PTHREAD_EXPLICIT_SCHED) {
|
||||
_pthread_reschedule(pt);
|
||||
unassert(_weaken(_pthread_reschedule));
|
||||
_weaken(_pthread_reschedule)(pt); // yoinked by attribute builder
|
||||
}
|
||||
// set long jump handler so pthread_exit can bring control back here
|
||||
if (!setjmp(pt->exiter)) {
|
||||
pt->next = __get_tls()->tib_pthread;
|
||||
__get_tls()->tib_pthread = (pthread_t)pt;
|
||||
unassert(!sigprocmask(SIG_SETMASK, (sigset_t *)pt->attr.__sigmask, 0));
|
||||
pthread_sigmask(SIG_SETMASK, (sigset_t *)pt->attr.__sigmask, 0);
|
||||
rc = pt->start(pt->arg);
|
||||
// ensure pthread_cleanup_pop(), and pthread_exit() popped cleanup
|
||||
unassert(!pt->cleanup);
|
||||
// calling pthread_exit() will either jump back here, or call exit
|
||||
pthread_exit(rc);
|
||||
}
|
||||
// avoid signal handler being triggered after we trash our own stack
|
||||
_sigblockall();
|
||||
// return to clone polyfill which clears tid, wakes futex, and exits
|
||||
return 0;
|
||||
}
|
||||
|
@ -160,7 +171,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
|
|||
// assume they know what they're doing as much as possible
|
||||
if (IsOpenbsd()) {
|
||||
if ((rc = FixupCustomStackOnOpenbsd(&pt->attr))) {
|
||||
_pthread_free(pt);
|
||||
_pthread_free(pt, false);
|
||||
return rc;
|
||||
}
|
||||
}
|
||||
|
@ -168,23 +179,19 @@ static errno_t pthread_create_impl(pthread_t *thread,
|
|||
// cosmo is managing the stack
|
||||
// 1. in mono repo optimize for tiniest stack possible
|
||||
// 2. in public world optimize to *work* regardless of memory
|
||||
unsigned long default_guardsize;
|
||||
default_guardsize = getauxval(AT_PAGESZ);
|
||||
pt->flags = PT_OWNSTACK;
|
||||
pt->attr.__stacksize = MAX(pt->attr.__stacksize, GetStackSize());
|
||||
pt->attr.__stacksize = roundup2pow(pt->attr.__stacksize);
|
||||
pt->attr.__guardsize = ROUNDUP(pt->attr.__guardsize, default_guardsize);
|
||||
if (pt->attr.__guardsize + default_guardsize >= pt->attr.__stacksize) {
|
||||
_pthread_free(pt);
|
||||
int granularity = FRAMESIZE;
|
||||
int pagesize = getauxval(AT_PAGESZ);
|
||||
pt->attr.__guardsize = ROUNDUP(pt->attr.__guardsize, pagesize);
|
||||
pt->attr.__stacksize = ROUNDUP(pt->attr.__stacksize, granularity);
|
||||
if (pt->attr.__guardsize + pagesize > pt->attr.__stacksize) {
|
||||
_pthread_free(pt, false);
|
||||
return EINVAL;
|
||||
}
|
||||
if (pt->attr.__guardsize == default_guardsize) {
|
||||
// user is wisely using smaller stacks with default guard size
|
||||
if (pt->attr.__guardsize == pagesize) {
|
||||
pt->attr.__stackaddr =
|
||||
mmap(0, pt->attr.__stacksize, PROT_READ | PROT_WRITE,
|
||||
MAP_STACK | MAP_ANONYMOUS, -1, 0);
|
||||
} else {
|
||||
// user is tuning things, performance may suffer
|
||||
pt->attr.__stackaddr =
|
||||
mmap(0, pt->attr.__stacksize, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
|
@ -203,9 +210,9 @@ static errno_t pthread_create_impl(pthread_t *thread,
|
|||
}
|
||||
}
|
||||
}
|
||||
if (pt->attr.__stackaddr == MAP_FAILED) {
|
||||
if (!pt->attr.__stackaddr || pt->attr.__stackaddr == MAP_FAILED) {
|
||||
rc = errno;
|
||||
_pthread_free(pt);
|
||||
_pthread_free(pt, false);
|
||||
errno = e;
|
||||
if (rc == EINVAL || rc == EOVERFLOW) {
|
||||
return EINVAL;
|
||||
|
@ -213,6 +220,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
|
|||
return EAGAIN;
|
||||
}
|
||||
}
|
||||
pt->pt_flags |= PT_OWNSTACK;
|
||||
if (IsAsan() && pt->attr.__guardsize) {
|
||||
__asan_poison(pt->attr.__stackaddr, pt->attr.__guardsize,
|
||||
kAsanStackOverflow);
|
||||
|
@ -220,6 +228,8 @@ static errno_t pthread_create_impl(pthread_t *thread,
|
|||
}
|
||||
|
||||
// set initial status
|
||||
pt->tib->tib_pthread = (pthread_t)pt;
|
||||
atomic_store_explicit(&pt->tib->tib_sigmask, -1, memory_order_relaxed);
|
||||
if (!pt->attr.__havesigmask) {
|
||||
pt->attr.__havesigmask = true;
|
||||
memcpy(pt->attr.__sigmask, &oldsigs, sizeof(oldsigs));
|
||||
|
@ -234,15 +244,15 @@ static errno_t pthread_create_impl(pthread_t *thread,
|
|||
memory_order_relaxed);
|
||||
break;
|
||||
default:
|
||||
_pthread_free(pt);
|
||||
_pthread_free(pt, false);
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
// add thread to global list
|
||||
// we add it to the end since zombies go at the beginning
|
||||
// we add it to the beginning since zombies go at the end
|
||||
dll_init(&pt->list);
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
dll_make_last(&_pthread_list, &pt->list);
|
||||
dll_make_first(&_pthread_list, &pt->list);
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
|
||||
// launch PosixThread(pt) in new thread
|
||||
|
@ -256,7 +266,7 @@ static errno_t pthread_create_impl(pthread_t *thread,
|
|||
pthread_spin_lock(&_pthread_lock);
|
||||
dll_remove(&_pthread_list, &pt->list);
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
_pthread_free(pt);
|
||||
_pthread_free(pt, false);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
@ -264,6 +274,13 @@ static errno_t pthread_create_impl(pthread_t *thread,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const char *DescribeHandle(char buf[12], errno_t err, pthread_t *th) {
|
||||
if (err) return "n/a";
|
||||
if (!th) return "NULL";
|
||||
FormatInt32(buf, _pthread_tid((struct PosixThread *)*th));
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates thread, e.g.
|
||||
*
|
||||
|
@ -316,11 +333,13 @@ static errno_t pthread_create_impl(pthread_t *thread,
|
|||
*/
|
||||
errno_t pthread_create(pthread_t *thread, const pthread_attr_t *attr,
|
||||
void *(*start_routine)(void *), void *arg) {
|
||||
errno_t rc;
|
||||
__require_tls();
|
||||
pthread_decimate_np();
|
||||
errno_t err;
|
||||
_pthread_decimate();
|
||||
BLOCK_SIGNALS;
|
||||
rc = pthread_create_impl(thread, attr, start_routine, arg, _SigMask);
|
||||
err = pthread_create_impl(thread, attr, start_routine, arg, _SigMask);
|
||||
ALLOW_SIGNALS;
|
||||
return rc;
|
||||
STRACE("pthread_create([%s], %p, %t, %p) → %s",
|
||||
DescribeHandle(alloca(12), err, thread), attr, start_routine, arg,
|
||||
DescribeErrno(err));
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -27,13 +27,13 @@
|
|||
/**
|
||||
* Releases memory of detached threads that have terminated.
|
||||
*/
|
||||
void pthread_decimate_np(void) {
|
||||
void _pthread_decimate(void) {
|
||||
struct Dll *e;
|
||||
struct PosixThread *pt;
|
||||
enum PosixThreadStatus status;
|
||||
StartOver:
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
for (e = dll_first(_pthread_list); e; e = dll_next(_pthread_list, e)) {
|
||||
for (e = dll_last(_pthread_list); e; e = dll_prev(_pthread_list, e)) {
|
||||
pt = POSIXTHREAD_CONTAINER(e);
|
||||
if (pt->tib == __get_tls()) continue;
|
||||
status = atomic_load_explicit(&pt->status, memory_order_acquire);
|
||||
|
@ -41,7 +41,7 @@ StartOver:
|
|||
if (!atomic_load_explicit(&pt->tib->tib_tid, memory_order_acquire)) {
|
||||
dll_remove(&_pthread_list, e);
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
_pthread_free(pt);
|
||||
_pthread_free(pt, false);
|
||||
goto StartOver;
|
||||
}
|
||||
}
|
|
@ -19,13 +19,36 @@
|
|||
#include "libc/assert.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/spawn.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
static errno_t pthread_detach_impl(struct PosixThread *pt) {
|
||||
enum PosixThreadStatus status, transition;
|
||||
for (;;) {
|
||||
status = atomic_load_explicit(&pt->status, memory_order_acquire);
|
||||
if (status == kPosixThreadJoinable) {
|
||||
transition = kPosixThreadDetached;
|
||||
} else if (status == kPosixThreadTerminated) {
|
||||
transition = kPosixThreadZombie;
|
||||
} else {
|
||||
return EINVAL;
|
||||
}
|
||||
if (atomic_compare_exchange_weak_explicit(&pt->status, &status, transition,
|
||||
memory_order_release,
|
||||
memory_order_relaxed)) {
|
||||
if (transition == kPosixThreadZombie) {
|
||||
_pthread_zombify(pt);
|
||||
}
|
||||
_pthread_decimate();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asks POSIX thread to free itself automatically upon termination.
|
||||
*
|
||||
|
@ -37,30 +60,13 @@
|
|||
* pthread_detach() can't be called twice on the same thread.
|
||||
*
|
||||
* @return 0 on success, or errno with error
|
||||
* @raise EINVAL if `thread` isn't joinable
|
||||
* @returnserrno
|
||||
* @threadsafe
|
||||
*/
|
||||
errno_t pthread_detach(pthread_t thread) {
|
||||
struct PosixThread *pt;
|
||||
enum PosixThreadStatus status, transition;
|
||||
for (pt = (struct PosixThread *)thread;;) {
|
||||
status = atomic_load_explicit(&pt->status, memory_order_acquire);
|
||||
if (status == kPosixThreadJoinable) {
|
||||
transition = kPosixThreadDetached;
|
||||
} else if (status == kPosixThreadTerminated) {
|
||||
transition = kPosixThreadZombie;
|
||||
} else {
|
||||
__builtin_unreachable();
|
||||
}
|
||||
if (atomic_compare_exchange_weak_explicit(&pt->status, &status, transition,
|
||||
memory_order_release,
|
||||
memory_order_relaxed)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (transition == kPosixThreadZombie) {
|
||||
_pthread_zombify(pt);
|
||||
}
|
||||
pthread_decimate_np();
|
||||
return 0;
|
||||
struct PosixThread *pt = (struct PosixThread *)thread;
|
||||
errno_t err = pthread_detach_impl(pt);
|
||||
STRACE("pthread_detach(%d) → %s", _pthread_tid(pt), DescribeErrno(err));
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -24,13 +24,15 @@
|
|||
#include "libc/intrin/weaken.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/mem/gc.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/runtime/internal.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "third_party/nsync/futex.internal.h"
|
||||
|
||||
static void CleanupThread(struct PosixThread *pt) {
|
||||
void _pthread_unwind(struct PosixThread *pt) {
|
||||
struct _pthread_cleanup_buffer *cb;
|
||||
while ((cb = pt->cleanup)) {
|
||||
pt->cleanup = cb->__prev;
|
||||
|
@ -38,25 +40,27 @@ static void CleanupThread(struct PosixThread *pt) {
|
|||
}
|
||||
}
|
||||
|
||||
static void DestroyTlsKeys(struct CosmoTib *tib) {
|
||||
void _pthread_unkey(struct CosmoTib *tib) {
|
||||
int i, j, gotsome;
|
||||
void *val, **keys;
|
||||
pthread_key_dtor dtor;
|
||||
keys = tib->tib_keys;
|
||||
for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; ++j) {
|
||||
for (gotsome = i = 0; i < PTHREAD_KEYS_MAX; ++i) {
|
||||
if ((val = keys[i]) &&
|
||||
(dtor = atomic_load_explicit(_pthread_key_dtor + i,
|
||||
memory_order_relaxed)) &&
|
||||
dtor != (pthread_key_dtor)-1) {
|
||||
gotsome = 1;
|
||||
keys[i] = 0;
|
||||
dtor(val);
|
||||
if ((keys = tib->tib_keys)) {
|
||||
for (j = 0; j < PTHREAD_DESTRUCTOR_ITERATIONS; ++j) {
|
||||
for (gotsome = i = 0; i < PTHREAD_KEYS_MAX; ++i) {
|
||||
if ((val = keys[i]) &&
|
||||
(dtor = atomic_load_explicit(_pthread_key_dtor + i,
|
||||
memory_order_relaxed)) &&
|
||||
dtor != (pthread_key_dtor)-1) {
|
||||
gotsome = 1;
|
||||
keys[i] = 0;
|
||||
dtor(val);
|
||||
}
|
||||
}
|
||||
if (!gotsome) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!gotsome) {
|
||||
break;
|
||||
}
|
||||
free(keys);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,19 +102,25 @@ wontreturn void pthread_exit(void *rc) {
|
|||
struct PosixThread *pt;
|
||||
enum PosixThreadStatus status, transition;
|
||||
|
||||
STRACE("pthread_exit(%p)", rc);
|
||||
|
||||
tib = __get_tls();
|
||||
pt = (struct PosixThread *)tib->tib_pthread;
|
||||
unassert(~pt->flags & PT_EXITING);
|
||||
pt->flags |= PT_EXITING;
|
||||
pt->pt_flags |= PT_NOCANCEL;
|
||||
pt->rc = rc;
|
||||
|
||||
STRACE("pthread_exit(%p)", rc);
|
||||
|
||||
// free resources
|
||||
CleanupThread(pt);
|
||||
DestroyTlsKeys(tib);
|
||||
_pthread_unwind(pt);
|
||||
_pthread_unkey(tib);
|
||||
_pthread_ungarbage();
|
||||
pthread_decimate_np();
|
||||
_pthread_decimate();
|
||||
|
||||
// run atexit handlers if orphaned thread
|
||||
if (pthread_orphan_np()) {
|
||||
if (_weaken(__cxa_finalize)) {
|
||||
_weaken(__cxa_finalize)(NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// transition the thread to a terminated state
|
||||
status = atomic_load_explicit(&pt->status, memory_order_acquire);
|
||||
|
@ -134,14 +144,17 @@ wontreturn void pthread_exit(void *rc) {
|
|||
_pthread_zombify(pt);
|
||||
}
|
||||
|
||||
// check if this is the main thread or an orphaned thread
|
||||
// check if this is the last survivor
|
||||
if (pthread_orphan_np()) {
|
||||
exit(0);
|
||||
for (const uintptr_t *p = __fini_array_end; p > __fini_array_start;) {
|
||||
((void (*)(void))(*--p))();
|
||||
}
|
||||
_Exit(0);
|
||||
}
|
||||
|
||||
// check if the main thread has died whilst children live
|
||||
// note that the main thread is joinable by child threads
|
||||
if (pt->flags & PT_STATIC) {
|
||||
if (pt->pt_flags & PT_STATIC) {
|
||||
atomic_store_explicit(&tib->tib_tid, 0, memory_order_release);
|
||||
nsync_futex_wake_(&tib->tib_tid, INT_MAX, !IsWindows());
|
||||
_Exit1(0);
|
||||
|
|
|
@ -39,34 +39,33 @@
|
|||
errno_t pthread_getaffinity_np(pthread_t thread, size_t size,
|
||||
cpu_set_t *bitset) {
|
||||
int rc, tid;
|
||||
tid = _pthread_tid((struct PosixThread *)thread);
|
||||
|
||||
if (!(rc = pthread_getunique_np(thread, &tid))) {
|
||||
if (size != sizeof(cpu_set_t)) {
|
||||
rc = einval();
|
||||
} else if (IsWindows() || IsMetal() || IsOpenbsd()) {
|
||||
rc = enosys();
|
||||
} else if (IsFreebsd()) {
|
||||
if (!sys_sched_getaffinity_freebsd(CPU_LEVEL_WHICH, CPU_WHICH_TID, tid,
|
||||
32, bitset)) {
|
||||
rc = 32;
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
} else if (IsNetbsd()) {
|
||||
if (!sys_sched_getaffinity_netbsd(tid, 0, 32, bitset)) {
|
||||
rc = 32;
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
if (size != sizeof(cpu_set_t)) {
|
||||
rc = einval();
|
||||
} else if (IsWindows() || IsMetal() || IsOpenbsd()) {
|
||||
rc = enosys();
|
||||
} else if (IsFreebsd()) {
|
||||
if (!sys_sched_getaffinity_freebsd(CPU_LEVEL_WHICH, CPU_WHICH_TID, tid, 32,
|
||||
bitset)) {
|
||||
rc = 32;
|
||||
} else {
|
||||
rc = sys_sched_getaffinity(tid, size, bitset);
|
||||
rc = -1;
|
||||
}
|
||||
if (rc > 0) {
|
||||
if (rc < size) {
|
||||
bzero((char *)bitset + rc, size - rc);
|
||||
}
|
||||
rc = 0;
|
||||
} else if (IsNetbsd()) {
|
||||
if (!sys_sched_getaffinity_netbsd(tid, 0, 32, bitset)) {
|
||||
rc = 32;
|
||||
} else {
|
||||
rc = -1;
|
||||
}
|
||||
} else {
|
||||
rc = sys_sched_getaffinity(tid, size, bitset);
|
||||
}
|
||||
if (rc > 0) {
|
||||
if (rc < size) {
|
||||
bzero((char *)bitset + rc, size - rc);
|
||||
}
|
||||
rc = 0;
|
||||
}
|
||||
|
||||
STRACE("pthread_getaffinity_np(%d, %'zu, %p) → %s", tid, size, bitset,
|
||||
|
|
|
@ -16,8 +16,18 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/struct/rlimit.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/runtime/stack.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/auxv.h"
|
||||
#include "libc/sysv/consts/rlim.h"
|
||||
#include "libc/sysv/consts/rlimit.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
|
@ -37,7 +47,7 @@
|
|||
* thread. This is useful for knowing where the stack is. It can also
|
||||
* be useful If you explicitly configured a stack too, since we might
|
||||
* have needed to slightly tune the address and size to meet platform
|
||||
* requirements. This function returns information that reflects that
|
||||
* requirements.
|
||||
*
|
||||
* 3. You can view changes pthread_create() may have made to the stack
|
||||
* guard size by calling pthread_attr_getguardsize() on `attr`
|
||||
|
@ -62,5 +72,9 @@ errno_t pthread_getattr_np(pthread_t thread, pthread_attr_t *attr) {
|
|||
default:
|
||||
__builtin_unreachable();
|
||||
}
|
||||
if (!attr->__stacksize && (pt->pt_flags & PT_STATIC)) {
|
||||
__get_main_stack(&attr->__stackaddr, &attr->__stacksize,
|
||||
&attr->__guardsize);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -31,10 +31,11 @@
|
|||
#include "libc/sysv/consts/pr.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
|
||||
static errno_t pthread_getname_impl(pthread_t thread, char *name, size_t size) {
|
||||
static errno_t pthread_getname_impl(struct PosixThread *pt, char *name,
|
||||
size_t size) {
|
||||
int e, fd, rc, tid, len;
|
||||
|
||||
if ((rc = pthread_getunique_np(thread, &tid))) return rc;
|
||||
tid = _pthread_tid(pt);
|
||||
if (!size) return 0;
|
||||
bzero(name, size);
|
||||
e = errno;
|
||||
|
@ -127,8 +128,10 @@ static errno_t pthread_getname_impl(pthread_t thread, char *name, size_t size) {
|
|||
*/
|
||||
errno_t pthread_getname_np(pthread_t thread, char *name, size_t size) {
|
||||
errno_t rc;
|
||||
struct PosixThread *pt;
|
||||
pt = (struct PosixThread *)thread;
|
||||
BLOCK_CANCELLATIONS;
|
||||
rc = pthread_getname_impl(thread, name, size);
|
||||
rc = pthread_getname_impl(pt, name, size);
|
||||
ALLOW_CANCELLATIONS;
|
||||
return rc;
|
||||
}
|
||||
|
|
70
libc/thread/pthread_getspecific.c
Normal file
70
libc/thread/pthread_getspecific.c
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
|
||||
// this is a legacy api so we avoid making the tib 1024 bytes larger
|
||||
static void pthread_key_init(void) {
|
||||
if (!__get_tls()->tib_keys) {
|
||||
__get_tls()->tib_keys = calloc(PTHREAD_KEYS_MAX, sizeof(void *));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets value of TLS slot for current thread.
|
||||
*
|
||||
* If `k` wasn't created by pthread_key_create() then the behavior is
|
||||
* undefined. If `k` was unregistered earlier by pthread_key_delete()
|
||||
* then the behavior is undefined.
|
||||
*/
|
||||
int pthread_setspecific(pthread_key_t k, const void *val) {
|
||||
// "The effect of calling pthread_getspecific() or
|
||||
// pthread_setspecific() with a key value not obtained from
|
||||
// pthread_key_create() or after key has been deleted with
|
||||
// pthread_key_delete() is undefined."
|
||||
// ──Quoth POSIX.1-2017
|
||||
pthread_key_init();
|
||||
unassert(0 <= k && k < PTHREAD_KEYS_MAX);
|
||||
unassert(atomic_load_explicit(_pthread_key_dtor + k, memory_order_acquire));
|
||||
__get_tls()->tib_keys[k] = (void *)val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets value of TLS slot for current thread.
|
||||
*
|
||||
* If `k` wasn't created by pthread_key_create() then the behavior is
|
||||
* undefined. If `k` was unregistered earlier by pthread_key_delete()
|
||||
* then the behavior is undefined.
|
||||
*/
|
||||
void *pthread_getspecific(pthread_key_t k) {
|
||||
// "The effect of calling pthread_getspecific() or
|
||||
// pthread_setspecific() with a key value not obtained from
|
||||
// pthread_key_create() or after key has been deleted with
|
||||
// pthread_key_delete() is undefined."
|
||||
// ──Quoth POSIX.1-2017
|
||||
pthread_key_init();
|
||||
unassert(0 <= k && k < PTHREAD_KEYS_MAX);
|
||||
unassert(atomic_load_explicit(_pthread_key_dtor + k, memory_order_acquire));
|
||||
return __get_tls()->tib_keys[k];
|
||||
}
|
|
@ -16,26 +16,14 @@
|
|||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
/**
|
||||
* Returns system thread id of POSIX thread.
|
||||
*
|
||||
* @return 0 on success, or errno on error
|
||||
*/
|
||||
errno_t pthread_getunique_np(pthread_t thread, pthread_id_np_t *out_tid) {
|
||||
int tid;
|
||||
struct PosixThread *pt;
|
||||
for (pt = (struct PosixThread *)thread;;) {
|
||||
tid = atomic_load_explicit(&pt->ptid, memory_order_acquire);
|
||||
if (!tid) {
|
||||
pthread_yield();
|
||||
} else {
|
||||
*out_tid = tid;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
*out_tid = _pthread_tid((struct PosixThread *)thread);
|
||||
return 0;
|
||||
}
|
||||
|
|
60
libc/thread/pthread_key_create.c
Normal file
60
libc/thread/pthread_key_create.c
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
/**
|
||||
* Allocates TLS slot.
|
||||
*
|
||||
* This function creates a thread-local storage registration, that will
|
||||
* apply to all threads. The new identifier is written to `key`, and it
|
||||
* can be passed to the pthread_setspecific() and pthread_getspecific()
|
||||
* functions to set and get its associated value. Each thread will have
|
||||
* its key value initialized to zero upon creation. It is also possible
|
||||
* to use pthread_key_delete() to unregister a key.
|
||||
*
|
||||
* If `dtor` is non-null, then it'll be called upon pthread_exit() when
|
||||
* the key's value is nonzero. The key's value is set to zero before it
|
||||
* is called. The ordering of multiple destructor calls is unspecified.
|
||||
* The same key can be destroyed `PTHREAD_DESTRUCTOR_ITERATIONS` times,
|
||||
* in cases where it gets set again by a destructor.
|
||||
*
|
||||
* @param key is set to the allocated key on success
|
||||
* @param dtor specifies an optional destructor callback
|
||||
* @return 0 on success, or errno on error
|
||||
* @raise EAGAIN if `PTHREAD_KEYS_MAX` keys exist
|
||||
*/
|
||||
int pthread_key_create(pthread_key_t *key, pthread_key_dtor dtor) {
|
||||
int i;
|
||||
pthread_key_dtor expect;
|
||||
if (!dtor) dtor = (pthread_key_dtor)-1;
|
||||
for (i = 0; i < PTHREAD_KEYS_MAX; ++i) {
|
||||
if (!(expect = atomic_load_explicit(_pthread_key_dtor + i,
|
||||
memory_order_acquire)) &&
|
||||
atomic_compare_exchange_strong_explicit(_pthread_key_dtor + i, &expect,
|
||||
dtor, memory_order_release,
|
||||
memory_order_relaxed)) {
|
||||
*key = i;
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return EAGAIN;
|
||||
}
|
|
@ -17,51 +17,25 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/cp.internal.h"
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "libc/thread/wait0.internal.h"
|
||||
#include "third_party/nsync/futex.internal.h"
|
||||
|
||||
/**
|
||||
* Blocks until memory location becomes zero.
|
||||
* Deletes TLS slot.
|
||||
*
|
||||
* This is intended to be used on the child thread id, which is updated
|
||||
* by the clone() system call when a thread terminates. We need this in
|
||||
* order to know when it's safe to free a thread's stack. This function
|
||||
* uses futexes on Linux, FreeBSD, OpenBSD, and Windows. On other
|
||||
* platforms this uses polling with exponential backoff.
|
||||
* This function should only be called if all threads have finished
|
||||
* using the key registration. If a key is used after being deleted
|
||||
* then the behavior is undefined. If `k` was not registered by the
|
||||
* pthread_key_create() function then the behavior is undefined.
|
||||
*
|
||||
* @param key was created by pthread_key_create()
|
||||
* @return 0 on success, or errno on error
|
||||
* @raise ECANCELED if calling thread was cancelled in masked mode
|
||||
* @raise EBUSY if `abstime` was specified and deadline expired
|
||||
* @cancellationpoint
|
||||
*/
|
||||
errno_t _wait0(atomic_int *ctid, struct timespec *abstime) {
|
||||
int x, e, rc = 0;
|
||||
// "The behavior is undefined if the value specified by the thread
|
||||
// argument to pthread_join() refers to the calling thread."
|
||||
// ──Quoth POSIX.1-2017
|
||||
unassert(ctid != &__get_tls()->tib_tid);
|
||||
// "If the thread calling pthread_join() is canceled, then the target
|
||||
// thread shall not be detached." ──Quoth POSIX.1-2017
|
||||
if (!(rc = pthread_testcancel_np())) {
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) {
|
||||
e = nsync_futex_wait_(ctid, x, !IsWindows(), abstime);
|
||||
if (e == -ECANCELED) {
|
||||
rc = ECANCELED;
|
||||
break;
|
||||
} else if (e == -ETIMEDOUT) {
|
||||
rc = EBUSY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
END_CANCELLATION_POINT;
|
||||
}
|
||||
return rc;
|
||||
int pthread_key_delete(pthread_key_t k) {
|
||||
unassert(0 <= k && k < PTHREAD_KEYS_MAX);
|
||||
unassert(atomic_load_explicit(_pthread_key_dtor + k, memory_order_acquire));
|
||||
atomic_store_explicit(_pthread_key_dtor + k, 0, memory_order_release);
|
||||
return 0;
|
||||
}
|
22
libc/thread/pthread_keys.c
Normal file
22
libc/thread/pthread_keys.c
Normal file
|
@ -0,0 +1,22 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
_Atomic(pthread_key_dtor) _pthread_key_dtor[PTHREAD_KEYS_MAX];
|
|
@ -17,9 +17,15 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/syscall_support-sysv.internal.h"
|
||||
#include "libc/calls/sig.internal.h"
|
||||
#include "libc/calls/syscall-sysv.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/runtime/syslib.internal.h"
|
||||
#include "libc/sysv/consts/sicode.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
|
||||
|
@ -29,22 +35,38 @@
|
|||
* @return 0 on success, or errno on error
|
||||
* @raise ESRCH if `tid` was valid but no such thread existed
|
||||
* @raise EAGAIN if `RLIMIT_SIGPENDING` was exceeded
|
||||
* @raise EINVAL if `tid` or `sig` was invalid
|
||||
* @raise EINVAL if `sig` wasn't a legal signal
|
||||
* @raise EPERM if permission was denied
|
||||
* @asyncsignalsafe
|
||||
*/
|
||||
errno_t pthread_kill(pthread_t thread, int sig) {
|
||||
int e, rc, tid;
|
||||
struct PosixThread *p;
|
||||
if (!(rc = pthread_getunique_np(thread, &tid))) {
|
||||
e = errno;
|
||||
p = (struct PosixThread *)thread;
|
||||
if (!__tkill(tid, sig, p->tib)) {
|
||||
rc = 0;
|
||||
int err = 0;
|
||||
struct PosixThread *pt;
|
||||
pt = (struct PosixThread *)thread;
|
||||
if (!(1 <= sig && sig <= 64)) {
|
||||
err = EINVAL;
|
||||
} else if (thread == __get_tls()->tib_pthread) {
|
||||
err = raise(sig); // XNU will EDEADLK it otherwise
|
||||
} else if (atomic_load_explicit(&pt->status, memory_order_acquire) >=
|
||||
kPosixThreadTerminated) {
|
||||
err = ESRCH;
|
||||
} else if (IsWindows()) {
|
||||
err = __sig_kill(pt, sig, SI_TKILL);
|
||||
} else {
|
||||
if (IsXnuSilicon()) {
|
||||
err = __syslib->__pthread_kill(_pthread_syshand(pt), sig);
|
||||
} else {
|
||||
rc = errno;
|
||||
errno = e;
|
||||
int e = errno;
|
||||
if (sys_tkill(_pthread_tid(pt), sig, pt->tib)) {
|
||||
err = errno;
|
||||
errno = e;
|
||||
}
|
||||
}
|
||||
if (err == ESRCH) {
|
||||
err = 0; // we already reported this
|
||||
}
|
||||
}
|
||||
return rc;
|
||||
STRACE("pthread_kill(%d, %G) → %s", _pthread_tid(pt), sig,
|
||||
DescribeErrno(err));
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -25,24 +25,22 @@
|
|||
#include "libc/thread/posixthread.internal.h"
|
||||
|
||||
errno_t _pthread_reschedule(struct PosixThread *pt) {
|
||||
int e, rc, tid;
|
||||
int policy = pt->attr.__schedpolicy;
|
||||
int e, rc, tid = _pthread_tid(pt);
|
||||
struct sched_param param = {pt->attr.__schedparam};
|
||||
if (!(rc = pthread_getunique_np((pthread_t)pt, &tid))) {
|
||||
e = errno;
|
||||
if (IsNetbsd()) {
|
||||
rc = sys_sched_setparam_netbsd(0, tid, policy, ¶m);
|
||||
} else if (IsLinux()) {
|
||||
rc = sys_sched_setscheduler(tid, policy, ¶m);
|
||||
} else if (IsFreebsd()) {
|
||||
rc = _pthread_setschedparam_freebsd(tid, policy, ¶m);
|
||||
} else {
|
||||
rc = enosys();
|
||||
}
|
||||
if (rc == -1) {
|
||||
rc = errno;
|
||||
errno = e;
|
||||
}
|
||||
e = errno;
|
||||
if (IsNetbsd()) {
|
||||
rc = sys_sched_setparam_netbsd(0, tid, policy, ¶m);
|
||||
} else if (IsLinux()) {
|
||||
rc = sys_sched_setscheduler(tid, policy, ¶m);
|
||||
} else if (IsFreebsd()) {
|
||||
rc = _pthread_setschedparam_freebsd(tid, policy, ¶m);
|
||||
} else {
|
||||
rc = enosys();
|
||||
}
|
||||
if (rc == -1) {
|
||||
rc = errno;
|
||||
errno = e;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
|
|
@ -25,8 +25,5 @@
|
|||
* @asyncsignalsafe
|
||||
*/
|
||||
pthread_t pthread_self(void) {
|
||||
pthread_t t;
|
||||
t = __get_tls()->tib_pthread;
|
||||
unassert(t);
|
||||
return t;
|
||||
return __get_tls()->tib_pthread;
|
||||
}
|
||||
|
|
|
@ -31,15 +31,12 @@
|
|||
#include "libc/thread/posixthread.internal.h"
|
||||
|
||||
static dontinline textwindows int sys_pthread_setaffinity_nt(
|
||||
int tid, uint64_t size, const cpu_set_t *bitset) {
|
||||
int rc;
|
||||
int64_t h;
|
||||
h = OpenThread(kNtThreadSetInformation | kNtThreadQueryInformation, false,
|
||||
tid);
|
||||
if (!h) return __winerr();
|
||||
rc = SetThreadAffinityMask(h, bitset->__bits[0]) ? 0 : __winerr();
|
||||
CloseHandle(h);
|
||||
return rc;
|
||||
struct PosixThread *pt, uint64_t size, const cpu_set_t *bitset) {
|
||||
if (SetThreadAffinityMask(_pthread_syshand(pt), bitset->__bits[0])) {
|
||||
return 0;
|
||||
} else {
|
||||
return __winerr();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -54,24 +51,25 @@ static dontinline textwindows int sys_pthread_setaffinity_nt(
|
|||
errno_t pthread_setaffinity_np(pthread_t thread, size_t size,
|
||||
const cpu_set_t *bitset) {
|
||||
int e, rc, tid;
|
||||
if (!(rc = pthread_getunique_np(thread, &tid))) {
|
||||
e = errno;
|
||||
if (size != sizeof(cpu_set_t)) {
|
||||
rc = einval();
|
||||
} else if (IsWindows()) {
|
||||
rc = sys_pthread_setaffinity_nt(tid, size, bitset);
|
||||
} else if (IsFreebsd()) {
|
||||
rc = sys_sched_setaffinity_freebsd(CPU_LEVEL_WHICH, CPU_WHICH_TID, tid,
|
||||
32, bitset);
|
||||
} else if (IsNetbsd()) {
|
||||
rc = sys_sched_setaffinity_netbsd(tid, 0, 32, bitset);
|
||||
} else {
|
||||
rc = sys_sched_setaffinity(tid, size, bitset);
|
||||
}
|
||||
if (rc == -1) {
|
||||
rc = errno;
|
||||
errno = e;
|
||||
}
|
||||
struct PosixThread *pt;
|
||||
e = errno;
|
||||
pt = (struct PosixThread *)thread;
|
||||
tid = _pthread_tid(pt);
|
||||
if (size != sizeof(cpu_set_t)) {
|
||||
rc = einval();
|
||||
} else if (IsWindows()) {
|
||||
rc = sys_pthread_setaffinity_nt(pt, size, bitset);
|
||||
} else if (IsFreebsd()) {
|
||||
rc = sys_sched_setaffinity_freebsd(CPU_LEVEL_WHICH, CPU_WHICH_TID, tid, 32,
|
||||
bitset);
|
||||
} else if (IsNetbsd()) {
|
||||
rc = sys_sched_setaffinity_netbsd(tid, 0, 32, bitset);
|
||||
} else {
|
||||
rc = sys_sched_setaffinity(tid, size, bitset);
|
||||
}
|
||||
if (rc == -1) {
|
||||
rc = errno;
|
||||
errno = e;
|
||||
}
|
||||
STRACE("pthread_setaffinity_np(%d, %'zu, %p) → %s", tid, size, bitset,
|
||||
DescribeErrno(rc));
|
||||
|
|
|
@ -18,10 +18,23 @@
|
|||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/dce.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/fmt/itoa.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/mem/alloca.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
|
||||
static const char *DescribeCancelType(char buf[12], int err, int *t) {
|
||||
if (err) return "n/a";
|
||||
if (!t) return "NULL";
|
||||
if (*t == PTHREAD_CANCEL_DEFERRED) return "PTHREAD_CANCEL_DEFERRED";
|
||||
if (*t == PTHREAD_CANCEL_ASYNCHRONOUS) return "PTHREAD_CANCEL_ASYNCHRONOUS";
|
||||
FormatInt32(buf, *t);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets cancellation strategy.
|
||||
*
|
||||
|
@ -35,29 +48,32 @@
|
|||
* @see pthread_cancel() for docs
|
||||
*/
|
||||
errno_t pthread_setcanceltype(int type, int *oldtype) {
|
||||
int err;
|
||||
struct PosixThread *pt;
|
||||
switch (type) {
|
||||
case PTHREAD_CANCEL_ASYNCHRONOUS:
|
||||
if (IsWindows()) {
|
||||
return ENOTSUP;
|
||||
}
|
||||
// fallthrough
|
||||
case PTHREAD_CANCEL_DEFERRED:
|
||||
pt = (struct PosixThread *)__get_tls()->tib_pthread;
|
||||
pt = _pthread_self();
|
||||
if (oldtype) {
|
||||
if (pt->flags & PT_ASYNC) {
|
||||
if (pt->pt_flags & PT_ASYNC) {
|
||||
*oldtype = PTHREAD_CANCEL_ASYNCHRONOUS;
|
||||
} else {
|
||||
*oldtype = PTHREAD_CANCEL_DEFERRED;
|
||||
}
|
||||
}
|
||||
if (type == PTHREAD_CANCEL_DEFERRED) {
|
||||
pt->flags &= ~PT_ASYNC;
|
||||
pt->pt_flags &= ~PT_ASYNC;
|
||||
} else {
|
||||
pt->flags |= PT_ASYNC;
|
||||
pt->pt_flags |= PT_ASYNC;
|
||||
}
|
||||
return 0;
|
||||
err = 0;
|
||||
break;
|
||||
default:
|
||||
return EINVAL;
|
||||
err = EINVAL;
|
||||
break;
|
||||
}
|
||||
STRACE("pthread_setcanceltype(%s, [%s]) → %s",
|
||||
DescribeCancelType(alloca(12), 0, &type),
|
||||
DescribeCancelType(alloca(12), err, oldtype), DescribeErrno(err));
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -24,20 +24,32 @@
|
|||
#include "libc/fmt/itoa.h"
|
||||
#include "libc/intrin/asan.internal.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/runtime/syslib.internal.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/at.h"
|
||||
#include "libc/sysv/consts/o.h"
|
||||
#include "libc/sysv/consts/pr.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread.h"
|
||||
#include "libc/thread/tls.h"
|
||||
|
||||
static errno_t pthread_setname_impl(pthread_t thread, const char *name) {
|
||||
static errno_t pthread_setname_impl(struct PosixThread *pt, const char *name) {
|
||||
char path[128], *p;
|
||||
int e, fd, rc, tid, len;
|
||||
|
||||
if ((rc = pthread_getunique_np(thread, &tid))) return rc;
|
||||
len = strlen(name);
|
||||
tid = _pthread_tid(pt);
|
||||
|
||||
if (IsLinux()) {
|
||||
if (IsXnuSilicon()) {
|
||||
if (pt == _pthread_self()) {
|
||||
__syslib->__pthread_setname_np(name);
|
||||
return 0;
|
||||
} else {
|
||||
return EPERM;
|
||||
}
|
||||
} else if (IsLinux()) {
|
||||
e = errno;
|
||||
if (tid == gettid()) {
|
||||
if (prctl(PR_SET_NAME, name) == -1) {
|
||||
|
@ -111,13 +123,17 @@ static errno_t pthread_setname_impl(pthread_t thread, const char *name) {
|
|||
* @return 0 on success, or errno on error
|
||||
* @raise ERANGE if length of `name` exceeded system limit, in which
|
||||
* case the name may have still been set with os using truncation
|
||||
* @raise ENOSYS on MacOS, and Windows
|
||||
* @raise ENOSYS on Windows and AMD64-XNU
|
||||
* @see pthread_getname_np()
|
||||
*/
|
||||
errno_t pthread_setname_np(pthread_t thread, const char *name) {
|
||||
errno_t rc;
|
||||
errno_t err;
|
||||
struct PosixThread *pt;
|
||||
pt = (struct PosixThread *)thread;
|
||||
BLOCK_CANCELLATIONS;
|
||||
rc = pthread_setname_impl(thread, name);
|
||||
err = pthread_setname_impl(pt, name);
|
||||
ALLOW_CANCELLATIONS;
|
||||
return rc;
|
||||
STRACE("pthread_setname_np(%d, %s) → %s", _pthread_tid(pt), name,
|
||||
DescribeErrno(err));
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -17,14 +17,67 @@
|
|||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/cp.internal.h"
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#include "libc/calls/struct/timespec.internal.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/fmt/itoa.h"
|
||||
#include "libc/intrin/atomic.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/dll.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/thread2.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "libc/thread/wait0.internal.h"
|
||||
#include "third_party/nsync/futex.internal.h"
|
||||
|
||||
// TODO(jart): Use condition variable for thread waiting.
|
||||
|
||||
static const char *DescribeReturnValue(char buf[30], int err, void **value) {
|
||||
char *p = buf;
|
||||
if (!value) return "NULL";
|
||||
if (err) return "[n/a]";
|
||||
*p++ = '[';
|
||||
p = FormatHex64(p, (uintptr_t)*value, 1);
|
||||
*p++ = ']';
|
||||
return buf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Blocks until memory location becomes zero.
|
||||
*
|
||||
* This is intended to be used on the child thread id, which is updated
|
||||
* by the clone() system call when a thread terminates. We need this in
|
||||
* order to know when it's safe to free a thread's stack. This function
|
||||
* uses futexes on Linux, FreeBSD, OpenBSD, and Windows. On other
|
||||
* platforms this uses polling with exponential backoff.
|
||||
*
|
||||
* @return 0 on success, or errno on error
|
||||
* @raise ECANCELED if calling thread was cancelled in masked mode
|
||||
* @raise EBUSY if `abstime` was specified and deadline expired
|
||||
* @cancellationpoint
|
||||
*/
|
||||
static errno_t _pthread_wait(atomic_int *ctid, struct timespec *abstime) {
|
||||
int x, e, rc = 0;
|
||||
unassert(ctid != &__get_tls()->tib_tid);
|
||||
// "If the thread calling pthread_join() is canceled, then the target
|
||||
// thread shall not be detached." ──Quoth POSIX.1-2017
|
||||
if (!(rc = pthread_testcancel_np())) {
|
||||
BEGIN_CANCELLATION_POINT;
|
||||
while ((x = atomic_load_explicit(ctid, memory_order_acquire))) {
|
||||
e = nsync_futex_wait_(ctid, x, !IsWindows(), abstime);
|
||||
if (e == -ECANCELED) {
|
||||
rc = ECANCELED;
|
||||
break;
|
||||
} else if (e == -ETIMEDOUT) {
|
||||
rc = EBUSY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
END_CANCELLATION_POINT;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for thread to terminate.
|
||||
|
@ -50,7 +103,7 @@
|
|||
*/
|
||||
errno_t pthread_timedjoin_np(pthread_t thread, void **value_ptr,
|
||||
struct timespec *abstime) {
|
||||
errno_t rc;
|
||||
errno_t err;
|
||||
struct PosixThread *pt;
|
||||
enum PosixThreadStatus status;
|
||||
pt = (struct PosixThread *)thread;
|
||||
|
@ -59,15 +112,18 @@ errno_t pthread_timedjoin_np(pthread_t thread, void **value_ptr,
|
|||
// argument to pthread_join() does not refer to a joinable thread."
|
||||
// ──Quoth POSIX.1-2017
|
||||
unassert(status == kPosixThreadJoinable || status == kPosixThreadTerminated);
|
||||
if (!(rc = _wait0(&pt->tib->tib_tid, abstime))) {
|
||||
if (!(err = _pthread_wait(&pt->tib->tib_tid, abstime))) {
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
dll_remove(&_pthread_list, &pt->list);
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
if (value_ptr) {
|
||||
*value_ptr = pt->rc;
|
||||
}
|
||||
_pthread_free(pt);
|
||||
pthread_decimate_np();
|
||||
_pthread_free(pt, false);
|
||||
_pthread_decimate();
|
||||
}
|
||||
return 0;
|
||||
STRACE("pthread_timedjoin_np(%d, %s, %s) → %s", _pthread_tid(pt),
|
||||
DescribeReturnValue(alloca(30), err, value_ptr),
|
||||
DescribeTimespec(err ? -1 : 0, abstime), DescribeErrno(err));
|
||||
return err;
|
||||
}
|
||||
|
|
|
@ -23,6 +23,6 @@
|
|||
void _pthread_zombify(struct PosixThread *pt) {
|
||||
pthread_spin_lock(&_pthread_lock);
|
||||
dll_remove(&_pthread_list, &pt->list);
|
||||
dll_make_first(&_pthread_list, &pt->list);
|
||||
dll_make_last(&_pthread_list, &pt->list);
|
||||
pthread_spin_unlock(&_pthread_lock);
|
||||
}
|
||||
|
|
|
@ -57,13 +57,13 @@ static void sem_open_unlock(void) {
|
|||
pthread_mutex_unlock(&g_semaphores.lock);
|
||||
}
|
||||
|
||||
static void sem_open_funlock(void) {
|
||||
static void sem_open_wipe(void) {
|
||||
pthread_mutex_init(&g_semaphores.lock, 0);
|
||||
}
|
||||
|
||||
static void sem_open_setup(void) {
|
||||
sem_open_funlock();
|
||||
pthread_atfork(sem_open_lock, sem_open_unlock, sem_open_funlock);
|
||||
sem_open_wipe();
|
||||
pthread_atfork(sem_open_lock, sem_open_unlock, sem_open_wipe);
|
||||
}
|
||||
|
||||
static void sem_open_init(void) {
|
||||
|
|
89
libc/thread/setitimer.c
Normal file
89
libc/thread/setitimer.c
Normal file
|
@ -0,0 +1,89 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/calls/struct/itimerval.h"
|
||||
#include "libc/calls/struct/itimerval.internal.h"
|
||||
#include "libc/dce.h"
|
||||
#include "libc/intrin/asan.internal.h"
|
||||
#include "libc/intrin/describeflags.internal.h"
|
||||
#include "libc/intrin/strace.internal.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/time/time.h"
|
||||
|
||||
/**
|
||||
* Schedules delivery of one-shot or intermittent interrupt signal, e.g.
|
||||
*
|
||||
* Raise SIGALRM every 1.5s:
|
||||
*
|
||||
* sigaction(SIGALRM,
|
||||
* &(struct sigaction){.sa_handler = OnSigalrm},
|
||||
* NULL);
|
||||
* setitimer(ITIMER_REAL,
|
||||
* &(const struct itimerval){{1, 500000},
|
||||
* {1, 500000}},
|
||||
* NULL);
|
||||
*
|
||||
* Single-shot alarm to interrupt connect() after 50ms:
|
||||
*
|
||||
* sigaction(SIGALRM,
|
||||
* &(struct sigaction){.sa_handler = OnSigalrm,
|
||||
* .sa_flags = SA_RESETHAND},
|
||||
* NULL);
|
||||
* setitimer(ITIMER_REAL,
|
||||
* &(const struct itimerval){{0, 0}, {0, 50000}},
|
||||
* NULL);
|
||||
* if (connect(...) == -1 && errno == EINTR) { ... }
|
||||
*
|
||||
* Disarm existing timer:
|
||||
*
|
||||
* setitimer(ITIMER_REAL, &(const struct itimerval){0}, NULL);
|
||||
*
|
||||
* If the goal is to use alarms to interrupt blocking i/o routines, e.g.
|
||||
* read(), connect(), etc. then it's important to install the signal
|
||||
* handler using sigaction() rather than signal(), because the latter
|
||||
* sets the `SA_RESTART` flag.
|
||||
*
|
||||
* Timers are not inherited across fork.
|
||||
*
|
||||
* @param which can be ITIMER_REAL, ITIMER_VIRTUAL, etc.
|
||||
* @param newvalue specifies the interval ({0,0} means one-shot) and
|
||||
* duration ({0,0} means disarm) in microseconds ∈ [0,999999] and
|
||||
* if this parameter is NULL, we'll polyfill getitimer() behavior
|
||||
* @param out_opt_old may receive remainder of previous op (if any)
|
||||
* @return 0 on success or -1 w/ errno
|
||||
*/
|
||||
int setitimer(int which, const struct itimerval *newvalue,
|
||||
struct itimerval *oldvalue) {
|
||||
int rc;
|
||||
if (IsAsan() &&
|
||||
((newvalue && !__asan_is_valid(newvalue, sizeof(*newvalue))) ||
|
||||
(oldvalue && !__asan_is_valid(oldvalue, sizeof(*oldvalue))))) {
|
||||
rc = efault();
|
||||
} else if (!IsWindows()) {
|
||||
if (newvalue) {
|
||||
rc = sys_setitimer(which, newvalue, oldvalue);
|
||||
} else {
|
||||
rc = sys_getitimer(which, oldvalue);
|
||||
}
|
||||
} else {
|
||||
rc = sys_setitimer_nt(which, newvalue, oldvalue);
|
||||
}
|
||||
STRACE("setitimer(%s, %s, [%s]) → %d% m", DescribeItimer(which),
|
||||
DescribeItimerval(0, newvalue), DescribeItimerval(rc, oldvalue), rc);
|
||||
return rc;
|
||||
}
|
|
@ -1,166 +0,0 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2022 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/thread/spawn.h"
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/mem/mem.h"
|
||||
#include "libc/runtime/internal.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/runtime/stack.h"
|
||||
#include "libc/stdalign.internal.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/clone.h"
|
||||
#include "libc/sysv/consts/map.h"
|
||||
#include "libc/sysv/consts/prot.h"
|
||||
#include "libc/sysv/errfuns.h"
|
||||
#include "libc/thread/posixthread.internal.h"
|
||||
#include "libc/thread/tls.h"
|
||||
#include "libc/thread/wait0.internal.h"
|
||||
|
||||
/**
|
||||
* @fileoverview Simple threading API
|
||||
*
|
||||
* This API is supported on all six operating systems. We have this
|
||||
* because the POSIX threads API is positively enormous. We currently
|
||||
* only implement a small subset of POSIX threads, e.g. mutexes. So
|
||||
* until we can implement all of POSIX threads, this API is great. If we
|
||||
* consider that the classic forking concurrency library consists of a
|
||||
* single function, it's a shame POSIX didn't define threads in the past
|
||||
* to just be this. Since create/join/atomics is really all we need.
|
||||
*
|
||||
* Your spawn library abstracts clone() which also works on all
|
||||
* platforms; however our implementation of clone() is significantly
|
||||
* complicated so we strongly recommend always favoring this API.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
|
||||
#define _TLSZ ((intptr_t)_tls_size)
|
||||
#define _TLDZ ((intptr_t)_tdata_size)
|
||||
#define _TIBZ sizeof(struct CosmoTib)
|
||||
#define _MEMZ ROUNDUP(_TLSZ + _TIBZ, alignof(struct CosmoTib))
|
||||
|
||||
struct spawner {
|
||||
int (*fun)(void *, int);
|
||||
void *arg;
|
||||
};
|
||||
|
||||
static int Spawner(void *arg, int tid) {
|
||||
int rc;
|
||||
struct spawner *spawner = arg;
|
||||
rc = spawner->fun(spawner->arg, tid);
|
||||
_pthread_ungarbage();
|
||||
free(spawner);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns thread, e.g.
|
||||
*
|
||||
* int worker(void *arg, int tid) {
|
||||
* const char *s = arg;
|
||||
* printf("%s\n", s);
|
||||
* return 0;
|
||||
* }
|
||||
*
|
||||
* int main() {
|
||||
* struct spawn th;
|
||||
* _spawn(worker, "hi", &th);
|
||||
* _join(&th);
|
||||
* }
|
||||
*
|
||||
* @param fun is thread worker callback, which receives `arg` and `ctid`
|
||||
* @param arg shall be passed to `fun`
|
||||
* @param opt_out_thread needn't be initialiized and is always clobbered
|
||||
* except when it isn't specified, in which case, the thread is kind
|
||||
* of detached and will (currently) just leak the stack / tls memory
|
||||
* @return 0 on success, or -1 w/ errno
|
||||
* @deprecated
|
||||
*/
|
||||
int _spawn(int fun(void *, int), void *arg, struct spawn *opt_out_thread) {
|
||||
errno_t rc;
|
||||
struct spawn *th, ths;
|
||||
struct spawner *spawner;
|
||||
__require_tls();
|
||||
if (!fun) return einval();
|
||||
|
||||
// we need to to clobber the output memory before calling clone, since
|
||||
// there's no guarantee clone() won't suspend the parent, and focus on
|
||||
// running the child instead; in that case child might want to read it
|
||||
if (opt_out_thread) {
|
||||
th = opt_out_thread;
|
||||
} else {
|
||||
th = &ths;
|
||||
}
|
||||
|
||||
// allocate enough TLS memory for all the GNU Linuker (_tls_size)
|
||||
// organized _Thread_local data, as well as Cosmpolitan Libc (64)
|
||||
if (!(th->tls = _mktls(&th->tib))) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// we must use _mapstack() to allocate the stack because OpenBSD has
|
||||
// very strict requirements for what's allowed to be used for stacks
|
||||
if (!(th->stk = NewCosmoStack())) {
|
||||
free(th->tls);
|
||||
return -1;
|
||||
}
|
||||
|
||||
spawner = malloc(sizeof(struct spawner));
|
||||
spawner->fun = fun;
|
||||
spawner->arg = arg;
|
||||
rc = clone(Spawner, th->stk, GetStackSize() - 16 /* openbsd:stackbound */,
|
||||
CLONE_VM | CLONE_THREAD | CLONE_FS | CLONE_FILES | CLONE_SIGHAND |
|
||||
CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID |
|
||||
CLONE_CHILD_SETTID | CLONE_CHILD_CLEARTID,
|
||||
spawner, &th->ptid, __adj_tls(th->tib), &th->tib->tib_tid);
|
||||
if (rc) {
|
||||
errno = rc;
|
||||
FreeCosmoStack(th->stk);
|
||||
free(th->tls);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for thread created by _spawn() to terminate.
|
||||
*
|
||||
* This will free your thread's stack and tls memory too.
|
||||
*
|
||||
* @deprecated
|
||||
*/
|
||||
int _join(struct spawn *th) {
|
||||
int rc;
|
||||
if (th->tib) {
|
||||
// wait for ctid to become zero
|
||||
npassert(!_wait0(&th->tib->tib_tid, 0));
|
||||
// free thread memory
|
||||
free(th->tls);
|
||||
rc = munmap(th->stk, GetStackSize());
|
||||
rc = 0;
|
||||
} else {
|
||||
rc = 0;
|
||||
}
|
||||
bzero(th, sizeof(*th));
|
||||
return rc;
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
#ifndef COSMOPOLITAN_LIBC_THREAD_SPAWN_H_
|
||||
#define COSMOPOLITAN_LIBC_THREAD_SPAWN_H_
|
||||
#include "libc/thread/tls.h"
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
struct spawn {
|
||||
int ptid;
|
||||
char *stk;
|
||||
char *tls;
|
||||
struct CosmoTib *tib;
|
||||
};
|
||||
|
||||
int _spawn(int (*)(void *, int), void *, struct spawn *);
|
||||
int _join(struct spawn *);
|
||||
char *_mktls(struct CosmoTib **);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_LIBC_THREAD_SPAWN_H_ */
|
|
@ -2,7 +2,7 @@
|
|||
#define COSMOPOLITAN_LIBC_THREAD_THREAD_H_
|
||||
|
||||
#define PTHREAD_KEYS_MAX 128
|
||||
#define PTHREAD_STACK_MIN 262144
|
||||
#define PTHREAD_STACK_MIN 65536
|
||||
#define PTHREAD_DESTRUCTOR_ITERATIONS 4
|
||||
|
||||
#define PTHREAD_BARRIER_SERIAL_THREAD 31337
|
||||
|
@ -97,10 +97,10 @@ typedef struct pthread_attr_s {
|
|||
int __schedparam;
|
||||
int __schedpolicy;
|
||||
int __contentionscope;
|
||||
unsigned __guardsize;
|
||||
unsigned __stacksize;
|
||||
int __guardsize;
|
||||
size_t __stacksize;
|
||||
uint32_t __sigmask[4];
|
||||
char *__stackaddr;
|
||||
void *__stackaddr;
|
||||
} pthread_attr_t;
|
||||
|
||||
struct _pthread_cleanup_buffer {
|
||||
|
@ -199,7 +199,6 @@ pthread_t pthread_self(void) pureconst;
|
|||
void *pthread_getspecific(pthread_key_t);
|
||||
void pthread_cleanup_pop(struct _pthread_cleanup_buffer *, int) paramsnonnull();
|
||||
void pthread_cleanup_push(struct _pthread_cleanup_buffer *, void (*)(void *), void *) paramsnonnull((1));
|
||||
void pthread_decimate_np(void);
|
||||
void pthread_exit(void *) wontreturn;
|
||||
void pthread_testcancel(void);
|
||||
|
||||
|
|
|
@ -49,6 +49,12 @@ $(LIBC_THREAD_A).pkg: \
|
|||
$(LIBC_THREAD_A_OBJS) \
|
||||
$(foreach x,$(LIBC_THREAD_A_DIRECTDEPS),$($(x)_A).pkg)
|
||||
|
||||
$(LIBC_THREAD_A_OBJS): private \
|
||||
COPTS += \
|
||||
-fno-sanitize=all \
|
||||
-Wframe-larger-than=4096 \
|
||||
-Walloca-larger-than=4096
|
||||
|
||||
LIBC_THREAD_LIBS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)))
|
||||
LIBC_THREAD_SRCS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_SRCS))
|
||||
LIBC_THREAD_HDRS = $(foreach x,$(LIBC_THREAD_ARTIFACTS),$($(x)_HDRS))
|
||||
|
|
|
@ -17,6 +17,9 @@ struct CosmoFtrace { /* 16 */
|
|||
int64_t ft_lastaddr; /* 8 */
|
||||
};
|
||||
|
||||
/* NOTE: update aarch64 libc/errno.h if sizeof changes */
|
||||
/* NOTE: update aarch64 libc/proc/vfork.S if sizeof changes */
|
||||
/* NOTE: update aarch64 libc/nexgen32e/gc.S if sizeof changes */
|
||||
struct CosmoTib {
|
||||
struct CosmoTib *tib_self; /* 0x00 */
|
||||
struct CosmoFtrace tib_ftracer; /* 0x08 */
|
||||
|
@ -29,54 +32,54 @@ struct CosmoTib {
|
|||
uint64_t tib_flags; /* 0x40 */
|
||||
int tib_ftrace; /* inherited */
|
||||
int tib_strace; /* inherited */
|
||||
uint64_t tib_sigmask; /* inherited */
|
||||
uint64_t tib_sigpending;
|
||||
void *tib_reserved4;
|
||||
void *tib_reserved5;
|
||||
void *tib_reserved6;
|
||||
void *tib_reserved7;
|
||||
void *tib_keys[128];
|
||||
_Atomic(uint64_t) tib_sigmask; /* inherited */
|
||||
_Atomic(uint64_t) tib_sigpending;
|
||||
_Atomic(uint64_t) tib_syshand; /* win32=kThread, xnusilicon=pthread_t */
|
||||
char *tib_sigstack_addr;
|
||||
uint32_t tib_sigstack_size;
|
||||
uint32_t tib_sigstack_flags;
|
||||
void **tib_keys;
|
||||
};
|
||||
|
||||
extern int __threaded;
|
||||
extern unsigned __tls_index;
|
||||
|
||||
char *_mktls(struct CosmoTib **);
|
||||
void __bootstrap_tls(struct CosmoTib *, char *);
|
||||
|
||||
#ifdef __x86_64__
|
||||
extern bool __tls_enabled;
|
||||
#define __tls_enabled_set(x) __tls_enabled = x
|
||||
#elif defined(__aarch64__)
|
||||
#define __tls_enabled \
|
||||
({ \
|
||||
register struct CosmoTib *_t asm("x28"); \
|
||||
!!_t; \
|
||||
})
|
||||
#define __tls_enabled true
|
||||
#define __tls_enabled_set(x) (void)0
|
||||
#else
|
||||
#error "unsupported architecture"
|
||||
#endif
|
||||
|
||||
void __require_tls(void);
|
||||
void __set_tls(struct CosmoTib *);
|
||||
|
||||
#ifdef __x86_64__
|
||||
/**
|
||||
* Returns location of thread information block.
|
||||
*
|
||||
* This can't be used in privileged functions.
|
||||
*/
|
||||
#define __get_tls() \
|
||||
({ \
|
||||
struct CosmoTib *_t; \
|
||||
asm("mov\t%%fs:0,%0" : "=r"(_t) : /* no inputs */ : "memory"); \
|
||||
_t; \
|
||||
})
|
||||
__funline pureconst struct CosmoTib *__get_tls(void) {
|
||||
#ifdef __chibicc__
|
||||
return 0;
|
||||
#elif __x86_64__
|
||||
struct CosmoTib *__tib;
|
||||
__asm__("mov\t%%fs:0,%0" : "=r"(__tib));
|
||||
return __tib;
|
||||
#elif defined(__aarch64__)
|
||||
register struct CosmoTib *__tls __asm__("x28");
|
||||
return __tls - 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __x86_64__
|
||||
#define __adj_tls(tib) (tib)
|
||||
#elif defined(__aarch64__)
|
||||
#define __get_tls() \
|
||||
({ \
|
||||
register struct CosmoTib *_t asm("x28"); \
|
||||
_t - 1; \
|
||||
})
|
||||
#define __adj_tls(tib) ((struct CosmoTib *)(tib) + 1)
|
||||
#endif
|
||||
|
||||
|
|
|
@ -25,14 +25,14 @@ __funline struct CosmoTib *__get_tls_privileged(void) {
|
|||
return (struct CosmoTib *)tib;
|
||||
}
|
||||
|
||||
static dontasan inline struct CosmoTib *__get_tls_win32(void) {
|
||||
__funline struct CosmoTib *__get_tls_win32(void) {
|
||||
char *tib, *lin = (char *)0x30;
|
||||
asm("mov\t%%gs:(%1),%0" : "=a"(tib) : "r"(lin) : "memory");
|
||||
tib = *(char **)(tib + 0x1480 + __tls_index * 8);
|
||||
return (struct CosmoTib *)tib;
|
||||
}
|
||||
|
||||
static dontasan inline void __set_tls_win32(void *tls) {
|
||||
__funline void __set_tls_win32(void *tls) {
|
||||
asm("mov\t%1,%%gs:%0" : "=m"(*((long *)0x1480 + __tls_index)) : "r"(tls));
|
||||
}
|
||||
|
||||
|
|
40
libc/thread/ualarm.c
Normal file
40
libc/thread/ualarm.c
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#include "libc/assert.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/itimerval.h"
|
||||
#include "libc/calls/struct/timeval.h"
|
||||
#include "libc/sysv/consts/itimer.h"
|
||||
|
||||
/**
|
||||
* Sets microsecond-precision alarm.
|
||||
*
|
||||
* This generates a `SIGALRM` signal after `usecs` microseconds. If
|
||||
* `reload` is non-zero, then it'll keep generating `SIGALRM` every
|
||||
* `reload` microseconds after the first signal.
|
||||
*
|
||||
* @asyncsignalsafe
|
||||
*/
|
||||
unsigned ualarm(unsigned usecs, unsigned reload) {
|
||||
struct itimerval it, old;
|
||||
it.it_value = timeval_frommicros(usecs);
|
||||
it.it_interval = timeval_frommicros(reload);
|
||||
npassert(!setitimer(ITIMER_REAL, &it, &old));
|
||||
return timeval_tomicros(old.it_value);
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
#ifndef COSMOPOLITAN_LIBC_INTRIN_WAIT0_H_
|
||||
#define COSMOPOLITAN_LIBC_INTRIN_WAIT0_H_
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#if !(__ASSEMBLER__ + __LINKER__ + 0)
|
||||
COSMOPOLITAN_C_START_
|
||||
|
||||
errno_t _wait0(_Atomic(int) *, struct timespec *);
|
||||
|
||||
COSMOPOLITAN_C_END_
|
||||
#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */
|
||||
#endif /* COSMOPOLITAN_LIBC_INTRIN_WAIT0_H_ */
|
Loading…
Add table
Add a link
Reference in a new issue