A NOTE has been added to this issue. 
====================================================================== 
https://austingroupbugs.net/view.php?id=1731 
====================================================================== 
Reported By:                geoffclare
Assigned To:                
====================================================================== 
Project:                    1003.1(2016/18)/Issue7+TC2
Issue ID:                   1731
Category:                   System Interfaces
Type:                       Clarification Requested
Severity:                   Objection
Priority:                   normal
Status:                     New
Name:                       Geoff Clare 
Organization:               The Open Group 
User Reference:              
Section:                    pthread_sigmask() 
Page Number:                1734 
Line Number:                56243 
Interp Status:              --- 
Final Accepted Text:         
====================================================================== 
Date Submitted:             2023-05-23 09:43 UTC
Last Modified:              2023-05-31 19:50 UTC
====================================================================== 
Summary:                    pthread_sigmask() pending signal requirement time
paradox
====================================================================== 

---------------------------------------------------------------------- 
 (0006296) kre (reporter) - 2023-05-31 19:50
 https://austingroupbugs.net/view.php?id=1731#c6296 
---------------------------------------------------------------------- 
Re https://austingroupbugs.net/view.php?id=1731#c6294

  we should probably also look at a version of BSD from before it started
to
  diverge into different variants.

That's easy, this is the implementation of the system call in 4.4_lite
(or something around that time - from the final CSRG SCCS files).

/*
 * Manipulate signal mask.
 * Note that we receive new mask, not pointer,
 * and return old mask as return value;
 * the library stub does the rest.
 */
int
sigprocmask(p, uap, retval)
        register struct proc *p;
        struct sigprocmask_args /* {
                syscallarg(int) how;
                syscallarg(sigset_t) mask;
        } */ *uap;
        register_t *retval;
{
        int error = 0;

        *retval = p->p_sigmask;
        (void) splhigh();

        switch (SCARG(uap, how)) {
        case SIG_BLOCK:
                p->p_sigmask |= SCARG(uap, mask) &~ sigcantmask;
                break;

        case SIG_UNBLOCK:
                p->p_sigmask &= ~SCARG(uap, mask);
                break;

        case SIG_SETMASK:
                p->p_sigmask = SCARG(uap, mask) &~ sigcantmask;
                break;
        
        default:
                error = EINVAL;
                break;
        }
        (void) spl0();
        return (error);
}

That's it,   There are also compat sys calls, for the earlier
implementation where there were separate sys calls for the functions,
rather than one with an arg saying what to do (as above).

int
compat_43_sigblock(p, uap, retval)
        register struct proc *p;
        struct compat_43_sigblock_args /* {
                syscallarg(int) mask;
        } */ *uap;
        register_t *retval;    
{               
                    
        (void) splhigh();
        *retval = p->p_sigmask;
        p->p_sigmask |= SCARG(uap, mask) &~ sigcantmask;
        (void) spl0();
        return (0);
}                
                 
int
compat_43_sigsetmask(p, uap, retval)
        struct proc *p;
        struct compat_43_sigsetmask_args /* {
                syscallarg(int) mask;
        } */ *uap;
        register_t *retval;
{

        (void) splhigh();
        *retval = p->p_sigmask;
        p->p_sigmask = SCARG(uap, mask) &~ sigcantmask;
        (void) spl0();
        return (0);
}

The latter was used to accomplish unblock (generally by setting the
mask to what was returned by sigblock()).


These are called from machine depenendent code (since the way sys calls
are encoded and their args passed, varies from architecture to
architecture).

Taking the vax as the original, and oldest, sys calls ar handled (big
surprise)
by the syscall() function ... I'm not going to include all of the code for
that,
it is fairly long, and most of what it is doing isn't relevant, but ...

It starts with code that checks the syscall() call orjginated in user mode
(if already in the kernel, it panics), then it works out what the PC was
(the length of the instr) which varies depending upon the sys call number
(how many bytes it takes to represent that) - deals with syscall(0) (the
indirect sys call) and then checks the sys call number is in range.   Once
that is known, it can copy in the expected args (from the sys call table,
which says how many there should be) after which (ignoring some ktrace
noise)
it does:

        rval[0] = 0;
        rval[1] = locr0[R1];
        error = (*callp->sy_call)(u.u_procp, &args, rval);
        if (error == ERESTART)
                pc = opc;
        else if (error != EJUSTRETURN) {
                if (error) {
                        locr0[R0] = error;
                        locr0[PS] |= PSL_C;     /* carry bit */
                } else {
                        locr0[R0] = rval[0];
                        locr0[R1] = rval[1];
                        locr0[PS] &= ~PSL_C;
                }
        }

