kernel: rudimentary sigwait

This commit is contained in:
K. Lange 2022-08-18 16:56:43 +09:00
parent 37bd4e08cb
commit 745bc29a26
8 changed files with 144 additions and 49 deletions

28
apps/test-sigwait.c Normal file
View File

@ -0,0 +1,28 @@
#include <stdio.h>
#include <signal.h>
#include <errno.h>
#include <string.h>
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;
}

View File

@ -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;

View File

@ -2,6 +2,7 @@
#include <stdint.h>
#include <sys/types.h>
#include <sys/signal.h>
#if defined(__x86_64__)
#include <kernel/arch/x86_64/regs.h>
@ -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);

View File

@ -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

View File

@ -77,3 +77,4 @@
#define SYS_SIGPENDING 74
#define SYS_SIGPROCMASK 75
#define SYS_SIGSUSPEND 76
#define SYS_SIGWAIT 77

View File

@ -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;
}

View File

@ -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,

View File

@ -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;
}