Subject: Re: sigwait(2) implementation
To: Jaromir Dolecek <jdolecek@netbsd.org>
From: Jason R Thorpe <thorpej@wasabisystems.com>
List: tech-kern
Date: 01/25/2003 09:47:12
On Sat, Jan 25, 2003 at 05:25:25PM +0100, Jaromir Dolecek wrote:
> it occurred to me sigwait(2) could be useful now that we have threads.
> So I implemented one, patch is appended to this e-mail[*]
> It should work according to how sigwait(2) is specified in SUSv3.
> There are two potential issues tho.
A couple of comments:
* Please implement it in terms of sigwaitinfo(), i.e.
do not use a second system call entry point.
* For a threaded program, sigwait()/sigwaitinfo() need to
be implemented entirely within libpthread, much like the
rest of signal delivery is implemented entirely within
libpthread for threaded programs.
> Second one is potentially more dangerous. If thread sigwaits for
> more than one signal and more than one signal actually occurs before
> the thread has a chance to pick them up (say, SIGSEGV and SIGBUS,
> from interrupt context), only one of the signals would get returned
> via sigwait(). The other signal(s) would stay on the ps_sigwaited
> list, and not acted upon by normal signal delivery, as they are
> supposed to. If the process would call sigwait(2) again with same
> args, it would pick up the 'other' signal(s)s from ps_sigwaited list, so they
> eventually _would_ be delivered. I wonder how big is this a problem.
Err, when the sigwait()/sigwaitinfo() call returns, you should simply
clear the bits from the list.
Do not attempt to put any multi-threaded awareness into the kernel's
signal delivery mechanism -- that is all for single-threaded processes;
the thread library deals with the complexity of signals for multi-threaded
applications.
> Also, current implementation lacks any locks. This should
> work as far as our high kernel is biglock+nonpreemptive.
Well, just like the rest of the signal code.
..and now on to specifics of the code...
> Opinions?
>
> Jaromir
>
> [*] The syscall glue in syscall tables and libc is not included in patch.
> Index: kern/kern_sig.c
> ===================================================================
> RCS file: /cvsroot/src/sys/kern/kern_sig.c,v
> retrieving revision 1.130
> diff -u -p -r1.130 kern_sig.c
> --- kern/kern_sig.c 2003/01/18 10:06:29 1.130
> +++ kern/kern_sig.c 2003/01/25 13:41:50
> @@ -356,6 +356,8 @@ siginit(struct proc *p)
> SIGACTION_PS(ps, signum).sa_flags = SA_RESTART;
> }
> sigemptyset(&p->p_sigctx.ps_sigcatch);
> + sigemptyset(&p->p_sigctx.ps_sigwait);
> + sigemptyset(&p->p_sigctx.ps_sigwaited);
> p->p_flag &= ~P_NOCLDSTOP;
>
> /*
> @@ -402,6 +404,8 @@ execsigs(struct proc *p)
> SIGACTION_PS(ps, signum).sa_flags = SA_RESTART;
> }
> sigemptyset(&p->p_sigctx.ps_sigcatch);
> + sigemptyset(&p->p_sigctx.ps_sigwait);
> + sigemptyset(&p->p_sigctx.ps_sigwaited);
> p->p_flag &= ~P_NOCLDSTOP;
>
> /*
> @@ -856,6 +860,22 @@ psignal1(struct proc *p, int signum,
> p->p_sigctx.ps_sigcheck = 1;
>
> /*
> + * If the signal doesn't have SA_CANTMASK (no override for SIGKILL,
> + * please!), check if anything waits on it. If yes, clear the
> + * pending signal from siglist set, add it to sigwaited set, and
> + * wakeup any sigwaiters. The signal won't be processed further
> + * here.
> + */
> + if ((prop & SA_CANTMASK) == 0
> + && sigismember(&p->p_sigctx.ps_sigwait, signum)) {
> + sigdelset(&p->p_sigctx.ps_siglist, signum);
> + sigaddset(&p->p_sigctx.ps_sigwaited, signum);
> +
> + wakeup(&p->p_sigctx.ps_sigwait);
You need to handle the case where the sched_lock might be held, here:
if (dolock)
wakeup(&p->p_sigctx.ps_sigwait);
else
sched_wakeup(&p->p_sigctx.ps_sigwait);
I know it's icky, but we currently have to do this little dance.
> + return;
> + }
> +
> + /*
> * Defer further processing for signals which are held,
> * except that stopped processes must be continued by SIGCONT.
> */
> @@ -1032,7 +1052,8 @@ psignal1(struct proc *p, int signum,
> goto out;
> } else {
> /* Else what? */
> - panic("psignal: Invalid process state.");
> + panic("psignal: Invalid process state %d.",
> + p->p_stat);
> }
> }
> /*NOTREACHED*/
> @@ -1818,6 +1839,79 @@ sys_setcontext(struct lwp *l, void *v, r
> return (EJUSTRETURN);
> }
>
> +int
> +sys_sigwait(struct lwp *l, void *v, register_t *retval)
> +{
> + struct sys_sigwait_args /* {
> + syscallarg(const sigset_t *) set;
> + syscallarg(int *) signum;
> + } */ *uap = v;
> + sigset_t waitset;
> + struct proc *p = l->l_proc;
> + int error;
> + int sig, signum = 0;
> +
> + sigemptyset(&waitset);
> + if ((error = copyin(SCARG(uap, set), &waitset, sizeof(waitset))))
> + return (error);
> +
> + /*
> + * First scan siglist and check if there already is signal from
> + * our waitlist pending. Ignore CANTMASK signals.
> + */
> + for(sig=1; sig < _NSIG; sig++) {
> + if ((sigprop[sig] & SA_CANTMASK) == 0
> + && sigismember(&waitset, sig)
> + && sigismember(&p->p_sigctx.ps_siglist, sig)) {
> + /* found pending signal */
> + signum = sig;
> + sigdelset(&p->p_sigctx.ps_siglist, sig);
> + goto out;
I think you want to find a more efficient way to do this.
> + }
> + }
> +
> + /*
> + * Add to ps_sigwait list according to our needs.
> + * XXXSMP this is only safe with BIG LOCK nonpreemptive kernel
> + */
> + sigplusset(&waitset, &p->p_sigctx.ps_sigwait);
Go ahead and remove the XXXSMP comment there -- it is known that all
of the signal stuff only works with big-lock right now.
> +
> + /*
> + * Loop until we'd either:
> + * 1. found the signal we want
> + * 2. would be interrupted by signal ourselves
> + */
> + for(; error == 0;) {
> + /*
> + * Check if a signal from our wait set has arrived.
> + */
> + for(sig=1; sig < _NSIG; sig++) {
> + if (sigismember(&waitset, sig)
> + && sigismember(&p->p_sigctx.ps_sigwaited, sig)) {
> + /* signal from our wait set has been posted */
> + signum = sig;
> +
> + sigdelset(&p->p_sigctx.ps_sigwaited, sig);
> + goto outset;
> + }
> + }
> +
> + /* wake us in better times */
> + error = tsleep(&p->p_sigctx.ps_sigwait, PUSER|PCATCH,
> + "sigwait", 0);
I think you want to use PPAUSE|PCATCH; see sigsuspend1().
> + }
> +
> + outset:
> + /* remove our set from waited list */
> + sigminusset(&waitset, &p->p_sigctx.ps_sigwait);
Again, I think you simply want to sigemptyset the wait list. A multi-threaded
program is going to handle all of this in the thread library.
> +
> + out:
> + /* If a signal from our wait set arrived, copy it to userland. */
> + if (signum > 0)
> + error = copyout(&signum, SCARG(uap, signum), sizeof(signum));
> +
> + return (error);
> +}
>
> /*
> * Returns true if signal is ignored or masked for passed process.
> Index: sys/signalvar.h
> ===================================================================
> RCS file: /cvsroot/src/sys/sys/signalvar.h,v
> retrieving revision 1.37
> diff -u -p -r1.37 signalvar.h
> --- sys/signalvar.h 2003/01/18 09:53:20 1.37
> +++ sys/signalvar.h 2003/01/25 13:41:50
> @@ -75,6 +75,8 @@ struct sigctx {
> sigset_t ps_sigmask; /* Current signal mask. */
> sigset_t ps_sigignore; /* Signals being ignored. */
> sigset_t ps_sigcatch; /* Signals being caught by user. */
> + sigset_t ps_sigwait; /* Signals being waited for */
> + sigset_t ps_sigwaited; /* Delivered signals from wait set */
> };
>
> /* signal flags */
It would also be nice if you wrote a regression test for this.
Please post an updated patch :-)
--
-- Jason R. Thorpe <thorpej@wasabisystems.com>