rval is where the results get passed back, and is init'd t what will be
returned in the normal case (if a syscall has no particular result to
return, and succeeds).   The indirect function call calls one of the
functions above in the cases of interest.   If the system call indicates
it should be restarted, the pc is pucked backwards (opc is the calculated
pc of the system call instruction from earlier).   Otherwise if error
isn' EJUSTRETURN (which a sys call function can return to indicate it has
already set up the result) then if there was an error, R0 (the result)
gets
set to the error number (which userland code will store in errno) and the
carry bit is set to indicate an error occurred, otherwise the results are
moved back to the user's register save area, and the carry bit is cleared.

That's followed by the most relevant part for this discussion:

        /*
         * Reinitialize proc pointer `p' as it may be different
         * if this is a child returning from fork syscall.
         */
        p = u.u_procp;
        if (i = CURSIG(p))
                postsig(i);

(no, not the update of 'p') - CURSIG is a macro:

/*    
 * Determine signal that should be delivered to process p, the current
 * process, 0 if none.  If there is a pending stop signal with default
 * action, the process stops in issignal().
 */     
#define CURSIG(p)                                                       \ 
        (((p)->p_siglist == 0 ||                                        \ 

            ((p)->p_flag & P_TRACED) == 0 &&                            \
            ((p)->p_siglist & ~(p)->p_sigmask) == 0) ?                  \
            0 : issignal(p)) 


It could just be   issignal(p)    but that would mean calling the function
(function calls were relatively expensive on the vax) in cases where it is
obvious that all the function will do is return 0 ... so the macro
extracts
the most likely tests to cause that to happen, and has them evaluated in
line, so we can avoid calling issignal() in a relativelu fast path piece
of
code (executed for every sys call made) if it obviously won't be needed.

In the cases we're interested in, that won't work, and we have to call
issignal() anyway.   If the return from all of this is != 0, it is a
signal number to deliver to the application (and postsig() does that).

I'll get to issignal() soon.   The rest of syscall() is just finishing up,
first we check whether this process should give up the CPU (there was only
one of them) or not - if we do we repeat the

                if (i = CURSIG(p))
                        postsig(i);

after the context switch was made - when we get back to here in that case,
there might have been a signal delivered while we weren't on the CPU, and
we want that to be delivered as well).

Then there's some profiling related code (adding the time spent in the sys
call
to the time spent at the sys call instruction for the purposes of
measuring
where the application is spending its time) and more ktrace stuff.

Then we're done, and the application runs again.

postsig() does the machine independent part of delivering a signal,
handling
it if the action is just to kill the process (if it was to be ignored, it
would have been dropped when the signal was first generated) plus dealing
with sigaction and such - to block signals while the handler is running,
if
needed,  then calls the machine dependent sendsig() if a user signal
handler
needs to be called.

sendsig() just establishes the application stack state, and pc, so it
looks
as if a call to the signal handler was just made, and arranges for when
that
returns (if it does) the correct actions get taken.   None of that is
relevant
right now.

All that is left is issignal() which does the work of selecting which
signal
to deliver.

/*
 * If the current process has received a signal (should be caught or cause
 * termination, should interrupt current syscall), return the signal
number.
 * Stop signals with default action are processed immediately, then
cleared;
 * they aren't returned.  This is checked after each entry to the system
for
 * a syscall or trap (though this can usually be done without calling
issignal
 * by checking the pending signal masks in the CURSIG macro.) The normal
call
 * sequence is  
 *               
 *      while (signum = CURSIG(curproc))
 *              postsig(signum);
 */              
