From 9514a64249a5960bd201e850ea6c8a1f856507c0 Mon Sep 17 00:00:00 2001 From: Christopher Faylor Date: Sat, 6 Mar 2004 21:43:57 +0000 Subject: [PATCH] update some documentation --- winsup/cygwin/how-signals-work.txt | 226 +++++++++++++++++------------ 1 file changed, 133 insertions(+), 93 deletions(-) diff --git a/winsup/cygwin/how-signals-work.txt b/winsup/cygwin/how-signals-work.txt index 26e5ee1ba..9a2b47853 100644 --- a/winsup/cygwin/how-signals-work.txt +++ b/winsup/cygwin/how-signals-work.txt @@ -1,84 +1,107 @@ -Copyright 2001, 2002, 2003 Red Hat Inc., Christopher Faylor +Copyright 2001, 2002, 2003, 2004 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). +[note that the following discussion is still incomplete] -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. +How do signals work? -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. +On process startup, cygwin starts a secondary thread which 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 '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, and a standard UNIX sigaction structure. The meat of -signal processing is in setup_handler. +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 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 +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. @@ -88,26 +111,43 @@ 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. +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(). You would use sig_send -to send a __SIGFLUSH to the signal thread. +called by a high level process like kill() or pthread_kill(). -- 2.43.5