From 745bc29a26d898834e6c140df99308fd730206a7 Mon Sep 17 00:00:00 2001 From: "K. Lange" Date: Thu, 18 Aug 2022 16:56:43 +0900 Subject: [PATCH] kernel: rudimentary sigwait --- apps/test-sigwait.c | 28 +++++++ base/usr/include/kernel/process.h | 2 +- base/usr/include/kernel/signal.h | 2 + base/usr/include/signal.h | 1 + base/usr/include/syscall_nums.h | 1 + kernel/sys/signal.c | 130 +++++++++++++++++++----------- kernel/sys/syscall.c | 14 ++++ libc/signal/sigsuspend.c | 15 ++++ 8 files changed, 144 insertions(+), 49 deletions(-) create mode 100644 apps/test-sigwait.c diff --git a/apps/test-sigwait.c b/apps/test-sigwait.c new file mode 100644 index 00000000..9192d474 --- /dev/null +++ b/apps/test-sigwait.c @@ -0,0 +1,28 @@ +#include +#include +#include +#include + +void handler(int sig) { + fprintf(stderr, "received %d\n", sig); +} + +int main(int argc, char * argv[]) { + signal(SIGINT, handler); + signal(SIGWINCH, handler); + + sigset_t mask; + sigemptyset(&mask); + sigaddset(&mask, SIGINT); + sigprocmask(SIG_BLOCK, &mask, NULL); + + while (1) { + int sig = 0; + int result = sigwait(&mask, &sig); + fprintf(stderr, "result = %d, sig = %d, errno = %s\n", result, sig, strerror(errno)); + } + + return 0; +} + + diff --git a/base/usr/include/kernel/process.h b/base/usr/include/kernel/process.h index ef0a36c2..113be127 100644 --- a/base/usr/include/kernel/process.h +++ b/base/usr/include/kernel/process.h @@ -138,7 +138,7 @@ typedef struct process { struct signal_config signals[NUMSIGNALS+1]; sigset_t blocked_signals; sigset_t pending_signals; - sigset_t active_signals; + sigset_t awaited_signals; int supplementary_group_count; gid_t * supplementary_group_list; diff --git a/base/usr/include/kernel/signal.h b/base/usr/include/kernel/signal.h index 66098c1c..eab184c9 100644 --- a/base/usr/include/kernel/signal.h +++ b/base/usr/include/kernel/signal.h @@ -2,6 +2,7 @@ #include #include +#include #if defined(__x86_64__) #include @@ -20,4 +21,5 @@ extern int send_signal(pid_t process, int signal, int force_root); extern int group_send_signal(pid_t group, int signal, int force_root); extern void return_from_signal_handler(struct regs*); extern void process_check_signals(struct regs*); +extern int signal_await(sigset_t awaited, int * sig); diff --git a/base/usr/include/signal.h b/base/usr/include/signal.h index ac4bec4c..01592655 100644 --- a/base/usr/include/signal.h +++ b/base/usr/include/signal.h @@ -25,5 +25,6 @@ extern int sigismember(sigset_t * set, int signum); extern int sigprocmask(int how, const sigset_t * restrict set, sigset_t * restrict oset); extern int sigpending(sigset_t * set); extern int sigsuspend(const sigset_t * restrict set); +extern int sigwait(const sigset_t * set, int * sig); _End_C_Header diff --git a/base/usr/include/syscall_nums.h b/base/usr/include/syscall_nums.h index f1c2ef5b..cf9521e9 100644 --- a/base/usr/include/syscall_nums.h +++ b/base/usr/include/syscall_nums.h @@ -77,3 +77,4 @@ #define SYS_SIGPENDING 74 #define SYS_SIGPROCMASK 75 #define SYS_SIGSUSPEND 76 +#define SYS_SIGWAIT 77 diff --git a/kernel/sys/signal.c b/kernel/sys/signal.c index b5995dda..5a7c0d51 100644 --- a/kernel/sys/signal.c +++ b/kernel/sys/signal.c @@ -204,61 +204,35 @@ _ignore_signal: int send_signal(pid_t process, int signal, int force_root) { process_t * receiver = process_from_pid(process); - if (!receiver) { - /* Invalid pid */ - return -ESRCH; + if (!receiver) return -ESRCH; + if (!force_root && receiver->user != this_core->current_process->user && this_core->current_process->user != USER_ROOT_UID && + !(signal == SIGCONT && receiver->session == this_core->current_process->session)) return -EPERM; + if (receiver->flags & PROC_FLAG_IS_TASKLET) return -EPERM; + if (signal > NUMSIGNALS) return -EINVAL; + if (receiver->flags & PROC_FLAG_FINISHED) return -ESRCH; + if (signal == 0) return 0; + + int awaited = receiver->awaited_signals & (1 << signal); + int ignored = !receiver->signals[signal].handler && !sig_defaults[signal]; + int blocked = (receiver->blocked_signals & (1 << signal)) && signal != SIGKILL && signal != SIGSTOP; + + /* sigcont always unsuspends */ + if (sig_defaults[signal] == SIG_DISP_Cont && (receiver->flags & PROC_FLAG_SUSPENDED)) { + __sync_and_and_fetch(&receiver->flags, ~(PROC_FLAG_SUSPENDED)); + receiver->status = 0; } - if (!force_root && receiver->user != this_core->current_process->user && this_core->current_process->user != USER_ROOT_UID) { - if (!(signal == SIGCONT && receiver->session == this_core->current_process->session)) { - return -EPERM; - } - } + /* Do nothing if the signal is not being waited for or blocked and the default disposition is to ignore. */ + if (!awaited && !blocked && ignored) return 0; - if (receiver->flags & PROC_FLAG_IS_TASKLET) { - /* Can not send signals to kernel tasklets */ - return -EINVAL; - } - - if (signal > NUMSIGNALS) { - /* Invalid signal */ - return -EINVAL; - } - - if (receiver->flags & PROC_FLAG_FINISHED) { - /* Can't send signals to finished processes */ - return -EINVAL; - } - - if (!receiver->signals[signal].handler && !sig_defaults[signal]) { - /* If there is no handler for a signal and its default disposition is IGNORE, - * we don't even bother sending it, to avoid having to interrupt + restart system calls. */ - return 0; - } - - if ((receiver->blocked_signals & (1 << signal)) && signal != SIGKILL && signal != SIGSTOP) { - spin_lock(sig_lock); - receiver->pending_signals |= (1 << signal); - spin_unlock(sig_lock); - return 0; - } - - if (sig_defaults[signal] == SIG_DISP_Cont) { - /* XXX: I'm not sure this check is necessary? And the SUSPEND flag flip probably - * should be on the receiving end. */ - if (!(receiver->flags & PROC_FLAG_SUSPENDED)) { - return -EINVAL; - } else { - __sync_and_and_fetch(&receiver->flags, ~(PROC_FLAG_SUSPENDED)); - receiver->status = 0; - } - } - - /* Append signal to list */ + /* Mark the signal for delivery. */ spin_lock(sig_lock); receiver->pending_signals |= (1 << signal); spin_unlock(sig_lock); + /* If the signal is blocked and not being awaited, end here. */ + if (blocked && !awaited) return 0; + /* Informs any blocking events that the process has been interrupted * by a signal, which should trigger those blocking events to complete * and potentially return -EINTR or -ERESTARTSYS */ @@ -358,3 +332,63 @@ void return_from_signal_handler(struct regs *r) { } maybe_restart_system_call(r,signum); } + +/** + * @brief Synchronously wait for specified signals to become pending. + * + * The signals in @c awaited are set as the current "awaited set". Delivery + * of these signals will ignore the blocked and ignored states and always + * result in the process be awoken with the signal marked pending if it is + * sleeping. When the process awakens from @c switch_task the awaiting set + * will be cleared. + * + * If no unblocked signal is pending and an awaited, blocked signal is pending, + * its signal number will be placed in @p sig and it will be unmarked as + * pending, returning 0. If a unblocked signal is received, @c -EINTR is + * returned, and under normal circumstances the caller should raise this + * return status up and allow normal signal handling to occur. + * + * Otherwise, if the process is reawoken by some other means and no unblocked + * signals or awaited signals are pending, it will apply the awaited set and + * sleep again. This will repeat until either of these conditions are met. + * + * If a signal specified in @p awaited is not currently blocked, but is pending + * upon entering signal_await, it will be marked as not pending and the call + * will return immediately; if an unblocked signal is not pending, it will not + * be awaited: signal_await will return with -EINTR. + * + * @param awaited Signals to wait for, should all be blocked by caller. + * @param sig Will be set to the awaited signal, if one arrives. + * @returns 0 if an awaited signal arrives, -EINTR if another signal arrives. + */ +int signal_await(sigset_t awaited, int * sig) { + do { + sigset_t maybe = awaited & this_core->current_process->pending_signals; + if (maybe) { + int signal = 0; + while (maybe && signal <= NUMSIGNALS) { + if (maybe & 1) { + spin_lock(sig_lock); + this_core->current_process->pending_signals &= ~(1 << signal); + *sig = signal; + spin_unlock(sig_lock); + return 0; + } + maybe >>= 1; + signal++; + } + } + + /* Set awaited signals */ + this_core->current_process->awaited_signals = awaited; + + /* Sleep */ + switch_task(0); + + /* Unset awaited signals. */ + this_core->current_process->awaited_signals = 0; + } while (!PENDING); + + return -EINTR; +} + diff --git a/kernel/sys/syscall.c b/kernel/sys/syscall.c index bac25384..34bd6412 100644 --- a/kernel/sys/syscall.c +++ b/kernel/sys/syscall.c @@ -1013,6 +1013,19 @@ long sys_sigsuspend_cur(void) { return -EINTR; } +long sys_sigwait(sigset_t * set, int * sig) { + PTRCHECK(set,sizeof(sigset_t),0); + PTRCHECK(sig,sizeof(int),MMU_PTR_WRITE); + + /* Silently ignore attempts to wait on KILL or STOP */ + sigset_t awaited = *set & ~((1 << SIGKILL) | (1 << SIGSTOP)); + + /* Don't let processes wait on unblocked signals */ + if (awaited & ~this_core->current_process->blocked_signals) return -EINVAL; + + return signal_await(awaited, sig); +} + long sys_fswait(int c, int fds[]) { PTR_VALIDATE(fds); if (!fds) return -EFAULT; @@ -1224,6 +1237,7 @@ static long (*syscalls[])() = { [SYS_SIGPENDING] = sys_sigpending, [SYS_SIGPROCMASK] = sys_sigprocmask, [SYS_SIGSUSPEND] = sys_sigsuspend_cur, + [SYS_SIGWAIT] = sys_sigwait, [SYS_SOCKET] = net_socket, [SYS_SETSOCKOPT] = net_setsockopt, diff --git a/libc/signal/sigsuspend.c b/libc/signal/sigsuspend.c index 86db232b..1d1f21c2 100644 --- a/libc/signal/sigsuspend.c +++ b/libc/signal/sigsuspend.c @@ -15,3 +15,18 @@ int sigsuspend(const sigset_t * restrict set) { return ret; } +DEFN_SYSCALL2(sigwait,SYS_SIGWAIT,const sigset_t *,int *); + +int sigwait(const sigset_t * set, int * sig) { + int res; + do { + res = syscall_sigwait(set,sig); + } while (res == -EINTR); + + if (res < 0) { + res = -res; + errno = res; + } + + return res; +}