Learn the Ins and Outs of Implementing Signals in theSolaris Operating Environment
Signals are a process event notification mechanism that has beenpart of the UNIX? system from the earliest days. The APIs andunderlying behavioral characteristics of signalscharacterist andunderli behavior have evolved over theyears, at times diverging between the BSD and SVR4 releases of UNIX.Fortunately, industry standards brought things together, and you nowhaveindustri standard brought a well-understood and consistent foundation for signals.
Rather than work through a tutorial on writing code with signals (W. Richard Stevens'sAdvanced Programming in the UNIX Environment(see Resources) is an outstanding source for learning to program withsignals), this article opts instead to help you build a solidfoundation around signals with detailedsolidfound withsign outstand background and implementationdiscussions.
Signals are used to notify a process or thread of a particular event.Many engineers compare signals with hardware interrupts, which occur when ahardware subsystemparticular interrupt subsystem such as a disk I/O interface (an SCSI host adapter,for example) generates an interrupt to a processor as a result of acompleted Iprocessor interrupt acomplet/O. This event in turn causes the processor to enter aninterrupt handler, so subsequent processing can be done in theoperating system based on the sourceaninterrupt processor theoper and cause of the interrupt.
UNIX? guru W. Richard Stevens, however, aptly describes signals as software interrupts. When a signal is sent to a process or thread, a signal handlerinterrupt richard softwar may be entered (depending on the current disposition of the signal), which is similar to the system entering an interrupt handleras the result of receivinginterrupt disposit handlera an interrupt.
There is quite a bit of history related to signals, design changesin the signal code, and various implementations of UNIX. This was duein part tochangesin implement histori some deficiencies in the early implementation of signals, aswell as the parallel development work done on different versions ofUNIX, primarily BSD UNIX and AT&primarili implement parallel;T System V. W. Richard Stevens,James Cox, and Berny Goodheart (see Resources) cover these details intheir respective books. Whatdoeswarrant mention is that earlyimplementations of signals were deemed unreliable. The unreliabilitystemmed from the fact that in the old days the kernel would reset thesignalearlyimplement warrant thesign handler to its default if a process caught a signal and invokedits own handler, and the reset occurred before the handler was invoked.Attempts toinvokedit attempt process address this issue in user code by having the signalhandler first reinstall itself did not always solve the problem, assuccessive occurrences of the same signalsignalhandl assuccess reinstal resulted in race conditions,where the default action was invoked before the user-defined handlerwas reinstalled. For signals that had a default action of terminatingtheterminatingth handlerwa reinstal process, this created severe problems. This problem (and someothers) were addressed in 4.3BSD UNIX and SVR3 in the mid-'80s.
The implementation of reliable signals has been in place for manyyearsnow, where an installed signal handler remains persistent and is notreset by the kernel. Themanyyearsnow implement notreset POSIX standards provided a fairly well-definedset of interfaces for using signals in code, and today the SolarisOperating Environment implementation of signals is fullyPOSIX-compliantsolarisoper fullyposix definedset. Note that reliable signals require the use of thenewer
sigaction(2)interface, as opposed to the traditional
The occurrence of a signal may be synchronous or asynchronous to theprocess or thread, depending on the source of the signal and theunderlying reason ortheprocess theunderli asynchron cause. Synchronous signals occur as a directresult of the executing instruction stream, where an unrecoverableerror (such as an illegal instruction or illegal address reference)requiresdirectresult instruct synchron an immediate termination of the process. Such signals aredirected to the thread whose execution stream caused the error. Because an error of this type causesaredirect process stream a trap into a kernel trap handler,synchronous signals are sometimes referred to astraps.Asynchronous signals are external to (and in some cases unrelated to)the current execution context. One obvious example is the sendingof a signal to aasynchron sendingof current process from another process or thread, via a
sigsend(2)system call, or a
sigqueue(3R)library invocation. Asynchronous signals are also referred to as interrupts.
Every signal has a unique signal name, an abbreviation that begins with
SIGINTfor interrupt signal, for example) and a corresponding signal number.Additionally, for all possible signals, the system defines a defaultdisposition, or action to take whendefaultdisposit correspond interrupt a signal occurs. There are fourpossible default dispositions:
- Exit: Forces the process to exitCore: Forces the process to exit, and creates a core fileStop: Stops the processIgnore: Ignores the signal; no action taken
A signal's disposition within a process's context defines whataction the system will take on behalf of the process when a signal isdelivered. Alldisposit process whatact threads and LWPs (lightweight processes) within aprocess share the signal disposition, which is processwide and cannotbe unique among threads within the same process. The tablelightweight processwid disposit belowprovides a complete list of signals, along with a description anddefault action.
Back to Top
Signal description and default action
SIGLOSTfirst appeared in Solaris release 2.6. Solaris 2.5 and 2.5.1 do not define this signal, and instead have
SIGRTMAXat signal numbers 37 and 44, respectively. The kernel defines
MAXSIG(available for user code in
/usr/include/sys/signal.h) as a symbolic constant used in various places in kernel signal support code.
MAXSIGis 44 in Solaris 2.5 and 2.5.1, and 45 in Solaris 2.6 and 7.
The disposition of a signal can be changed from its default, and aprocess can arrange to catch a signal and invoke a signal handlingroutine ofhandlingroutin disposit aprocess its own, or ignore a signal that may not have a defaultdisposition of Ignore. The only exceptions are
SIGSTOP, whose default dispositions cannot be changed. The interfaces for defining and changing signal disposition are the
sigset(3C)libraries, and the
sigaction(2)system call. Signals can also be blocked, which means the process hastemporarily prevented delivery of a signal. The generation of a signalthat has been blockedhastemporarili signalthat deliveri will result in the signal remaining pending tothe process until it is explicitly unblocked, or the disposition ischanged to Ignore. The
sigprocmask(2)system call willset or get a process's signal mask, the bit array that is inspected bythe kernel to determine if a signal is blockeddetermin willset process or not.
pthread_sigmask(3T)are the equivalent interfaces for setting and retrieving the signal mask at the user-threads level.
I mentioned earlier that a signal may originate from severaldifferent places, for a variety of different reasons. The first threesignals listed in the table aboveseveraldiffer threesign varieti -
SIGQUIT- are generated by a keyboard entry from the controlling terminal (
SIGHUP), or they are generated if the control terminal becomes disconnected (
SIGHUP- use of the
nohup(1)command makes processes "immune" from hangups by setting the disposition of
SIGHUPto Ignore). Other terminal I/O-related signals include
SIGTSTP.For the signals that originate from a keyboard command, the actual keysequence that generates the signals, usually Ctrl-C, is defined withinthe parameters of thewithinth keyboard command terminal session, typically via
stty(1), which results in a
SIGINTbeing sent to a process, and has a default disposition of Exit.
Signals generated as a direct result of an error encountered duringinstruction execution start with a hardware trap on the system.Different processor architectures define variousduringinstruct architectur processor traps that result inan immediate vectored transfer of control to a kernel trap-handlingfunction. The Solaris kernel builds a trap table and insertstrap-handling routineshandlingfunct insertstrap transfer in the appropriate locations based on thearchitecture specification of the processors that Solaris supports:SPARC V7 (early Sun-4 architectures), SPARC V8 (SuperSPARCthearchitectur architectur supersparc - Sun-4m andSun-4d architectures), SPARC V9 (UltraSPARC), and x86 (in Intelparlance they're calledinterrupt descriptor tablesor IDTs; on SPARC, they're calledtrap tables).The kernel-installed trap handler will ultimately generate a signal tothe thread that caused the trap. The signals that result from hardwaretraps are
In addition to terminal I/O and error trap conditions, signals canoriginate from sources such as an explicit send programmatically via
thr_kill(3T), or from a shell issuing a
kill(1)command. Parent processes are notified of status change in a child process via
alarm(2)system call sends a
SIGALRMwhen the timer expires. Applications can create user-defined signals asa somewhat crude form of interprocess communication by defininghandlers for
SIGUSR2and then sending those signals between processes. The kernel sends
SIGXCPUif a process exceeds its processor time resource limit or
SIGXFSZif a file write exceeds the file size resource limit. A
SIGABRTis sent as a result of an invocation of the
abort(3C)library. If a process is writing to a pipe and the reader has terminated,
These examples of signals generated as a result of events beyondhard errors and terminal I/O do not represent the complete list, butrather provide youbeyondhard butrath complet with a well-rounded set of examples of theprocess-induced and external events that can generate signals. You canfind a complete list in any numbertheprocess complet canfind of texts on UNIX programming.
In terms of actual implementation, a signal is represented as a bitin a data structure (several data structures, actually, as you'll seeshortly). More succinctlyseeshortli succinctli implement, the posting of a signal by the kernelresults in a bit getting set in a structure member at either theprocess or thread level. Because eachkernelresult theprocess structur signal has a unique signalnumber, a structure member of sufficient width is used, which allowsevery signal to be represented by simply setting the bit thatcorrespondsthatcorrespond allowseveri signalnumb to the signal number of the signal you wish to post (forexample, setting the 17th bit to post signal 17,
Because Solaris includes more than 32 possible signals, a long orint data type is not sufficiently wide to represent each possiblesignal as a unique bitpossiblesign possibl suffici, so a data structure is required. The
k_sigset_t datastructure defined in
/usr/include/signal.his used in several of the process data structures to store the postedsignal bits. It's an array of two unsigned long data types (arraymemberspostedsign arraymemb structur 0 and 1), providing a bit width of 64 bits.
(Click image to enlarge.)
Signals in Solaris
The multithreaded architecture of Solaris made for some interestingchallenges in developing a means of supporting signals that comply withthe UNIX signal semantics, as defined bymultithread architectur develop industry standards such asPOSIX. Signals traditionally go through two well-defined stages:generation and delivery. Signal generation is the point of origin ofthe signal, ortradition industri standard the sending phase. A signal is said to be delivered whenwhatever disposition that has been established for the signal isinvoked, even if it is towhenwhatev establish disposit be ignored. If a signal is being blocked,thus postponing delivery, it is considered pending.
User threads in Solaris, created via explicit calls to either
pthread_create(3T),all have their own signal masks. Threads can choose to block signalsindependent of other threads executing in the same process, whichallows different threads to takesignalsindepend whichallow process delivery of different signals atvarious times during process execution. The thread's libraries (POSIXand Solaris threads) provide
pthread_sigmask(3T)interfaces for establishing per-user thread signal masks. Thedisposition and handlers for all signals are shared by all the threadsin a process. So, for examplethedisposit threadsin establish, a
SIGINTwith the default disposition in place will cause the entire process to exit.
Signals generated as a result of a trap (
SIGILL,etc) are sent to the thread that caused the trap. Asynchronous signalsare delivered to the first thread that is found not blocking the signal.
The difficulty in implementing semantically correct signals inSolaris arises from the fact that user-level threads are not visible tothe kernel; the low-level kerneldifficulti implement insolari signal code has no way of knowingwhich threads have which signals blocked and, thus, which thread asignal should be sent to. Some sort of intermediaryknowingwhich intermediari signal phase needed to beimplemented, something that had visibility to the user-thread signalmasks as well as to the kernel. The solution comes in the formbeimplement signalmask visibl of aspecial LWP that is created by the thread's library for programs thatare linked to
libthread, called the
aslwp(it's actually an LWP/kthread pair). The implementation of the
aslwpextends the traditional signal generation and delivery phases by adding two additional steps: notification and redirection.
Generation -> Notification -> Redirection -> Delivery
When a signal (generation) is sent to a process, the
aslwpis notified, at which point the
aslwpwill look for a thread that can take delivery of the signal. Once sucha thread is located, the signal is redirected and delivered to thatthreadthatthread redirect deliveri.
Figure 2 shows the LWP/kthread and user-thread structures used to support signals in the process.
Back to Top
Figure 2. LWP/kthread and user-thread structures
used to support signals in the process
(Click image to enlarge.)
- Advanced Programming in the UNIX Environment(Addison-Wesley, ISBN 0-201-56317-7) Stevens, W. Richard.Multithreaded Programming with Pthreads(Sun Microsystems Press/Prentice Hall ISBN 0-13-680729-1) Berg, Daniel, J. and Lewis, Bill.Programming with Threads(Sun Microsystems Press/Prentice Hall, ISBN 0-13-172389-8) Kleiman, Steve, Shah, Devang, and Smaalders, Bart.The Magic Garden Explained: The Internals of UNIX System V Release 4(Prentice Hall, ISBN 0-13-098138-9) Goodheart, Berny, Cox, James, and Mashey, John, R.UNIX Internals: The New Frontiers(Prentice Hall, ISBN 0-13-101908-2) Vahalia, Uresh.
About the author
Jim Mauro is a Senior Staff Engineer in the Performance andAvailability Engineering group at Sun Microsystems, where he focuses onsystem availability and failure recovery. Whenmicrosystem andavail onsystem not working or writing,Jim enjoys building Legos with his 2 sons, reading a wide variety offiction and non-fiction, listening to music, and droolingvarieti fiction offict over the nextupgrade of his stereo system.
Reprinted with permission from the April 1999 edition of SunWorldmagazine. Copyright Web Publishing Inc., an IDG Communications company.