Re: sigaction.2: clarification for SA_NODEFER needed

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hello Heinrich,

On 11/11/20 1:42 AM, Heinrich Schuchardt wrote:
> Hello Michael,
> 
> I have been writing a handler for SIGILL and SIGSEGV which restarts the
> program using execv() if an exception occurs. The handler never returns.
> 
>>From the description of SA_NODEFER it was not evident that even if the
> process is restarted with execv() the signal remains masked if
> SA_NODEFER is not set as a flag.
> 
> It think this behavior deserves mentioning on the sigaction.2 manpage, e.g.
> 
> "Do not prevent the signal from being received from within its own
> signal handler. A signal handler call is not terminated by calling
> execv() as the pending signal property is inherited by the new process."
> 
> On the signal.7 manpage there is a paragraph "Signal mask and pending
> signals". Here pending signals are mentioned. There is a sentence
> "Between the time when it is generated and when it is delivered a signal
> is said to be pending."
> 
> To me "delivered" means the instance when the signal handler is called
> and not the instance when the signal handler returns. So said sentence
> should be reworked, e.g.
> 
> "Between the time when it is generated and when the signal handler
> returns a signal is said to be pending."

Your description above seems to conflate two concepts: the signal mask
and pending signals. The sentence that you propose reworking is
correct as it stands.

The sequence of events goes like this:

1. Signal is generated

[At this point the signal is pending, but typically the interval
between this step and the next is typically so brief that the fact
that the signal is pending is unobservable]

2. The kernel delivers the signal to the process:

+ The signal is removed from the set of pending signals.
+ The signal is added to the signal mask (unless SA_NOFER
  was specified)
+ The kernel constructs a frame for the signal handler on the
  user-space stack. The return address in that frame points to a
  small piece of code in user-space called the signal trampoline.
+ The kernel passes control back to the process with execution
  commencing at the start of the signal handler.

3. The signal handler executes.

[At this point, the signal is not pending, but it is present in the
signal mask.]

4. The signal handler returns.

5. Control passes to the signal trampoline, which calls
   sigreturn(2).

6. When sigreturn(2) is called, the kernel once more has control
   and restored various pieces of process state (e.g., the signal
   mask) to the values they had before the signal handler was
   invoked.

7. At completion of the sigreturn(2) system call, the kernel passes control
   back to the user-space program with execution recommencing at the
   point where the main program was interrupted by the signal handler.

Now, if you interrupt things before step 4 (e.g., with exec(),
swapcontext(3), or possibly--depending on how sigsetjmp() was 
called--siglongjmp()), then of course the remaining steps are not
performed.

I've added a few words to the SA_NODEFER description to hopefully
further clarify what it does:

       SA_NODEFER
              Do not prevent the signal from being received  from  within
              its  own signal handler (i.e., do not add the signal to the
              thread's signal mask while the handler is executing).

Probably, the signal(7) manual page could say more about all of this.
What would you think of the following text to be added to that page

   Execution of signal handlers
       Whenever  there is a transition from kernel-mode to user-mode exe‐
       cution (e.g., on return from a system  call  or  scheduling  of  a
       thread onto the CPU), the kernel checks whether there is a pending
       signal for which the process has established a signal handler.  If
       there is such a pending signal, the following steps occur:

       1. The  kernel performs the necessary preparatory steps for execu‐
          tion of the signal handler:

          a) The signal is removed from the set of pending signals.

          b) If the thread has defined an alternate signal  stack  (using
             sigaltstack(2)), then that stack is installed.

          c) Various  pieces  of  signal-related context are saved into a
             "hidden" frame that is created on the stack.  The saved  in‐
             formation includes:

             + the  program  counter  register  (i.e., the address of the
               next instruction in the main program that should  be  exe‐
               cuted when the signal handler returns);

             + the thread's current signal mask;

             + the thread's alternate signal stack settings.

          d) The  thread's  signal  mask is adjusted by adding the signal
             (unless the handler was  established  using  the  SA_NODEFER
             flag)  as  well  as  any  additional  signals  specified  in
             act->sa_mask when sigaction(2) was called.

       2. The kernel constructs a frame for the  signal  handler  on  the
          stack.  Within that frame, the return address points to a piece
          of user-space code called the signal trampoline  (described  in
          sigreturn(2)).

       3. The  kernel  passes control back to user-space, where execution
          commences at the start of the signal handler function.

       4. When the signal handler returns, control passes to  the  signal
          trampoline code.

       5. The  signal  trampoline  calls sigreturn(2), a system call that
          uses the information in the "hidden" stack frame to restore the
          thread's  signal  mask  and  alternate  stack settings to their
          state before the signal handler was called.  Upon completion of
          the  call to sigreturn(2), the kernel transfers control back to
          user space, and the thread recommences execution at  the  point
          where it was interrupted by the signal handler.

       Note  that if the signal handler does not return (e.g., control is
       transferred out of the  handler  using  sigsetjmp(3)  or  swapcon‐
       text(3),  or  the  handler executes a new program with execve(2)),
       then the final step is not performed.  In particular, in such sce‐
       narios it is the programmer's responsibility to restore that state
       of the signal mask, if that is desired.
?

Thanks,

Michael
-- 
Michael Kerrisk
Linux man-pages maintainer; http://www.kernel.org/doc/man-pages/
Linux/UNIX System Programming Training: http://man7.org/training/



[Index of Archives]     [Kernel Documentation]     [Netdev]     [Linux Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux