KCC UN*X SIGNAL SIMULATION This file provides some user-oriented information on how to use the KCC simulation of the Un*x signal mechanism. Unfortunately, there is no single consistent signal mechanism used by all UN*X-type systems. The currently known schemes fall into three basic categories: (1) Traditional: V7, SYS V, ANSI. [signal()] (2) Better: 4.2 BSD / SUN [signal(), sigvec(), sig*()] KCC=> (3) Best: 4.3 BSD [signal(), sigvec(), sig*()] Attributes: Handler reset Signal mask Calls restarted (1) yes no no (2) no yes no KCC=> (3) no yes yes Handler Reset: In traditional Un*x implementations, the call of a signal handler automatically resets that signal's handler to SIG_DFL (default handling, normally termination). The handler has to do invoke signal() again if it wishes to catch additional signals. 4.3BSD and KCC do not do this reset. Signal mask: BSD introduced a signal mask which allows signals to be kept pending until the mask no longer blocks them from being handled. Whenever a handler is called, the corresponding bit in its mask (at least) is always set; thus there is no need to reset the handler to SIG_DFL. This is much more robust as there is no gap during which quickly repeated asynchronous signals can mistakenly kill a process. BSD added a number of new calls to handle this mask. They are sigvec (general-purpose replacement for signal), sigsetmask, sigblock, and sigpause. KCC implements all of these. Call restart: In both (1) and (2) a signal during certain system calls would cause those calls to return -1 with errno set to EINTR. In 4.3BSD this was changed so that normally such calls are restarted automatically when a signal handler returns. A flag bit with sigvec() allows the old action of EINTR to still be taken. A new call, sigreturn(), was added to permit true context restoration. KCC implements this too. More on system call interruption: On Un*x signals may only interrupt the following calls: read(), write() - slow devices only (never DSK:) wait() ioctl() on a slow device (esp. TTY:) any call that locks an inode - open(), creat()? Calls which are not restarted by 4.3BSD if interrupted: pause(), sigpause() KCC permits only the above USYS calls to be interrupted. For all but pause() and sigpause(), the calls will be restarted automatically unless specifically requested by the SV_INTERRUPT bit in a sigvec call. Changing the signal mechanism: To accomodate cases where it is difficult to change the user code (e.g. during initial stages of porting some software), the signal mechanism can be changed from 4.3BSD to 4.2BSD or V7/SYSV by including the following code in the module where "main" is defined: #define _URTSUD_DEFAULT_SIGS _URTSUD_xxx_SIGS #include where "xxx" is one of SYSV, BSD42, or BSD43. For additional information more detailed than that provided in this file, consult the files CODSIG.DOC and SIGVEC.C in the source directory. KCC-supported signals: /--------------- (A)synchronous or (S)ynchronous. | /------------ (P)anic channel or not. | | /--- Only seen for (J)SYS or (U)ser; * = both. Code: [AS][P-][O-][JU*] \------- O means if interrupt PC is user-mode, the PC is that of the offending instruction and not the next one (as for all other cases). Signal PSI Code SIGINT x A--* TTY Interrupt (interactive) SIGQUIT x A--* TTY Quit (interactive) SIGALRM x A--* Alarm Clock (TIMER%) SIGCHLD .ICIFT A--* Inferior fork termination SIGFPE .ICFOV S--U Floating Point overflow SIGFPE .ICAOV S--U Arithmetic overflow SIGSEGV .ICPOV SP-U PDL overflow SIGILL .ICILI SP-* Illegal Instruction SIGBUS .ICIRD SPO* Illegal memory read SIGBUS .ICIWR SPO* Illegal memory write SIGPIPE .ICDAE SPO* Device or data error SIGXFSZ .ICQTA SPO* Quota exceeded SIGXFSZ .ICMSE SPO* Machine resources exhausted SIGT20EOF .ICEOF S--J EOF condition on input SIGT20NXP .ICNXP S-O* Ref to non-ex page If a panic signal occurs during execution of a USYS call then the program will be halted with an error message, even if a handler is defined for that signal. It is possible to ignore panic signals with SIG_IGN although this is unwise. The default action (SIG_DFL) for a particular signal varies. As long as a signal is not set to anything, its action remains whatever the TOPS-20 system action is; panic signals will cause the process to be halted, and all other signals are ignored. If a signal is explicitly set to SIG_DFL then its default action will become whatever the Un*x default action is. See the include file for a listing of all signals with their default actions. A "core" file is never made, since this is unnecessary and unhelpful on TOPS-20/TENEX. Use of SIGINT and SIGQUIT: At startup there are no interrupt characters. That is, t_intrc and t_quitc of the "tchars" ioctl structure are both -1. Simply setting these characters does not cause either to generate signals unless the signal handler has been explicitly set to something by signal(). If explicitly set to SIG_DFL then the signal will terminate the process, since this is the default Un*x action. These interrupt characters can only be some form of control character, including DEL. Signal Handlers: When a signal handler is invoked, it is called with the following arguments: void sighandler(sig, chn, scp) int sig; /* Signal # */ int chn; /* PSI channel # (T20/10X) */ struct sigcontext *scp; /* Pointer to context */ Since more than one PSI channel is mapped into a single signal, the "chn" variable allows the handler to distinguish between them if necessary. The signal context structure is defined in and contains the complete context of the signal, including the interrupt PC and flags, saved ACs, and old signal mask. Code which references this structure is not portable to machines other than the PDP-10, but at this level things are non-portable anyway. Returning from the handler will resume the process where it was interrupted. longjmp() to some other location will work as long as the signal handlers are not nested. WARNING: Any time you write a signal handler routine, you must be aware of what you might be interrupting and how the handler actions may affect the rest of the program. There are too many subtleties to go into more than a few of the important aspects here. For example, while it should be okay to use USYS calls within the handler (the interrupt system ensures that these are treated as atomic), it is almost NEVER okay to invoke any library routine which alters static data, such as "ctime". In particular, none of the storage allocation facilities such as "malloc" should be called, because the program might have been interrupted out of a call to malloc, and the data structures will be in an inconsistent state. It is also risky to use any of the standard I/O library routines, for similar reasons. WARNING: The signal code goes to a great deal of trouble to ensure that if a user program JSYS is interrupted, it can be continued on return from the handler. But the TOPS-20/TENEX PSI scheme is so complex and messed up that it is impossible to guarantee that this will always work. To be COMPLETELY safe, you can use the jsys() facility, which will never permit its JSYS to be interrupted unless the JSYS_OKINT flag is set, and even then will provide you with a definite indication that a signal was handled. Additional notes: What "a/synchronous" means: A SYNCHRONOUS interrupt is one that happens at a specific PC due to the instruction at that location. Typical examples: illegal instruction interrupts (which can include JSYS calls), floating-point exceptions, and illegal memory references. For these types of interrupts the PC is significant and it or the contents it points to may need to be checked to determine what to do, because simply continuing the process at that PC will very likely just generate another such interrupt. An ASYNCHRONOUS interrupt is one that may happen at ANY time, regardless of place; these are generated by events external to the program. Typical examples: TTY input interrupts, timer interrupts. For these, the PC is unimportant except that it should be preserved and restored if the interrupt handler wishes to continue whatever was interrupted. No UN*X C signal handler has the capability of returning from handling a synchronous interrupt. In fact there is no mechanism provided for a signal handler to find out what its return PC is. (it's possible, with trickery, but I've never seen an example). 4.3BSD (as opposed to 4.2 or any other Un*x) now makes this possible by providing the handler with a pointer to a saved-context structure! Note that some signal handlers return to normal code by means of longjmp(); this is particularly true for alarm() handlers. ANSI specifies that longjmp should restore the environment properly even from within a signal handler, but is not required to do anything meaningful if called from a nested signal handler. KCC supports this use of longjmp(). Extensions to sigvec(): For additional flexibility, the "sigvec" structure has been extended to include additional parameters. Some new flags in sv_flags are used to indicate when the additional structure members are significant. The things that can be specified, independently of each other: SV_XINTLEV: ON If handler should run at interrupt level. SV_XASMHAN: ON If handler is special assembler routine (ACs not saved, no args given). Otherwise, normal C handler. SV_XOS: ON If the sigvec structure should be checked for: (1) Exact PSI channel # to use for this signal (0 = existing). (2) What PSI level to use (0 = existing). (3) .TICxx code (plus 1) to ATI% to this channel (0 = none). Not all of the flags work yet: Currently only SV_XINTLEV is implemented. It works to use longjmp() within handlers called with this flag! SV_XASMHAN is not yet used. If added, it will be an error to specify SV_XASMHAN without SV_XINTLEV; that is, if the handler is an assembly routine, then it MUST run at interrupt level. SV_XOS is not yet used. If added, specification of a positive .TIC code will always replace any existing code by the new one, and use of -1 will always clear any existing code. If the value is 0, however, then the meaning depends on whether a channel # was specified. If the channel # was given, 0 is the same as -1. Otherwise, if no channel # was given, then 0 means leave any existing code alone.