Skip to content

Commit

Permalink
Implement raise() with getcontext() / setcontext()
Browse files Browse the repository at this point in the history
  • Loading branch information
jart committed Nov 6, 2023
1 parent dd83db9 commit 736fdb7
Show file tree
Hide file tree
Showing 12 changed files with 73 additions and 97 deletions.
6 changes: 0 additions & 6 deletions libc/calls/getcontext.S
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@

// Gets machine state.
//
// This function goes 14x slower if sigaction() has ever been used to
// install a signal handling function. If you don't care about signal
// safety and just want fast fibers, then you may override the global
// variable `__interruptible` to disable the sigprocmask() calls, for
// pure userspace context switching.
//
// @return 0 on success, or -1 w/ errno
// @see makecontext()
// @see swapcontext()
Expand Down
10 changes: 8 additions & 2 deletions libc/calls/raise.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "libc/intrin/strace.internal.h"
#include "libc/runtime/syslib.internal.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/errfuns.h"

/**
* Sends signal to self.
Expand All @@ -35,15 +36,20 @@
*
* @param sig can be SIGALRM, SIGINT, SIGTERM, SIGKILL, etc.
* @return 0 on success, or nonzero on failure
* @raise EINVAL if `sig` is invalid
* @asyncsignalsafe
*/
int raise(int sig) {
int rc;
if (IsXnuSilicon()) {
rc = __syslib->__raise(sig);
} else if (IsWindows()) {
__sig_raise(sig, SI_TKILL);
rc = 0;
if (0 <= sig && sig <= 64) {
__sig_raise(sig, SI_TKILL);
rc = 0;
} else {
rc = einval();
}
} else {
rc = sys_tkill(gettid(), sig, 0);
}
Expand Down
91 changes: 47 additions & 44 deletions libc/calls/sig.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "libc/intrin/atomic.h"
#include "libc/intrin/bsf.h"
#include "libc/intrin/describebacktrace.internal.h"
#include "libc/intrin/kprintf.h"
#include "libc/intrin/popcnt.h"
#include "libc/intrin/strace.internal.h"
#include "libc/intrin/weaken.h"
Expand Down Expand Up @@ -169,53 +170,59 @@ static textwindows bool __sig_start(struct PosixThread *pt, int sig,
}

static textwindows sigaction_f __sig_handler(unsigned rva) {
atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed);
return (sigaction_f)(__executable_start + rva);
}

