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

159 lines
8.3 KiB
Plaintext

Contributed by Christopher Faylor
[note that the following discussion is still incomplete]
How do signals work?
On process startup, cygwin starts a secondary thread which deals with
signals. This thread contains a loop which blocks waiting for
information to arrive on a pipe whose handle (sendsig) is currently
stored in _pinfo (this may change).
Communication on the sendsig pipe is via the 'sigpacket' 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).
Any cygwin function which calls a win32 api function is wrapped by the
assembly functions "_sigfe" and "_sigbe". These functions maintain a
cygwin "signal stack" which is used by the signal thread to control
handling of signal interrupts. Cygwin functions which need to be
wrapped by these functions (the majority) are labelled by the SIGFE
option in the file cygwin.din.
The cygwin.din function is translated into a standard cygwin.def file by
the perl script "gendef". This function notices exported cygwin
functions which are labelled as SIGFE and generates a front end assembly
file "sigfe.s" which contains the wrapper glue necessary for every
function to call sigfe prior to actually dispatching to the real cygwin
function. This generated file contains low-level signal related
functions: _sigfe, _sigbe, sigdelayed, sigreturn, longjmp, and setjmp.
The signal stack maintained by sigfe/sigbe and friends is a secondary
shadow stack. Addresses from this stack are swapped into the "real"
stack as needed to control program flow. The intent is that executing
cygwin functions will still see the same stack layout as if they had
been called directly and will be able to retrieve arguments from the
stack but will always return to the _sigbe routine so that any signal
handlers will be properly called.
Upon receipt of a "non-special" (see below) signal, the function
sigpacket::process is called. This function determines what action, if
any, to take on the signal. Possible actions are: Ignore the signal
(e.g., SIGUSR1), terminate the program (SIGKILL, SIGTERM), stop the
program (SIGSTOP, SIGTSTP, etc.), wake up a sigwait or sigwaitinfo in a
targetted thread, or call a signal handler (possibly in a thread). If
no thread information has been sent to sigpacket::process, it determines
the correct thread to use based on various heuristics, as per UNIX. As
per linux, the only time a handler is called in a thread is when there
is some kind of fault like SIGSEGV, SIGILL, etc. Signals sent via the
UNIX kill() function are normally sent to the main thread. Ditto
signals sent as the result of pressing tty keys, like CTRL-C.
Signals which stop a process are handled by a special internal handler:
sig_handle_tty_stop. Some signals (e.g., SIGKILL, SIGSTOP) are
uncatchable, 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, a standard UNIX sigaction structure, and a pointer to the
thread's "_cygtls" information. The meat of signal processing is in
setup_handler.
setup_handler has a "simple" task. It tries to stop the appropriate
thread and either redirect its execution to the signal handler function,
flag that a signal has been received (sigwait) or both (sigpause).
To accomplish its task, setup_handler first inspects the target thread's
local storage (_cygtls) 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 target thread. If this
structure seems to be "active", then setup_handler returns, notifying it's
parent via a false value. Otherwise processing continues.
(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. This causes the signal handler to
rescan the signal array looking for pending signals.)
After determining that it's ok to send a signal, setup_handler will lock
the cygtls stack to ensure that it has complete access. It will then
inspect the thread's 'incyg' boolean. If this is true, the thread is
currently executing a cygwin function. If it is false, the thread is
unlocked and it is assumed that the thread is executing "user" code.
The actions taken by setup_handler differ based on whether the program
is executing a cygwin routine or not.
If the program is executing a cygwin routine, then the
interrupt_on_return function is called which causes the address of the
'sigdelayed' function to be pushed onto the thread's signal stack, and
the signal's mask and handler to be saved in the tls structure. After
performing these operations, the 'signal_arrived' event is signalled, as
well as any thread-specific wait event.
Since the sigdelayed function was saved on the thread's signal stack,
when the cygwin function returns, it will eventually return to the
sigdelayed "front end". The sigdelayed function will save a lot of
state on the stack and set the signal mask as appropriate for POSIX.
It uses information from the _cygtls structure which has been filled in
by interrupt_setup, as called by setup_handler. sigdelayed pushes a
"call" to the function "sigreturn" on the thread's signal stack. This
will be the return address eventually 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 signal number in the
_cygtls 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 a cygwin routine has set a special
"restore this errno on returning from a signal" value and sets errno to
this, if so. It pops the signal stack, places the new return address on
the real stack, restores all of the register values that were in effect
when sigdelayed was called, and then returns.
Ok. That is more or less how cygwin interrupts a process which is
executing a cygwin function. We are almost ready to talk about how
cygwin interrupts user code but there is one more thing to talk about:
SA_RESTART.
UNIX allows some blocking functions to be interrupted by a signal
handler and then return to blocking. In cygwin, so far, only
read/readv() and the wait* functions operate in this fashion. To
accommodate this behavior, a function notices when a signal comes in and
then calls the _cygtls function 'call_signal_handler_now'.
'call_signal_handler_now' emulates the behavior of both sigdelayed and
sigreturn. It sets the appropriate masks and calls the handler,
returning true to the caller if SA_RESTART is active. If SA_RESTART is
active, the function will loop. Otherwise it will typically return -1
and set the errno to EINTR.
Phew. So, now we turn to the case where cygwin needs to interrupt the
program when it is not executing a cygwin function. In this scenario,
we rely on the win32 "SuspendThread" function. Cygwin will suspend the
thread using this function and then inspect the location at which the
thread is executing using the win32 "GetThreadContext" call. In theory,
the program should not be executing in a win32 api since attempts to
suspend a process executing a win32 call can cause disastrous results,
especially on Win9x.
If the process is executing in an unsafe location then setup_handler
will (quickly!) return false as in the case above. Otherwise, the
current location of the thread is pushed on the thread's signal stack
and the thread is redirected to the sigdelayed function via the win32
"SetThreadContext" call. Then the thread is restarted using the win32
"ResumeThread" call and things proceed as per the sigdelayed discussion
above.
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() or pthread_kill().
** More to come **