Copyright 2001, 2002, 2003, 2004 Red Hat Inc., 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 function 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 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 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" to it. 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' element. 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 sets the address of the 'sigdelayed' function is pushed onto the thread's signal stack, and the signal's mask and handler is saved in the tls structure. Then 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 functio 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() operate in this fashion. To accommodate this behavior, 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 of the signal manipulation process. sig_send is the low-level function called by a high level process like kill() or pthread_kill().