newlib/winsup/cygwin/how-signals-work.txt

114 lines
6.0 KiB
Plaintext

Copyright 2001, 2002, 2003 Red Hat Inc., Christopher Faylor
[this information is currently out-of-date]
How do signals work?
On process startup, cygwin starts a secondary thread that deals with signals.
This thread contains a loop which blocks waiting for information to show up
on a pipe whose handle (sendsig) is currently stored in _pinfo (this may change).
Communication on the sendsig pipe is via the 'sigelem' structure. This
structure is filled out by the sig_send function with information about the
signal being sent, such as (as of this writing) the signal number, the
originating pid, the originating thread, and the address of the mask to
use (this may change).
If the signal is not blocked, then the function "sig_handle" is called
with the signal number as an argument. This is a fairly straightforward
function. It first checks to see if the signal is special in any way.
A special signal is something like SIGKILL or SIGSTOP. The user has no
control over how those signals affect a UNIX process. If a SIGKILL is
received then sig_handle calls exit_sig to exit the process. If SIGSTOP
is called then sig_handle calls the regular signal dispatch function
with a special function argument "sig_handle_tty_stop". The signal
dispatch function is described below.
An uncaught signal like SIGTERM or SIGHUP will cause the process to exit
with the standard UNIX exit values. Uncaught signals like SIGUSR1 are
ignored, as on UNIX.
If the signal has an associated signal handler, then the setup_handler
function is eventually called. It is passed the signal, the address of
the handler, and a standard UNIX sigaction structure. The meat of
signal processing is in setup_handler.
setup_handler has a "simple" task. It tries to stop the appropriate
thread and redirect its execution to the signal handler function.
Currently, the "appropriate thread" is only the main thread. Someday
we'll have to change this to allow cygwin to interrupt other user
threads.
To accomplish its task, setup_handler first inspects the static sigsave
structure. This structure contains information on any not-yet-handled
signals that may have been set up by a previous call to setup_handler
but not yet dispatched in the main thread. If the sigsave structure
seems to be "active", then a "pending" flag is set (see below) and the
function returns. Otherwise processing continues.
After determining that sigsave is available, setup_handler will take one
of two routes, depending on whether the main thread is executing in the
cygwin DLL or is currently in "user" code. We'll discuss the cygwin DLL
case first.
If sigsave seems to be available, then the frame information for the
main thread is inspected. This information is set by any cygwin
function that is known to block (such as _read()), usually by calling
'sigframe thisframe (mainthread)' in the cygwin function. This call
sets up information about the current stack frame of an executing cygwin
process. Any function which uses 'sigframe thisframe' should be signal
aware. It should detect when a signal has arrived and return
immediately. This method is also used throughout the DLL to ensure
accurate frame info for the executing function. So, you'll see it
sprinkled liberally throughout the DLL, usually at places where
empirical tests have indicated problems finding this address via the
brute force method stack walking method employed in setup_handler.
So, if mainframe is active, that means that we have good information
about the state of the main thread. Cygwin uses the stack frame info
from this structure to insert a call to the assembly language function
'sigdelayed' in place of the main thread's normal return address. So,
when a call to (e.g.) _read returns after detecting a signal, it does
not return to its caller. Rather, it returns to sigdelayed.
The sigdelayed function saves a lot of state on the stack and sets the
signal mask as appropriate for POSIX. It uses information from the
sigsave structure which has been filled in by interrupt_on_return, as
called by setup_handler. sigdelayed pushes a "call" to the function
"sigreturn" on the stack. This will be the return address seen by the
signal handler. After setting up the return value, modifying the signal
mask, and saving other information on the stack, sigreturn clears the
sigsave structure (so that setup_handler can use it) and jumps to the
signal handler function. And, so a UNIX signal handler function is
emulated.
The signal handler function operates as normal for UNIX but, upon
return, it does not go directly back to the return address of the
original cygwin function. Instead it returns to the previously
mentioned 'sigreturn' assembly language function.
sigreturn resets the process mask to its state prior to calling the
signal handler. It checks to see if any new signals have come in and
calls the handler for them now, ensuring that the order of signal
arrival is more or less maintained. It checks to see if a cygwin
routine has set a special "restore this errno on returning from a
signal" value and sets errno to this, if so. Finally, it restores all
of the register values that were in effect when sigdelayed was called.
Ok, you thought I had forgotten about the 'pending' stuff didn't you?
Well, if you can rewind up to the discussion of sig_handle, we'll return
to the situation where sigsave was currently active. In this case,
setup_handler will set a "pending" flag, will reincrement the appropriate
element of the above signal array, and will return 0 to indicate that
the interrupt did not occur. Otherwise setup_handler returns 1.
For pending signals, the theory is that the signal handler thread will
be forced to be rerun by having some strategic cygwin function call
sig_send with a __SIGFLUSH "argument" to it. This causes the signal
handler to rescan the signal array looking for pending signals.
This leads us to the sig_send function. This is the "client side" part
of the signal manipulation process. sig_send is the low-level function
called by a high level process like kill(). You would use sig_send
to send a __SIGFLUSH to the signal thread.