int
issignal(p)             
        register struct proc *p;
{
        register int signum, mask, prop;

        for (;;) {
                mask = p->p_siglist & ~p->p_sigmask;
                if (p->p_flag & P_PPWAIT)   
                        mask &= ~stopsigmask;
                if (mask == 0)          /* no signal to send */
                        return (0);   
                signum = ffs((long)mask);
                mask = sigmask(signum);
                prop = sigprop[signum];
                /*
                 * We should see pending but ignored signals
                 * only if P_TRACED was on when they were posted.
                 */
                if (mask & p->p_sigignore && (p->p_flag & P_TRACED) == 0) {
  
                        p->p_siglist &= ~mask; 
                        continue;
                }
                if (p->p_flag & P_TRACED && (p->p_flag & P_PPWAIT) == 0) {
                        /*
                         * If traced, always stop, and stay
                         * stopped until released by the parent.
                         *
                         * Note that we must clear the pending signal
                         * before we call trace_req since that routine
                         * might cause a fault, calling tsleep and
                         * leading us back here again with the same
signal.
                         * Then we would be deadlocked because the tracer
                         * would still be blocked on the ipc struct from
                         * the initial request.
                         */
                        p->p_xstat = signum;
                        p->p_siglist &= ~mask;
                        psignal(p->p_pptr, SIGCHLD);
                        do {
                                stop(p);
                                mi_switch();
                        } while (!trace_req(p) && p->p_flag & P_TRACED);
                 
                        /*
                         * If parent wants us to take the signal,
                         * then it will leave it in p->p_xstat;
                         * otherwise we just look for signals again.
                         */
                        signum = p->p_xstat;
                        if (signum == 0)
                                continue;
   
                        /*
                         * Put the new signal into p_siglist.  If the
                         * signal is being masked, look for other signals.
                         */
                        mask = sigmask(signum);
                        p->p_siglist |= mask;
                        if (p->p_sigmask & mask)
                                continue;

                        /*
                         * If the traced bit got turned off, go back up
                         * to the top to rescan signals.  This ensures
                         * that p_sig* and ps_sigact are consistent.
                         */
                        if ((p->p_flag & P_TRACED) == 0)
                                continue;
                }

                /*
                 * Decide whether the signal should be returned.
                 * Return the signal's number, or fall through
                 * to clear it from the pending mask.
                 */
                switch ((long)p->p_sigacts->ps_sigact[signum]) {

                case (long)SIG_DFL:
                        /*
                         * Don't take default actions on system processes.
                         */
                        if (p->p_pid <= 1) {
#ifdef DIAGNOSTIC
                                /*
                                 * Are you sure you want to ignore SIGSEGV
                                 * in init? XXX
                                 */
                                printf("Process (pid %d) got signal %d\n",
                                        p->p_pid, signum);
#endif
                                break;          /* == ignore */
                        }
                        /*
                         * If there is a pending stop signal to process
                         * with default action, stop here,
                         * then clear the signal.  However,
                         * if process is member of an orphaned
                         * process group, ignore tty stop signals.
                         */
                        if (prop & SA_STOP) {
                                if (p->p_flag & P_TRACED ||
                                    (p->p_pgrp->pg_jobc == 0 && 
                                    prop & SA_TTYSTOP))
                                        break;  /* == ignore */
                                p->p_xstat = signum;
                                stop(p);
                                if ((p->p_pptr->p_flag & P_NOCLDSTOP) ==
0)
                                        psignal(p->p_pptr, SIGCHLD);
                                mi_switch();
                                break;
                        } else if (prop & SA_IGNORE) {
                                /* 
                                 * Except for SIGCONT, shouldn't get here.
                                 * Default action is to ignore; drop it.  
                                 */     
                                break;          /* == ignore */
                        } else  
                                return (signum);
                        /*NOTREACHED*/

                case (long)SIG_IGN:
                        /*
                         * Masking above should prevent us ever trying
                         * to take action on an ignored signal other
                         * than SIGCONT, unless process is traced.
                         */
                        if ((prop & SA_CONT) == 0 &&
                            (p->p_flag & P_TRACED) == 0)
                                printf("issignal\n");
                        break;          /* == ignore */

                default:
                        /*
                         * This signal has an action, let
                         * postsig() process it.
                         */
                        return (signum);
                }
                p->p_siglist &= ~mask;          /* take the signal! */
        }

That is it.    In that the important lines are

                signum = ffs((long)mask);

where "mask" is the list of signals that are pending, with any that are
blocked removed.   ffs() is "find first set" - which finds the lowest bit
set, and returns its number (so if SIGHUP (#1) is set, and not masked,
that
one comes first).

P_TRACED related to ptrace() - ie: running under a debugger, we can forget
that case here (code that requires P_TRACED to be set should just be
skipped).

The code then (if not ptrace'd) simply ignores the signal if it should be
ignored, and goes and finds another (after removing this one from the list
of pending signals).   That would happen (for example) if a signal was
pending, but the current system call had just instructed it to be ignored.
If it had already been ignored, it wouldn't have been pending (ptraced'd
processed excepted).

The switch() looks at the user's intended disposition of the signal.

Here, the only one we care about is "default" (ie: not SIG_DFL or SIG_IGN)
where we just do

                        return (signum);

and we're done.   The signal returned is the lowest numbered pending one.

It is completely irrelevant whether this is one of the ones that was just
unblocked (nothing in issignal() - which makes the choice) has any idea
what those might have been, and the system calls don't save that anywhere,
other than by the effect that they have on p_sigmask (the list of blocked
signals - manipulated by the system calls, then used here).

When I said in https://austingroupbugs.net/view.php?id=1731#c6292
    As to how implementations work - it has been a while since I got down
    and dirty at the syscall interface

this vintage code is what I was referring to.   I already knew (without
looking) that it worked like this.

I would find it surprising if any implementation was significantly
different.
The basic strategy of signal delivery on syscall exit, except without
masked
signals, dates back to the early Bell Labs releases (at least 5th edition,
the earliest I ever worked with). 

The system call function for sigprocmask in NetBSD is:

/*
 * Manipulate signal mask.
 * Note that we receive new mask, not pointer,
 * and return old mask as return value;
 * the library stub does the rest.
 */
int
sigprocmask(p, uap, retval)
        register struct proc *p;
        struct sigprocmask_args /* {
                syscallarg(int) how;
                syscallarg(sigset_t) mask;
        } */ *uap;
        register_t *retval;
{
        int error = 0;
 
        *retval = p->p_sigmask;
        (void) splhigh();
 
        switch (SCARG(uap, how)) {
        case SIG_BLOCK:
                p->p_sigmask |= SCARG(uap, mask) &~ sigcantmask;
                break;

        case SIG_UNBLOCK:
                p->p_sigmask &= ~SCARG(uap, mask);
                break;

        case SIG_SETMASK:
                p->p_sigmask = SCARG(uap, mask) &~ sigcantmask;
                break;

        default:
                error = EINVAL;
                break;
        }
        (void) spl0();
        return (error);
}

which is rather similar to what was done in the CSRG BSD code.
Nothing at all there about arranging to deliver one of the unblocked
signals in the SIG_UNBLOCK case - just remove bits from sigmask, then
let issignal() do its thing.

The same general thing also happens after traps - but those can't be
altering
the signal mask, but are also far more likely to have generated a signal
to
deliver).

Very similar code exists in NetBSD today, issignal() is still there,
though
ffs() has been renamed to firstsig() (the code now needs to be able to
deal
with more signals than fit as bits in an int).   Otherwise it all looks
quite
similar. 

Issue History 
Date Modified    Username       Field                    Change               
====================================================================== 
2023-05-23 09:43 geoffclare     New Issue                                    
2023-05-23 09:43 geoffclare     Name                      => Geoff Clare     
2023-05-23 09:43 geoffclare     Organization              => The Open Group  
2023-05-23 09:43 geoffclare     Section                   => pthread_sigmask()
2023-05-23 09:43 geoffclare     Page Number               => 1734            
2023-05-23 09:43 geoffclare     Line Number               => 56243           
2023-05-23 09:43 geoffclare     Interp Status             => ---             
2023-05-23 21:08 kre            Note Added: 0006287                          
2023-05-25 08:40 geoffclare     Note Added: 0006288                          
2023-05-25 18:26 kre            Note Added: 0006292                          
2023-05-25 18:32 kre            Note Edited: 0006292                         
2023-05-25 18:44 kre            Note Added: 0006293                          
2023-05-30 11:14 geoffclare     Note Added: 0006294                          
2023-05-31 19:50 kre            Note Added: 0006296                          
======================================================================


  • [1003.1(2016... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
      • Re:... Geoff Clare via austin-group-l at The Open Group
      • Re:... Robert Elz via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group
    • [1003.1... Austin Group Bug Tracker via austin-group-l at The Open Group

Reply via email to