update some documentation

This commit is contained in:
Christopher Faylor 2004-03-06 21:43:57 +00:00
parent f2afcfa616
commit 9514a64249
1 changed files with 122 additions and 82 deletions

View File

@ -1,84 +1,107 @@
Copyright 2001, 2002, 2003 Red Hat Inc., Christopher Faylor Copyright 2001, 2002, 2003, 2004 Red Hat Inc., Christopher Faylor
[note that the following discussion is still incomplete]
[this information is currently out-of-date]
How do signals work? How do signals work?
On process startup, cygwin starts a secondary thread that deals with signals. On process startup, cygwin starts a secondary thread which deals with
This thread contains a loop which blocks waiting for information to show up signals. This thread contains a loop which blocks waiting for
on a pipe whose handle (sendsig) is currently stored in _pinfo (this may change). 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 Communication on the sendsig pipe is via the 'sigpacket' structure.
structure is filled out by the sig_send function with information about the This structure is filled out by the sig_send function with information
signal being sent, such as (as of this writing) the signal number, the about the signal being sent, such as (as of this writing) the signal
originating pid, the originating thread, and the address of the mask to number, the originating pid, the originating thread, and the address of
use (this may change). the mask to use (this may change).
If the signal is not blocked, then the function "sig_handle" is called Any cygwin function which calls a win32 api function is wrapped by the
with the signal number as an argument. This is a fairly straightforward assembly functions "_sigfe" and "_sigbe". These functions maintain a
function. It first checks to see if the signal is special in any way. 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.
A special signal is something like SIGKILL or SIGSTOP. The user has no The cygwin.din function is translated into a standard cygwin.def file by
control over how those signals affect a UNIX process. If a SIGKILL is the perl script "gendef". This function notices exported cygwin
received then sig_handle calls exit_sig to exit the process. If SIGSTOP functions which are labelled as SIGFE and generates a front end assembly
is called then sig_handle calls the regular signal dispatch function file "sigfe.s" which contains the wrapper glue necessary for every
with a special function argument "sig_handle_tty_stop". The signal function to call sigfe prior to actually dispatching to the real cygwin
dispatch function is described below. function. This generated function contains low-level signal related
functions: _sigfe, _sigbe, sigdelayed, sigreturn, longjmp, and setjmp.
An uncaught signal like SIGTERM or SIGHUP will cause the process to exit The signal stack maintained by sigfe/sigbe and friends is a secondary
with the standard UNIX exit values. Uncaught signals like SIGUSR1 are shadow stack. Addresses from this stack are swapped into the "real"
ignored, as on UNIX. stack as needed to control program flow. The intent is that executing
cygwin functions will still see roughly the same stack layout 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.
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 If the signal has an associated signal handler, then the setup_handler
function is eventually called. It is passed the signal, the address of function is eventually called. It is passed the signal, the address of
the handler, and a standard UNIX sigaction structure. The meat of the handler, a standard UNIX sigaction structure, and a pointer to the
signal processing is in setup_handler. 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 setup_handler has a "simple" task. It tries to stop the appropriate
thread and redirect its execution to the signal handler function. thread and either redirect its execution to the signal handler function,
Currently, the "appropriate thread" is only the main thread. Someday flag that a signal has been received (sigwait) or both (sigpause).
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 To accomplish its task, setup_handler first inspects the target thread's
structure. This structure contains information on any not-yet-handled local storage (_cygtls) structure. This structure contains information
signals that may have been set up by a previous call to setup_handler on any not-yet-handled signals that may have been set up by a previous
but not yet dispatched in the main thread. If the sigsave structure call to setup_handler but not yet dispatched in the target thread. If this
seems to be "active", then a "pending" flag is set (see below) and the structure seems to be "active", then setup_handler returns, notifying it's
function returns. Otherwise processing continues. parent via a false value. Otherwise processing continues.
After determining that sigsave is available, setup_handler will take one (For pending signals, the theory is that the signal handler thread will
of two routes, depending on whether the main thread is executing in the be forced to be rerun by having some strategic cygwin function call
cygwin DLL or is currently in "user" code. We'll discuss the cygwin DLL sig_send with a __SIGFLUSH "argument" to it. This causes the signal
case first. handler to rescan the signal array looking for pending signals.)
If sigsave seems to be available, then the frame information for the After determining that it's ok to send a signal, setup_handler will lock
main thread is inspected. This information is set by any cygwin the cygtls stack to ensure that it has complete access. It will then
function that is known to block (such as _read()), usually by calling inspect the thread's 'incyg' element. If this is true, the thread is
'sigframe thisframe (mainthread)' in the cygwin function. This call currently executing a cygwin function. If it is false, the thread is
sets up information about the current stack frame of an executing cygwin unlocked and it is assumed that the thread is executing "user" code.
process. Any function which uses 'sigframe thisframe' should be signal The actions taken by setup_handler differ based on whether the program
aware. It should detect when a signal has arrived and return is executing a cygwin routine or not.
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 If the program is executing a cygwin routine, then the
about the state of the main thread. Cygwin uses the stack frame info interrupt_on_return function is called which sets the address of the
from this structure to insert a call to the assembly language function 'sigdelayed' function is pushed onto the thread's signal stack, and the
'sigdelayed' in place of the main thread's normal return address. So, signal's mask and handler is saved in the tls structure. Then the
when a call to (e.g.) _read returns after detecting a signal, it does 'signal_arrived' event is signalled, as well as any thread-specific wait
not return to its caller. Rather, it returns to sigdelayed. event.
The sigdelayed function saves a lot of state on the stack and sets the Since the sigdelayed function was saved on the thread's signal stack,
signal mask as appropriate for POSIX. It uses information from the when the cygwin functio returns, it will eventually return to the
sigsave structure which has been filled in by interrupt_on_return, as sigdelayed "front end". The sigdelayed function will save a lot of
called by setup_handler. sigdelayed pushes a "call" to the function state on the stack and set the signal mask as appropriate for POSIX.
"sigreturn" on the stack. This will be the return address seen by the It uses information from the _cygtls structure which has been filled in
signal handler. After setting up the return value, modifying the signal by interrupt_setup, as called by setup_handler. sigdelayed pushes a
mask, and saving other information on the stack, sigreturn clears the "call" to the function "sigreturn" on the thread's signal stack. This
sigsave structure (so that setup_handler can use it) and jumps to the 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 signal handler function. And, so a UNIX signal handler function is
emulated. emulated.
@ -88,26 +111,43 @@ original cygwin function. Instead it returns to the previously
mentioned 'sigreturn' assembly language function. mentioned 'sigreturn' assembly language function.
sigreturn resets the process mask to its state prior to calling the 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 signal handler. It checks to see if a cygwin routine has set a special
calls the handler for them now, ensuring that the order of signal "restore this errno on returning from a signal" value and sets errno to
arrival is more or less maintained. It checks to see if a cygwin this, if so. It pops the signal stack, places the new return address on
routine has set a special "restore this errno on returning from a the real stack, restores all of the register values that were in effect
signal" value and sets errno to this, if so. Finally, it restores all when sigdelayed was called, and then returns.
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? Ok. That is more or less how cygwin interrupts a process which is
Well, if you can rewind up to the discussion of sig_handle, we'll return executing a cygwin function. We are almost ready to talk about how
to the situation where sigsave was currently active. In this case, cygwin interrupts user code but there is one more thing to talk about:
setup_handler will set a "pending" flag, will reincrement the appropriate SA_RESTART.
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 UNIX allows some blocking functions to be interrupted by a signal
be forced to be rerun by having some strategic cygwin function call handler and then return to blocking. In cygwin, so far, only
sig_send with a __SIGFLUSH "argument" to it. This causes the signal read/readv() operate in this fashion. To accommodate this behavior,
handler to rescan the signal array looking for pending signals. readv 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, readv will loop. Otherwise
it will 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 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 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 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 called by a high level process like kill() or pthread_kill().
to send a __SIGFLUSH to the signal thread.