textwindows int __sig_raise(int sig, int sic) {
textwindows int __sig_raise(volatile int sig, int sic) {

// create machine context object
struct PosixThread *pt = _pthread_self();
ucontext_t ctx = {.uc_sigmask = pt->tib->tib_sigmask};
struct NtContext nc;
nc.ContextFlags = kNtContextFull;
GetThreadContext(GetCurrentThread(), &nc);
_ntcontext2linux(&ctx, &nc);
// bitset of kinds of handlers called
volatile int handler_was_called = 0;

// process signal(s)
int handler_was_called = 0;
do {
// start the signal
unsigned rva, flags;
if (__sig_start(pt, sig, &rva, &flags)) {
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
// loop over pending signals
ucontext_t ctx;
getcontext(&ctx);
if (!sig) {
if ((sig = __sig_get(ctx.uc_sigmask))) {
sic = SI_KERNEL;
} else {
return handler_was_called;
}
}

// update the signal mask in preparation for signal handller
sigset_t blocksigs = __sighandmask[sig];
if (!(flags & SA_NODEFER)) blocksigs |= 1ull << (sig - 1);
ctx.uc_sigmask = atomic_fetch_or_explicit(
&pt->tib->tib_sigmask, blocksigs, memory_order_acquire);

// call the user's signal handler
char ssbuf[2][128];
siginfo_t si = {.si_signo = sig, .si_code = sic};
STRACE("__sig_raise(%G, %t) mask %s → %s", sig, __sig_handler(rva),
(DescribeSigset)(ssbuf[0], 0, &ctx.uc_sigmask),
(DescribeSigset)(ssbuf[1], 0, (sigset_t *)&pt->tib->tib_sigmask));
__sig_handler(rva)(sig, &si, &ctx);
(void)ssbuf;

// restore the original signal mask
atomic_store_explicit(&pt->tib->tib_sigmask, ctx.uc_sigmask,
memory_order_release);
handler_was_called |= (flags & SA_RESTART) ? 2 : 1;
// process signal(s)
unsigned rva, flags;
struct PosixThread *pt = _pthread_self();
if (__sig_start(pt, sig, &rva, &flags)) {
if (flags & SA_RESETHAND) {
STRACE("resetting %G handler", sig);
__sighandrvas[sig] = (int32_t)(intptr_t)SIG_DFL;
}
sic = SI_KERNEL;
} while ((sig = __sig_get(ctx.uc_sigmask)));
return handler_was_called;

// update the signal mask in preparation for signal handller
sigset_t blocksigs = __sighandmask[sig];
if (!(flags & SA_NODEFER)) blocksigs |= 1ull << (sig - 1);
ctx.uc_sigmask = atomic_fetch_or_explicit(&pt->tib->tib_sigmask, blocksigs,
memory_order_acquire);

// call the user's signal handler
char ssbuf[2][128];
siginfo_t si = {.si_signo = sig, .si_code = sic};
STRACE("__sig_raise(%G, %t) mask %s → %s", sig, __sig_handler(rva),
(DescribeSigset)(ssbuf[0], 0, &ctx.uc_sigmask),
(DescribeSigset)(ssbuf[1], 0, (sigset_t *)&pt->tib->tib_sigmask));
__sig_handler(rva)(sig, &si, &ctx);
(void)ssbuf;

// record this handler
handler_was_called |= (flags & SA_RESTART) ? 2 : 1;
}

// restore sigmask
// loop back to top
// jump where handler says
sig = 0;
return setcontext(&ctx);
}

textwindows int __sig_relay(int sig, int sic, sigset_t waitmask) {
Expand Down Expand Up @@ -258,7 +265,6 @@ static textwindows wontreturn void __sig_tramp(struct SignalFrame *sf) {
struct CosmoTib *tib = __get_tls();
struct PosixThread *pt = (struct PosixThread *)tib->tib_pthread;
for (;;) {
atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed);

// update the signal mask in preparation for signal handller
sigset_t blocksigs = __sighandmask[sig];
Expand Down Expand Up @@ -513,9 +519,6 @@ static int __sig_crash_sig(struct NtExceptionPointers *ep, int *code) {
static void __sig_unmaskable(struct NtExceptionPointers *ep, int code, int sig,
struct CosmoTib *tib) {

// increment the signal count for getrusage()
atomic_fetch_add_explicit(&__sig.count, 1, memory_order_relaxed);

// log vital crash information reliably for --strace before doing much
// we don't print this without the flag since raw numbers scare people
// this needs at least one page of stack memory in order to get logged
Expand Down
14 changes: 0 additions & 14 deletions libc/calls/sigaction.c
Original file line number Diff line number Diff line change
Expand Up @@ -479,13 +479,6 @@ static int __sigaction(int sig, const struct sigaction *act,
* spawned your process, happened to call `setrlimit()`. Doing this is
* a wonderful idea.
*
* Using signals might make your C runtime slower. Upon successfully
* installing its first signal handling function, sigaction() will set
* the global variable `__interruptible` to true, to let everything else
* know that signals are in play. That way code which would otherwise be
* frequently calling sigprocmask() out of an abundance of caution, will
* no longer need to pay its outrageous cost.
*
* Signal handlers should avoid clobbering global variables like `errno`
* because most signals are asynchronous, i.e. the signal handler might
* be called at any assembly instruction. If something like a `SIGCHLD`
Expand All @@ -506,13 +499,6 @@ int sigaction(int sig, const struct sigaction *act, struct sigaction *oldact) {
rc = einval();
} else {
rc = __sigaction(sig, act, oldact);
if (!rc && act && (uintptr_t)act->sa_handler >= kSigactionMinRva) {
static bool once;
if (!once) {
__interruptible = true;
once = true;
}
}
}
STRACE("sigaction(%G, %s, [%s]) → %d% m", sig, DescribeSigaction(0, act),
DescribeSigaction(rc, oldact), rc);
Expand Down
6 changes: 0 additions & 6 deletions libc/calls/swapcontext.S
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,6 @@
//
// swapcontext(x, y);
//
// This function goes 14x slower if sigaction() has ever been used to
// install a signal handling function. If you don't care about signal
// safety and just want fast fibers, then you may override the global
// variable `__interruptible` to disable the sigprocmask() calls, for
// pure userspace context switching.
//
// @return 0 on success, or -1 w/ errno
// @returnstwice
.ftrace1
Expand Down
8 changes: 0 additions & 8 deletions libc/calls/ucontext.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,21 +24,13 @@
int __tailcontext(const ucontext_t *);

static int __contextmask(const sigset_t *opt_set, sigset_t *opt_out_oldset) {
if (!__interruptible) return 0;
// signal handling functions might exist
// now context switching needs to go 14x slower
return sigprocmask(SIG_SETMASK, opt_set, opt_out_oldset);
}

/**
* Sets machine context.
*
* This function goes 14x slower if sigaction() has ever been used to
* install a signal handling function. If you don't care about signal
* safety and just want fast fibers, then you may override the global
* variable `__interruptible` to disable the sigprocmask() calls, for
* pure userspace context switching.
*
* @return -1 on error w/ errno, otherwise won't return unless sent back
* @see swapcontext()
* @see makecontext()
Expand Down
5 changes: 0 additions & 5 deletions libc/nexgen32e/threaded.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,6 @@
*/
int __threaded;

/**
* Set to true if sigaction() has installed signal handlers.
*/
bool __interruptible;

#ifdef __x86_64__
bool __tls_enabled;
#endif
Expand Down
1 change: 0 additions & 1 deletion libc/runtime/runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,6 @@ extern int __argc;
extern char **__argv;
extern char **__envp;
extern unsigned long *__auxv;
extern bool __interruptible;
extern intptr_t __oldstack;
extern uint64_t __nosync;
extern int __strace;
Expand Down
11 changes: 1 addition & 10 deletions test/libc/calls/getcontext_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ TEST(getcontext, test) {
TEST(getcontext, canReadAndWriteSignalMask) {
sigset_t ss, old;
volatile int n = 0;
__interruptible = true;
sigemptyset(&ss);
sigaddset(&ss, SIGUSR1);
sigprocmask(SIG_SETMASK, &ss, &old);
Expand All @@ -72,8 +71,7 @@ TEST(getcontext, canReadAndWriteSignalMask) {
}

void SetGetContext(void) {
static int a;
a = 0;
int a = 0;
getcontext(&context);
if (!a) {
a = 1;
Expand All @@ -82,9 +80,6 @@ void SetGetContext(void) {
}

BENCH(getcontext, bench) {
__interruptible = false;
EZBENCH2("getsetcontext nosig", donothing, SetGetContext());
__interruptible = true;
EZBENCH2("getsetcontext", donothing, SetGetContext());
}

Expand All @@ -99,10 +94,6 @@ BENCH(swapcontext, bench) {
}
} else {
ready = true;
__interruptible = false;
EZBENCH2("swapcontextx2 nosig", donothing, swapcontext(&loop, &main));
// kprintf("dollar\n");
__interruptible = true;
EZBENCH2("swapcontextx2", donothing, swapcontext(&loop, &main));
// kprintf("dollar\n");
}
Expand Down
12 changes: 12 additions & 0 deletions test/libc/calls/raise_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,12 @@
#include "libc/calls/struct/sigaction.h"
#include "libc/calls/struct/siginfo.h"
#include "libc/dce.h"
#include "libc/intrin/kprintf.h"
#include "libc/runtime/runtime.h"
#include "libc/sysv/consts/sa.h"
#include "libc/sysv/consts/sicode.h"
#include "libc/sysv/consts/sig.h"
#include "libc/testlib/ezbench.h"
#include "libc/testlib/subprocess.h"
#include "libc/testlib/testlib.h"
#include "libc/thread/thread.h"
Expand Down Expand Up @@ -67,9 +69,19 @@ void *Worker(void *arg) {
}

TEST(raise, threaded) {
SPAWN(fork);
signal(SIGILL, SIG_DFL);
pthread_t worker;
ASSERT_EQ(0, pthread_create(&worker, 0, Worker, 0));
ASSERT_EQ(0, pthread_join(worker, 0));
pthread_exit(0);
EXITS(0);
}

void OnRaise(int sig) {
}

BENCH(raise, bench) {
signal(SIGUSR1, OnRaise);
EZBENCH2("raise", donothing, raise(SIGUSR1));
}
5 changes: 5 additions & 0 deletions test/libc/calls/sig_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,3 +116,8 @@ TEST(poll, interrupt) {
ASSERT_TRUE(gotsig);
ASSERT_TRUE(didit);
}

TEST(raise, zero) {
ASSERT_SYS(0, 0, raise(0));
ASSERT_SYS(EINVAL, -1, raise(-1));
}
1 change: 0 additions & 1 deletion test/libc/thread/makecontext_test.c
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ void check_args(long x0, long x1, long x2, long x3, long x4, long x5, double f0,

TEST(makecontext, args) {
char stack[1024];
__interruptible = false;
getcontext(&uc);
uc.uc_link = &goback;
uc.uc_stack.ss_sp = stack;
Expand Down

0 comments on commit 736fdb7

Please sign in to comment.