Chapter 10. Signals¶
Introduction¶
Signals are software interrupts. They provide a way of handling asynchronous events. Most nontrivial application programs need to deal with signals.
POSIX reliable signals¶
Signals have been provided since the early versions of the UNIX System, but the signal model provided with systems such as Version 7 was not reliable. Signals could get lost, and it was difficult for a process to turn off selected signals when executing critical regions of code. Both 4.3BSD and SVR3 made changes to the signal model, adding what are called reliable signals. But the changes made by Berkeley and AT&T were incompatible. Fortunately, POSIX.1 standardized the reliable-signal routines, and that is what we describe here.
This chapter starts with an overview of signals and a description of what each signal is normally used for, then discusses problems with earlier implementations, since it is often important to understand what is wrong with an implementation before seeing how to do things correctly. This chapter contains numerous examples that are not entirely correct and a discussion of the defects.
Signal Concepts¶
-
Every signal has a name. They all begin with the three characters
SIG
. For example:SIGABRT
is the abort signal that is generated when a process calls theabort
function.SIGALRM
is the alarm signal that is generated when the timer set by thealarm
function goes off.
FreeBSD 8.0 supports 32 different signals. Mac OS X 10.6.8 and Linux 3.2.0 each support 31 different signals, whereas Solaris 10 supports 40 different signals. FreeBSD, Linux, and Solaris, support additional application-defined signals introduced to support real-time applications.
-
Signal names are all defined by positive integer constants (the signal number) in the header
<signal.h>
.- Implementations actually define the individual signals in a different header file, but this header file is included by
<signal.h>
. - It bad for the kernel to include header files meant for user-level applications, so if the applications and the kernel both need the same definitions, the information is placed in a kernel header file that is then included by the user-level header file.
<sys/signal.h>
: FreeBSD 8.0 and Mac OS X 10.6.8<bits/signum.h>
: Linux 3.2.0<sys/iso/signal_iso.h>
: Solaris 10
- Implementations actually define the individual signals in a different header file, but this header file is included by
- No signal has a signal number of 0. The
kill
function uses the signal number of 0 for a special case. POSIX.1 calls this value the null signal. - Numerous conditions can generate a signal:
- The terminal-generated signals occur when users press certain terminal keys. Pressing the DELETE key or Control-C on the terminal normally causes the interrupt signal (
SIGINT
) to be generated. - Hardware exceptions generate signals. For example, divide by 0 and invalid memory reference. These conditions are usually detected by the hardware, and the kernel is notified. The kernel then generates the appropriate signal for the process that was running at the time the condition occurred. For example,
SIGSEGV
is generated for a process that executes an invalid memory reference. - The
kill(2)
function allows a process to send any signal to another process or process group, with limitations: we have to be the owner of the process that we’re sending the signal to, or we have to be the superuser. - The
kill(1)
command allows us to send signals to other processes. This program is just an interface to thekill
function. This command is often used to terminate a runaway background process. - Software conditions can generate signals when a process should be notified of various events. For example:
SIGURG
: generated when out-of-band data arrives over a network connection),SIGPIPE
: generated when a process writes to a pipe that has no reader)SIGALRM
: generated when an alarm clock set by the process expires).
- The terminal-generated signals occur when users press certain terminal keys. Pressing the DELETE key or Control-C on the terminal normally causes the interrupt signal (
Signals are classic examples of asynchronous events. They occur at random times to the process. The process can’t simply test a variable (such as errno
) to see whether a signal has occurred; instead, the process has to tell the kernel "if and when this signal occurs, do the following".
Signal dispositions¶
We can tell the kernel to do one of three things when a signal occurs. This is called the disposition of the signal, or the action associated with a signal. (signal(7)
)
- Ignore the signal. Most signals can be ignored, but two signals can never be ignored:
SIGKILL
andSIGSTOP
.- The reason these two signals can’t be ignored is to provide the kernel and the superuser with a surefire way of either killing or stopping any process.
- If we ignore some of the signals that are generated by a hardware exception (such as illegal memory reference or divide by 0), the behavior of the process is undefined.
- Catch the signal. To do this, we tell the kernel to call a function of ours whenever the signal occurs. In our function, we can do whatever we want to handle the condition. For example:
- If we’re writing a command interpreter, when the user generates the interrupt signal at the keyboard, we probably want to return to the main loop of the program, terminating whatever command we were executing for the user.
- If the
SIGCHLD
signal is caught, it means that a child process has terminated, so the signal-catching function can callwaitpid
to fetch the child’s process ID and termination status. - If the process has created temporary files, we may want to write a signal-catching function for the SIGTERM signal (the termination signal that is the default signal sent by the kill command) to clean up the temporary files.
- Note that the two signals
SIGKILL
andSIGSTOP
can’t be caught.
- Let the default action apply. Every signal has a default action. The default action for most signals is to terminate the process.
The signals SIGKILL
and SIGSTOP
cannot be caught, blocked, or ignored. (signal(7)
)
UNIX System signals¶
The following table lists the names of all the signals, an indication of which systems support the signal, and the default action for the signal. The SUS column contains "x" if the signal is defined as part of the base POSIX.1 specification and XSI if it is defined as part of the XSI option. The supported systems are FreeBSD 8.0, Linux 3.2.0, Mac OS X 10.6.8 and Solaris 10.
Name | Description | ISO C | SUS | FreeBSD | Linux | Mac OS X | Solaris | Default action |
---|---|---|---|---|---|---|---|---|
SIGABRT |
abnormal termination (abort ) |
x | x | x | x | x | x | terminate+core |
SIGALRM |
timer expired (alarm ) |
x | x | x | x | x | terminate | |
SIGBUS |
hardware fault | x | x | x | x | x | terminate+core | |
SIGCANCEL |
threads library internal use | x | ignore | |||||
SIGCHLD |
change in status of child | x | x | x | x | x | ignore | |
SIGCONT |
continue stopped process | x | x | x | x | x | continue/ignore | |
SIGEMT |
hardware fault | x | x | x | x | terminate+core | ||
SIGFPE |
arithmetic exception | x | x | x | x | x | x | terminate+core |
SIGFREEZE |
checkpoint freeze | x | ignore | |||||
SIGHUP |
hangup | x | x | x | x | x | terminate | |
SIGILL |
illegal instruction | x | x | x | x | x | x | terminate+core |
SIGINFO |
status request from keyboard | x | x | ignore | ||||
SIGINT |
terminal interrupt character | x | x | x | x | x | x | terminate |
SIGIO |
asynchronous I/O | x | x | x | x | terminate/ignore | ||
SIGIOT |
hardware fault | x | x | x | x | terminate+core | ||
SIGJVM1 |
Java virtual machine internal use | x | ignore | |||||
SIGJVM2 |
Java virtual machine internal use | x | ignore | |||||
SIGKILL |
termination | x | x | x | x | x | terminate | |
SIGLOST |
resource lost | x | terminate | |||||
SIGLWP |
threads library internal use | x | x | ignore | ||||
SIGPIPE |
write to pipe with no readers | x | x | x | x | x | terminate | |
SIGPOLL |
pollable event (poll ) |
x | x | terminate | ||||
SIGPROF |
profiling time alarm (setitimer ) |
x | x | x | x | terminate | ||
SIGPWR |
power fail/restart | x | x | terminate/ignore | ||||
SIGQUIT |
terminal quit character | x | x | x | x | x | terminate+core | |
SIGSEGV |
invalid memory reference | x | x | x | x | x | x | terminate+core |
SIGSTKFLT |
coprocessor stack fault | x | terminate | |||||
SIGSTOP |
stop | x | x | x | x | x | stop process | |
SIGSYS |
invalid system call | XSI | x | x | x | x | terminate+core | |
SIGTERM |
termination | x | x | x | x | x | x | terminate |
SIGTHAW |
checkpoint thaw | x | ignore | |||||
SIGTHR |
threads library internal use | x | x | terminate | ||||
SIGTRAP |
hardware fault | XSI | x | x | x | x | terminate+core | |
SIGTSTP |
terminal stop character | x | x | x | x | x | stop process | |
SIGTTIN |
background read from control tty | x | x | x | x | x | stop process | |
SIGTTOU |
background write to control tty | x | x | x | x | x | stop process | |
SIGURG |
urgent condition (sockets) | x | x | x | x | x | ignore | |
SIGUSR1 |
user-defined signal | x | x | x | x | x | terminate | |
SIGUSR2 |
user-defined signal | x | x | x | x | x | terminate | |
SIGVTALRM |
virtual time alarm (setitimer ) |
XSI | x | x | x | x | terminate | |
SIGWAITING |
threads library internal use | x | ignore | |||||
SIGWINCH |
terminal window size change | x | x | x | x | ignore | ||
SIGXCPU |
CPU limit exceeded (setrlimit ) |
XSI | x | x | x | x | terminate+core/ignore | |
SIGXFSZ |
file size limit exceeded (setrlimit ) |
XSI | x | x | x | x | terminate+core/ignore | |
SIGXRES |
resource control exceeded | x | ignore |
The core file¶
When the default action (in the table above) is labeled "terminate+core", it means that a memory image of the process is left in the file named core
of the current working directory of the process. This file can be used with most UNIX System debuggers to examine the state of the process at the time it terminated.
The name of the core
file varies among implementations. On Mac OS X 10.6.8, the core file is named core.pid, where pid is the ID of the process that received the signal. On Linux 3.2.0, the name is configured through /proc/sys/kernel/core_pattern
. (core(5)
) [p315]
Most implementations leave the core file in the current working directory of the corresponding process; Mac OS X places all core files in /cores
instead.
The core file will not be generated if:
- the process was set-user-ID and the current user is not the owner of the program file,
- the process was set-group-ID and the current user is not the group owner of the file,
- the user does not have permission to write in the current working directory,
- the file already exists and the user does not have permission to write to it,
- the file is too big (see
RLIMIT_CORE
limit in Section 7.11)
The permissions of the core file (assuming that the file doesn’t already exist) are usually user-read and user-write, although Mac OS X sets only user-read.
In the table, the signals with a description of "hardware fault" correspond to implementation-defined hardware faults.
Detailed description of signals¶
SIGABRT
: generated by calling theabort
function. The process terminates abnormally.SIGALRM
:- This signal is generated when a timer set with the
alarm
function expires. - This signal is also generated when an interval timer set by the
setitimer(2)
function expires.
- This signal is generated when a timer set with the
SIGBUS
: indicates an implementation-defined hardware fault. Implementations usually generate this signal on certain types of memory faults.SIGCANCEL
: used internally by the Solaris threads library. It is not meant for general use.SIGCHLD
: Whenever a process terminates or stops, theSIGCHLD
signal is sent to the parent. By default, this signal is ignored, so the parent must catch this signal if it wants to be notified whenever a child’s status changes. The normal action in the signal-catching function is to call one of thewait
functions to fetch the child’s process ID and termination status. [p317]SIGCONT
: this job-control signal is sent to a stopped process when it is continued. The default action is to continue a stopped process, but to ignore the signal if the process wasn’t stopped.SIGEMT
: indicates an implementation-defined hardware fault. Not all platforms support this signal. [p318]SIGFPE
: signals an arithmetic exception, such as divide by 0, floating-point overflow, and so on. The name is derived from "floating-point exception" (Program Error Signals).SIGFREEZE
: defined only by Solaris. [p318]SIGHUP
: this signal is sent to the controlling process (session leader) associated with a controlling terminal if a disconnect is detected by the terminal interface.- This signal is generated for this condition only if the terminal’s
CLOCAL
flag is not set. TheCLOCAL
flag for a terminal is set if the attached terminal is local. The flag tells the terminal driver to ignore all modem status lines. - The session leader that receives this signal may be in the background (Figure 9.7). This differs from the normal terminal-generated signals (interrupt, quit, and suspend), which are always delivered to the foreground process group.
- This signal is also generated if the session leader terminates. In this case, the signal is sent to each process in the foreground process group.
- This signal is commonly used to notify daemon processes (Chapter 13) to reread their configuration files. The reason
SIGHUP
is chosen for this task is that a daemon should not have a controlling terminal and would normally never receive this signal.
- This signal is generated for this condition only if the terminal’s
SIGILL
: indicates that the process has executed an illegal hardware instruction.- 4.3BSD generated this signal from the abort function.
SIGABRT
is now used for this purpose.
- 4.3BSD generated this signal from the abort function.
SIGINFO
: This BSD signal is generated by the terminal driver when we type the status key (often Control-T). This signal is sent to all processes in the foreground process group (Figure 9.9). This signal normally causes status information on processes in the foreground process group to be displayed on the terminal. Linux doesn’t provide support forSIGINFO
.SIGINT
: generated by the terminal driver when we press the interrupt key (often DELETE or Control-C). This signal is sent to all processes in the foreground process group (Figure 9.9). This signal is often used to terminate a runaway program, especially when it’s generating a lot of unwanted output on the screen.SIGIO
: indicates an asynchronous I/O event.SIGIOT
: indicates an implementation-defined hardware fault.SIGABRT
is now used for this purpose. On FreeBSD 8.0, Linux 3.2.0, Mac OS X 10.6.8, and Solaris 10,SIGIOT
is defined to be the same value asSIGABRT
.SIGJVM1
andSIGJVM2
: reserved for use by the Java virtual machine on Solaris.SIGKILL
: one of the two that can’t be caught or ignored. It provides the system administrator with a sure way to kill any process.SIGLOST
: used to notify a process running on a Solaris NFSv4 client system that a lock could not be reacquired during recovery.SIGPIPE
: If we write to a pipeline but the reader has terminated,SIGPIPE
is generated. This signal is also generated when a process writes to a socket of typeSOCK_STREAM
that is no longer connected.SIGPOLL
: This signal is marked obsolescent in SUSv4, so it might be removed in a future version of the standard. It can be generated when a specific event occurs on a pollable device.SIGPROF
: This signal is marked obsolescent in SUSv4, so it might be removed in a future version of the standard. This signal is generated when a profiling interval timer set by thesetitimer(2)
function expires.SIGPWR
: system dependent, mainly used on a system that has an uninterruptible power supply (UPS).- If power fails, the UPS takes over and the software can usually be notified. Nothing needs to be done at this point, as the system continues running on battery power. But if the battery gets low, the software is usually notified again; at this point, it behooves the system to shut everything down. The process that is notified of the low-battery condition sends the
SIGPWR
signal to theinit
process, andinit
handles the system shutdown. - Solaris 10 and some Linux distributions have entries in the
inittab
file for this purpose:powerfail
andpowerwait
(orpowerokwait
). - The default action for
SIGPWR
as either "terminate" or "ignore", which depends on the system. The default on Linux is to terminate the process. On Solaris, the signal is ignored by default.
- If power fails, the UPS takes over and the software can usually be notified. Nothing needs to be done at this point, as the system continues running on battery power. But if the battery gets low, the software is usually notified again; at this point, it behooves the system to shut everything down. The process that is notified of the low-battery condition sends the
SIGQUIT
: generated by the terminal driver when we press the terminal quit key (often Control-backslash). This signal is sent to all processes in the foreground process group (Figure 9.9). This signal not only terminates the foreground process group (as doesSIGINT
), but also generates acore
file.SIGSEGV
: indicates that the process has made an invalid memory reference (which is usually a sign that the program has a bug, such as dereferencing an uninitialized pointer). The name SEGV stands for "segmentation violation".SIGSTKFLT
: This signal is defined only by Linux. It showed up in the earliest versions of Linux, where it was intended to be used for stack faults taken by the math coprocessor. This signal is not generated by the kernel, but remains for backward compatibility.SIGSTOP
: This job-control signal stops a process. It is similar to the interactive stop signal (SIGTSTP
), butSIGSTOP
cannot be caught or ignored.SIGSYS
: indicates an invalid system call. The process executed a machine instruction that the kernel thought was a system call, but the parameter with the instruction that indicates the type of system call was invalid. For example, if you build a program that uses a new system call and you then try to run the same binary on an older version of the operating system where the system call doesn’t exist. [p320]SIGTERM
: the termination signal sent by thekill(1)
command by default. Because it can be caught by applications, usingSIGTERM
gives programs a chance to terminate gracefully by cleaning up before exiting (in contrast toSIGKILL
, which can’t be caught or ignored).SIGTHAW
: defined only by Solaris and used to notify processes that need to take special action when the system resumes operation after being suspended.SIGTHR
: reserved for use by the thread library on FreeBSD. It is defined to have the same value asSIGLWP
.SIGTRAP
: indicates an implementation-defined hardware fault. The signal name comes from the PDP-11 TRAP instruction. Implementations often use this signal to transfer control to a debugger when a breakpoint instruction is executed.SIGTSTP
: This interactive stop signal is generated by the terminal driver when we press the terminal suspend key (often Control-Z). This signal is sent to all processes in the foreground process group (Figure 9.9). [p321]SIGTTIN
: generated by the terminal driver when a process in a background process group tries to read from its controlling terminal. If either of the following case occurs, the signal is not generated; instead, the read operation fails with errno set toEIO
:- The reading process is ignoring or blocking this signal.
- The process group of the reading process is orphaned.
-
SIGTTOU
: generated by the terminal driver when a process in a background process group tries to write to its controlling terminal. Unlike the case with background reads, a process can choose to allow background writes to the controlling terminal. If background writes are not allowed, then like theSIGTTIN
signal, the signal is not generated if either of the following cases occurs; instead, the read operation fails with errno set toEIO
:- The writing process is ignoring or blocking this signal
- The process group of the writing process is orphaned
Regardless of whether background writes are allowed, certain terminal operations (other than writing), including
tcsetattr
,tcsendbreak
,tcdrain
,tcflush
,tcflow
, andtcsetpgrp
can also generate theSIGTTOU
signal. -
SIGURG
: notifies the process that an urgent condition has occurred. It is optionally generated when out-of-band data is received on a network connection. SIGUSR1
andSIGUSR2
: user-defined signals, for use in application programs.SIGVTALRM
: generated when a virtual interval timer set by thesetitimer(2)
function expires.SIGWAITING
: used internally by the Solaris threads library, and is not available for general use.SIGWINCH
: The kernel maintains the size of the window associated with each terminal and pseudo terminal. A process can get and set the window size with theioctl
function. If a process changes the window size from its previous value using theioctl
set-window-size command, the kernel generates theSIGWINCH
signal for the foreground process group.SIGXCPU
: generated if the process exceeds its soft CPU time limit. The default action depends on the operating system. The Single UNIX Specification requires that the default action be to terminate the process abnormally.- Linux 3.2.0 and Solaris 10 support a default action of terminate with a core file
- FreeBSD 8.0 and Mac OS X 10.6.8 support a default action of terminate without generating a core file.
SIGXFSZ
: generated if the process exceeds its soft file size limit. The default action depends on the operating system, similar toSIGXCPU
.SIGXRES
: defined only by Solaris.
signal
Function¶
The simplest interface to the signal features of the UNIX System is the signal function
#include <signal.h> void (*signal(int signo, void (*func)(int)))(int); /* Returns: previous disposition of signal (see following) if OK, SIG_ERR on error */
Implementations derived from UNIX System V support the signal
function, which provides the old unreliable-signal semantics. New applications should not use these unreliable signals. 4.4BSD also provides the signal
function, but it is defined in terms of the sigaction
function, so using it under 4.4BSD provides the newer reliable-signal semantics. Most current systems follow this strategy except Solaris. [p323]
Because the semantics of signal differ among implementations, we must use the sigaction
function instead. We provide an implementation of signal
that uses sigaction
(later this chapter).
Arguments:
- The signo argument is the name of the signal from previous table.
- The value of func one of the following:
- the constant
SIG_IGN
, which tells the system ignore the signal; - the constant
SIG_DFL
, which sets the action associated with the signal to its default value; - the address of a function to be called when the signal occurs, which arranges to "catch" the signal. This function is called either the signal handler or the signal-catching function.
- the constant
The prototype for the signal
function states that the function requires two arguments and returns a pointer to a function that returns nothing (void
):
- The first argument, signo, is an integer.
- The second argument, func, is a pointer to a function that takes a single integer argument and returns nothing.
- The returned function (function whose address is returned as the value of
signal
) takes a single integer argument (the final (int
)).
In plain English, this declaration says that the signal handler is passed a single integer argument (the signal number) and that it returns nothing. When we call signal to establish the signal handler, the second argument is a pointer to the function. The return value from signal
is the pointer to the previous signal handler.
The signal
function prototype can be made much simpler through the use of the following typedef
:
typedef void Sigfunc(int);
Then the prototype becomes:
Sigfunc *signal(int, Sigfunc *);
This typedef
is included in apue.h and is used with the functions in this chapter.
If we examine the system’s header <signal.h>
, we will probably find declarations of the form:
#define SIG_ERR (void (*)())-1 #define SIG_DFL (void (*)())0 #define SIG_IGN (void (*)())1
These constants can be used in place of the "pointer to a function that takes an integer argument and returns nothing", the second argument to signal
, and the return value from signal
. The three values used for these constants need not be −1, 0, and 1. They must be three values that can never be the address of any declarable function. Most UNIX systems use the values shown. (See Doubts and Solutions for details)
Example:
The following code shows a simple signal handler that catches either of the two user-defined signals and prints the signal number.
#include "apue.h" static void sig_usr(int); /* one handler for both signals */ int main(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR1"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("can't catch SIGUSR2"); for ( ; ; ) pause(); } static void sig_usr(int signo) /* argument is signal number */ { if (signo == SIGUSR1) printf("received SIGUSR1\n"); else if (signo == SIGUSR2) printf("received SIGUSR2\n"); else err_dump("received signal %d\n", signo); }
We invoke the program in the background and use the kill(1)
command to send it signals. The term kill in the UNIX System is a misnomer. The kill(1)
command and the kill(2)
function just send a signal to a process or process group. Whether that signal terminates the process depends on which signal is sent and whether the process has arranged to catch the signal.
Result:
$ ./a.out & # start process in background [1] 7216 # job-control shell prints job number and process ID $ kill -USR1 7216 # send it SIGUSR1 received SIGUSR1 $ kill -USR2 7216 # send it SIGUSR2 received SIGUSR2 $ kill 7216 # now send it SIGTERM [1]+ Terminated ./a.out
When we send the SIGTERM
signal, the process is terminated, since it doesn’t catch the signal, and the default action for the signal is termination.
Program Start-Up¶
When a program is executed, the status of all signals is either default or ignore. All signals are set to their default action, unless the process that calls exec
is ignoring the signal. The exec
functions change the disposition of any signals being caught to their default action and leave the status of all other signals alone. The reason is that a signal that is being caught by a process that calls exec
cannot be caught by the same function in the new program, since the address of the signal-catching function in the caller probably has no meaning in the new program file that is executed. [p325]
With a shell that doesn’t support job control, when we execute a process in the background:
cc main.c &
The shell automatically sets the disposition of the interrupt and quit signals in the background process to be ignored. This is done so that if we type the interrupt character, it doesn’t affect the background process. If this weren’t done and we typed the interrupt character, it would terminate not only the foreground process, but also all the background processes.
Many interactive programs that catch these two signals have code that looks like:
void sig_int(int), sig_quit(int); if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, sig_int); if (signal(SIGQUIT, SIG_IGN) != SIG_IGN) signal(SIGQUIT, sig_quit);
Following this approach, the process catches the signal only if the signal is not currently being ignored.
The signal
function has a limitation: we are not able to determine the current disposition of a signal without changing the disposition. The sigaction
function (discussed later in this chapter) allows us to determine a signal’s disposition without changing it.
Process Creation¶
When a process calls fork
, the child inherits the parent’s signal dispositions. Here, since the child starts off with a copy of the parent’s memory image, the address of a signal-catching function has meaning in the child.
Unreliable Signals¶
In earlier versions of the UNIX System, signals were unreliable, which means that signals could get lost: a signal could occur and the process would never know about it. [p326]
One problem with these early versions was that the action for a signal was reset to its default each time the signal occurred. The code that was described usually looked like:
int sig_int(); /* my signal handling function */ ... signal(SIGINT, sig_int); /* establish handler */ ... sig_int() { signal(SIGINT, sig_int); /* reestablish handler for next time */ ... /* process the signal ... */ . }
The problem with this code fragment is that there is a window of time (after the signal has occurred, but before the call to signal
in the signal handler) when the interrupt signal could occur another time. This second signal would cause the default action to occur, which terminates the process. This is one of those conditions that works correctly most of the time, causing us to think that it is correct, when it isn’t.
Another problem with these earlier systems was that the process was unable to turn a signal off when it didn’t want the signal to occur. All the process could do was ignore the signal. There are times when we would like to tell the system "prevent the following signals from interrupting me, but remember if they do occur". The following code catches a signal and sets a flag for the process that indicates that the signal occurred:
int sig_int(); /* my signal handling function */ int sig_int_flag; /* set nonzero when signal occurs */ main() { signal(SIGINT, sig_int); /* establish handler */ ... while (sig_int_flag == 0) pause(); /* go to sleep, waiting for signal */ ... } sig_int() { signal(SIGINT, sig_int); /* reestablish handler for next time */ sig_int_flag = 1; /* set flag for main loop to examine */ }
The process is calling the pause
function to put it to sleep until a signal is caught. When the signal is caught, the signal handler just sets the flag sig_int_flag
to a nonzero value. The process is automatically awakened by the kernel after the signal handler returns, notices that the flag is nonzero, and does whatever it needs to do. But there is a window of time when things can go wrong. If the signal occurs after the test of sig_int_flag
but before the call to pause, the process could go to sleep forever (assuming that the signal is never generated again). This occurrence of the signal is lost.
Interrupted System Calls¶
In earlier UNIX systems, if a process caught a signal while the process was blocked in a "slow" system call, the system call was interrupted. The system call returned an error and errno
was set to EINTR
. This was done under the assumption that since a signal occurred and the process caught it, there is a good chance that something has happened that should wake up the blocked system call.
Slow system calls¶
The system calls are divided into two categories: the "slow" system calls and all the others. The slow system calls are those that can block forever:
- Reads that can block the caller forever if data isn’t present with certain file types (pipes, terminal devices, and network devices)
- Writes that can block the caller forever if the data can’t be accepted immediately by these same file types
- Opens on certain file types that block the caller until some condition occurs (such as a terminal device open waiting until an attached modem answers the phone)
- The
pause
function (which by definition puts the calling process to sleep until a signal is caught) and thewait
function - Certain
ioctl
operations - Some of the interprocess communication functions
The notable exception to these slow system calls is anything related to disk I/O. Although a read or a write of a disk file can block the caller temporarily (while the disk driver queues the request and then the request is executed), unless a hardware error occurs, the I/O operation always returns and unblocks the caller quickly.
Historically, POSIX.1 semantics gave implementations a choice of how to deal with read
s and write
s that have processed partial amounts of data, implementations derived from System V fail the system call, whereas BSD-derived implementations return partial success. With the 2001 version of the POSIX.1 standard, the BSD-style semantics are required. [p328]
The problem with interrupted system calls is that we now have to handle the error return explicitly. Assuming a read operation and assuming that we want to restart the read even if it’s interrupted, the typical code sequence would be:
again: if ((n = read(fd, buf, BUFFSIZE)) < 0) { if (errno == EINTR) goto again; /* just an interrupted system call */ /* handle other errors */ }
Automatic restarts of interrupted system calls¶
Automatic restarting of certain interrupted system calls were introducted since 4.2BSD to prevent applications from having to handle interrupted system calls. The system calls that were automatically restarted are:
- Functions that are interrupted by a signal only if they are operating on a slow device:
ioctl
read
readv
write
writev
- Functions that are always interrupted when a signal is caught.
wait
waitpid
Some applications didn’t want the operation restarted if it was interrupted; 4.3BSD allowed the process to disable this feature on a per-signal basis.
Difference between the signal
and sigaction
functions on restarts¶
POSIX.1 requires an implementation to restart system calls only when the SA_RESTART
flag is in effect for the interrupting signal. This flag is used with the sigaction
function to allow applications to request that interrupted system calls be restarted.
Historically, when using the signal
function to establish a signal handler, implementations varied with respect to how interrupted system calls were handled. System V never restarted system calls by default. BSD, in contrast, restarted them if the calls were interrupted by signals. On FreeBSD 8.0, Linux 3.2.0, and Mac OS X 10.6.8, when signal handlers are installed with the signal
function, interrupted system calls will be restarted. By using our own implementation of the signal
function, we avoid having to deal with these differences. [p329]
One reason 4.2BSD introduced the automatic restart feature is that sometimes we don’t know that the input or output device is a slow device. [p329]
The figure below summarizes the signal functions and their semantics provided by the various implementations.
Later this chapter, we provide our own version of the signal
function that automatically tries to restart interrupted system calls (other than for the SIGALRM
signal), and signal_intr
, that tries to never do the restart.
Reentrant Functions¶
When a signal that is being caught is handled by a process, the normal sequence of instructions being executed by the process is temporarily interrupted by the signal handler. The process then continues executing, but the instructions in the signal handler are now executed. If the signal handler returns (instead of calling exit
or longjmp
), then the normal sequence of instructions that the process was executing when the signal was caught continues executing. This is similar to what happens when a hardware interrupt occurs.
However, in the signal handler, we can’t tell where the process was executing when the signal was caught:
- What if the process was in the middle of allocating additional memory on its heap using
malloc
, and we callmalloc
from the signal handler?- Havoc can result for the process, since
malloc
usually maintains a linked list of all its allocated areas, and it may have been in the middle of changing this list.
- Havoc can result for the process, since
- What if the process was in the middle of a call to a function, such as
getpwnam
(Section 6.2), that stores its result in a static location, and we call the same function from the signal handler?- The information returned to the normal caller can get overwritten with the information returned to the signal handler.
The Single UNIX Specification specifies the functions that are guaranteed to be safe to call from within a signal handler. These functions are reentrant and are called async-signal safe by the SUS. Besides being reentrant, they block any signals during operation if delivery of a signal might cause inconsistencies.
The following table lists these async-signal safe functions, which are reentrant functions that may be called from a signal handler.
abort |
faccessat |
linkat |
select |
socketpair |
accept |
fchmod |
listen |
sem_post |
stat |
access |
fchmodat |
lseek |
send |
symlink |
aio_error |
fchown |
lstat |
sendmsg |
symlinkat |
aio_return |
fchownat |
mkdir |
sendto |
tcdrain |
aio_suspend |
fcntl |
mkdirat |
setgid |
tcflow |
alarm |
fdatasync |
mkfifo |
setpgid |
tcflush |
bind |
fexecve |
mkfifoat |
setsid |
tcgetattr |
cfgetispeed |
fork |
mknod |
setsockopt |
tcgetpgrp |
cfgetospeed |
fstat |
mknodat |
setuid |
tcsendbreak |
cfsetispeed |
fstatat |
open |
shutdown |
tcsetattr |
cfsetospeed |
fsync |
openat |
sigaction |
tcsetpgrp |
chdir |
ftruncate |
pause |
sigaddset |
time |
chmod |
futimens |
pipe |
sigdelset |
timer_getoverrun |
chown |
getegid |
poll |
sigemptyset |
timer_gettime |
clock_gettime |
geteuid |
posix_trace_event |
sigfillset |
timer_settime |
close |
getgid |
pselect |
sigismember |
times |
connect |
getgroups |
raise |
signal |
umask |
creat |
getpeername |
read |
sigpause |
uname |
dup |
getpgrp |
readlink |
sigpending |
unlink |
dup2 |
getpid |
readlinkat |
sigprocmask |
unlinkat |
execl |
getppid |
recv |
sigqueue |
utime |
execle |
getsockname |
recvfrom |
sigset |
utimensat |
execv |
getsockopt |
recvmsg |
sigsuspend |
utimes |
execve |
getuid |
rename |
sleep |
wait |
_Exit |
kill |
renameat |
sockatmark |
waitpid |
_exit |
link |
rmdir |
socket |
write |
Most of the functions that are not included in table above are missing because:
- They are known to use static data structures;
- They call
malloc
orfree
; - They are part of the standard I/O library.
Note when using the functions in the table above:
- Most implementations of the standard I/O library use global data structures in a nonreentrant way.
-
Be aware that even if we call a function listed in the table above from a signal handler, there is only one
errno
variable per thread (recall the discussion of errno and threads in Section 1.7), and we might potentially modify its value.- Consider a signal handler that is invoked right after main has set
errno
. If the signal handler callsread
, for example, this call can change the value oferrno
, wiping out the value that was just stored inmain
.
Therefore, as a general rule, when calling the functions listed in the table above from a signal handler, we should save and restore
errno
. Be aware that a commonly caught signal isSIGCHLD
, and its signal handler usually calls one of the wait functions. All thewait
functions can changeerrno
. - Consider a signal handler that is invoked right after main has set
-
longjmp
(Section 7.10) andsiglongjmp
(Section 10.15) are missing from the table because the signal may have occurred while themain
routine was updating a data structure in a nonreentrant way. This data structure could be left half updated if we callsiglongjmp
instead of returning from the signal handler. If it is going to do such things as update global data structures, while catching signals that causesigsetjmp
to be executed, an application needs to block the signals while updating the data structures.
Example of calling getpwnam
from a signal handler *¶
The code below shows a program that calls the nonreentrant function getpwnam
from a signal handler that is called every second. We use the alarm
function (Section 10.10) here to generate a SIGALRM
signal every second.
#include "apue.h" #include <pwd.h> static void my_alarm(int signo) { struct passwd *rootptr; printf("in signal handler\n"); if ((rootptr = getpwnam("root")) == NULL) err_sys("getpwnam(root) error"); alarm(1); } int main(void) { struct passwd *ptr; signal(SIGALRM, my_alarm); alarm(1); for ( ; ; ) { if ((ptr = getpwnam("sar")) == NULL) err_sys("getpwnam error"); if (strcmp(ptr->pw_name, "sar") != 0) printf("return value corrupted!, pw_name = %s\n", ptr->pw_name); } }
When this program was run, the results were random. Usually, the program would be terminated by a SIGSEGV
signal when the signal handler returned after several iterations. An examination of the core
file showed that the main function had called getpwnam
, but that when getpwnam
called free
, the signal handler interrupted it and called getpwnam
, which in turn called free
. The data structures maintained by malloc
and free
had been corrupted when the signal handler (indirectly) called free
while the main function was also calling free
. Occasionally, the program would run for several seconds before crashing with a SIGSEGV
error. When the main function did run correctly after the signal had been caught, the return value was sometimes corrupted and sometimes fine.
SIGCLD
Semantics¶
Two signals that continually generate confusion are SIGCLD
and SIGCHLD
. The name SIGCLD
(without the H
) is from System V, and this signal has different semantics from the BSD signal, named SIGCHLD
. The POSIX.1 signal is also named SIGCHLD
.
The semantics of the BSD SIGCHLD
signal are normal and its semantics are similar to all other signals. When the signal occurs, the status of a child has changed, and we need to call one of the wait
functions to determine what has happened.
System V, however, has traditionally handled the SIGCLD
signal differently from other signals:
- If the process specifically sets its disposition to
SIG_IGN
, children of the calling process will not generate zombie processes. [p333]- 4.4BSD always generates zombies if
SIGCHLD
is ignored. If we want to avoid zombies, we have towait
for our children.
- 4.4BSD always generates zombies if
- If we set the disposition of
SIGCLD
to be caught, the kernel immediately checks whether any child processes are ready to bewait
ed for and, if so, calls theSIGCLD
handler. [p333-335]- FreeBSD 8.0 and Mac OS X 10.6.8 don’t exhibit this problem, because BSD-based systems generally don’t support historical System V semantics for SIGCLD.
- Linux 3.2.0 also doesn’t exhibit this problem, because it doesn’t call the
SIGCHLD
signal handler when a process arranges to catchSIGCHLD
and child processes are ready to be waited for, even thoughSIGCLD
andSIGCHLD
are defined to be the same value. - Solaris avoids this problem by including extra code in the kernel.
Of the four platforms described in this text, only Linux 3.2.0 and Solaris 10 define SIGCLD
. On these platforms, SIGCLD
is equivalent to SIGCHLD
.
Reliable-Signal Terminology and Semantics¶
This section defines some terms used through the discussion of signals.
- A signal is generated for a process (or sent to a process) when the event that causes the signal occurs. When the signal is generated, the kernel usually sets a flag of some form in the process table. The event could be:
- Hardware exception (e.g., divide by 0),
- Software condition (e.g., an alarm timer expiring),
- Terminal-generated signal,
- A call to the
kill
function.
- A signal is delivered to a process when the action for a signal is taken.
- A signal is pending during the time between its generation and delivery.
-
A process has the option of blocking the delivery of a signal. If a signal that is blocked is generated for a process, and if the action for that signal is either the default action or to catch the signal, then the signal remains pending for the process until the process either:
- unblocks the signal, or
- changes the action to ignore the signal.
The system determines what to do with a blocked signal when the signal is delivered, not when it’s generated. This allows the process to change the action for the signal before it’s delivered. The
sigpending
function (Section 10.13) can be called by a process to determine which signals are blocked and pending.
POSIX.1 allows the system to deliver the signal either once or more than once in case a blocked signal is generated more than once before the process unblocks the signal. If the system delivers the signal more than once, we say that the signals are queued. Most UNIX systems, however, do not queue signals unless they support the real-time extensions to POSIX.1. Instead, the UNIX kernel simply delivers the signal once. [p336]
Section 10.20 discusses queueing signals further.
POSIX.1 does not specify the order in which the signals are delivered to the process. The Rationale for POSIX.1 does suggest, however, that signals related to the current state of the process be delivered before other signals. (SIGSEGV
is one such signal.)
Each process has a signal mask that defines the set of signals currently blocked from delivery to that process. This mask has one bit for each possible signal. If the bit is on for a given signal, that signal is currently blocked. A process can examine and change its current signal mask by calling sigprocmask
(Section 10.12). Since it is possible for the number of signals to exceed the number of bits in an integer, POSIX.1 defines a data type, called sigset_t
, that holds a signal set. The signal mask is stored in one of these signal sets. The five functions that operate on signal sets are described in Section 10.11.
kill
and raise
Functions¶
- The
kill
function sends a signal to a process or a group of processes. - The
raise
function allows a process to send a signal to itself.- The
raise
function was originally defined by ISO C. POSIX.1 includes it to align itself with the ISO C standard, but POSIX.1 extends the specification of raise to deal with threads. Since ISO C does not deal with multiple processes, it could not define a function, such askill
, that requires a process ID argument.
- The
#include <signal.h> int kill(pid_t pid, int signo); int raise(int signo); /* Both return: 0 if OK, −1 on error */
The call:
raise(signo);
is equivalent to the call:
kill(getpid(), signo);
There are four different conditions for the pid argument to kill:
pid
> 0. The signal is sent to the process whose process ID ispid
.pid
== 0 The signal is sent to all processes whose process group ID equals the process group ID of the sender and for which the sender has permission to send the signal.pid
< 0. The signal is sent to all processes whose process group ID equals the absolute value ofpid
and for which the sender has permission to send the signal.pid
== −1. The signal is sent to all processes on the system for which the sender has permission to send the signal.
Note that the term all processes in the four conditions above excludes an implementation-defined set of system processes, including kernel processes and init
(pid 1).
A process needs permission to send a signal to another process:
- The superuser can send a signal to any process.
- For other users, the real or effective user ID of the sender has to equal the real or effective user ID of the receiver. If the implementation supports
_POSIX_SAVED_IDS
, the saved set-user-ID of the receiver is checked instead of its effective user ID. - One special case for the permission testing also exists: if the signal being sent is
SIGCONT
,aprocess can send it to any other process in the same session.
Some other notes on the kill
function:
- POSIX.1 defines signal number 0 as the null signal. If the signo argument is 0, then the normal error checking is performed by
kill
, but no signal is sent. This technique is often used to determine if a specific process still exists. If we send the process the null signal and it doesn’t exist,kill
returns −1 anderrno
is set toESRCH
. Be aware, however, that UNIX systems recycle process IDs after some amount of time, so the existence of a process with a given process ID does not necessarily mean that it’s the process that you think it is. - The test for process existence is not atomic. By the time that
kill
returns the answer to the caller, the process in question might have exited, so the answer is of limited value. - If the call to
kill
causes the signal to be generated for the calling process and if the signal is not blocked, either signo or some other pending, unblocked signal is delivered to the process beforekill
returns. For additional conditions that occur with threads, see Section 12.8.
alarm
and pause
Functions¶
The alarm
function sets a timer that will expire at a specified time in the future. When the timer expires, the SIGALRM
signal is generated. If we ignore or don’t catch this signal, its default action is to terminate the process.
#include <unistd.h> unsigned int alarm(unsigned int seconds); /* Returns: 0 or number of seconds until previously set alarm */
- The seconds value is the number of clock seconds in the future when the signal should be generated. When that time occurs, the signal is generated by the kernel, although additional time could elapse before the process gets control to handle the signal, because of processor scheduling delays.
- There is only one of these alarm clocks per process. If
alarm
is called with a previously registered alarm clock not yet expired, then:- The number of seconds left for previous alarm clock is returned as the value of this function.
- The previous alarm clock is replaced by the new value.
- If a previously registered alarm clock for the process has not yet expired and if the seconds value is 0, the previous alarm clock is canceled. The number of seconds left for that previous alarm clock is still returned as the value of the function.
Although the default action for SIGALRM
is to terminate the process, most processes that use an alarm clock catch this signal, which can perform whatever cleanup is required before terminating if the process wants to terminate. If we intend to catch SIGALRM
, we need to be careful to install its signal handler before calling alarm
. If we call alarm
first and are sent SIGALRM
before we can install the signal handler, our process will terminate.
The pause
function suspends the calling process until a signal is caught.
#include <unistd.h> int pause(void); /* Returns: −1 with errno set to EINTR */
The only time pause
returns is if a signal handler is executed and that handler returns. In that case, pause
returns −1 with errno
set to EINTR
.
sleep1
example¶
Using alarm
and pause
, we can put a process to sleep for a specified amount of time. The following implementation of sleep1
is incomplete and has problems:
#include <signal.h> #include <unistd.h> static void sig_alrm(int signo) { /* nothing to do, just return to wake up the pause */ } unsigned int sleep1(unsigned int seconds) { if (signal(SIGALRM, sig_alrm) == SIG_ERR) return(seconds); alarm(seconds); /* start the timer */ pause(); /* next caught signal wakes us up */ return(alarm(0)); /* turn off timer, return unslept time */ }
This simple implementation has three problems:
- If the caller already has an alarm set, that alarm is erased by the first call to
alarm
. We can correct this by looking atalarm
’s return value:- If the number of seconds until some previously set alarm is less than the argument, then we should wait only until the existing alarm expires.
- If the previously set alarm will go off after ours, then before returning we should reset this alarm to occur at its designated time in the future.
- We have modified the disposition for
SIGALRM
. If we’re writing a function for others to call, we should save the disposition when our function is called and restore it when we’re done. We can correct this by saving the return value fromsignal
and resetting the disposition before our function returns. - There is a race condition between the first call to
alarm
and the call topause
. On a busy system, it’s possible for the alarm to go off and the signal handler to be called before we call pause. If that happens, the caller is suspended forever in the call topause
(assuming that some other signal isn’t caught).
sleep2
example: using setjmp
and longjmp
¶
The following example corrects problem 3 as described above using setjmp
. The problem 3 can also be corrected using sigprocmask
and sigsuspend
, as described in Section 10.19.
#include <setjmp.h> #include <signal.h> #include <unistd.h> static jmp_buf env_alrm; static void sig_alrm(int signo) { longjmp(env_alrm, 1); } unsigned int sleep2(unsigned int seconds) { if (signal(SIGALRM, sig_alrm) == SIG_ERR) return(seconds); if (setjmp(env_alrm) == 0) { alarm(seconds); /* start the timer */ pause(); /* next caught signal wakes us up */ } return(alarm(0)); /* turn off timer, return unslept time */ }
The sleep2
function avoids the race condition. Even if the pause
is never executed, the sleep2
function returns when the SIGALRM
occurs.
sleep2
's interaction with other signals¶
There is another subtle problem with the sleep2
function involving its interaction with other signals. If the SIGALRM
interrupts some other signal handler, then when we call longjmp
, we abort the other signal handler, as shown in the following code:
#include "apue.h" unsigned int sleep2(unsigned int); static void sig_int(int); int main(void) { unsigned int unslept; if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); unslept = sleep2(5); printf("sleep2 returned: %u\n", unslept); exit(0); } static void sig_int(int signo) { int i, j; volatile int k; /* * Tune these loops to run for more than 5 seconds * on whatever system this test program is run. */ printf("\nsig_int starting\n"); for (i = 0; i < 300000; i++) for (j = 0; j < 4000; j++) k += i * j; printf("sig_int finished\n"); }
The loop in the SIGINT
handler was written so that it executes for longer than 5 seconds on one of the systems used by the author. We simply want it to execute longer than the argument to sleep2
. The integer k
is declared as volatile
to prevent an optimizing compiler from discarding the loop.
Run this program and interrupt the sleep by typing the interrupt character:
$ ./a.out ˆC # we type the interrupt character sig_int starting sleep2 returned: 0
The longjmp
from the sleep2
function aborted the other signal handler, sig_int
, even though it wasn’t finished.
The above examples of sleep1
and sleep2
show the pitfalls in dealing naively with signals. The following sections will show ways around all these problems, so we can handle signals reliably, without interfering with other pieces of code.
Implementing a timeout using alarm
¶
A common use for alarm
, in addition to implementing the sleep
function, is to put an upper time limit on operations that can block. For example, if we have a read
operation on a device that can block (slow device, as described in Section 10.5), we might want the read
to time out after some amount of time. The following example reads one line from standard input (with a timeout) and writes it to standard.
#include "apue.h" static void sig_alrm(int); int main(void) { int n; char line[MAXLINE]; if (signal(SIGALRM, sig_alrm) == SIG_ERR) err_sys("signal(SIGALRM) error"); alarm(10); if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0) err_sys("read error"); alarm(0); write(STDOUT_FILENO, line, n); exit(0); } static void sig_alrm(int signo) { /* nothing to do, just return to interrupt the read */ }
Though this code is common in UNIX applications, it has two problems:
- There is a a race condition between the first call to
alarm
and the call toread
, similar to the firstalarm
andpause
example in early this section. If the kernel blocks the process between these two function calls for longer than the alarm period, theread
could block forever, though most operations of this type use a long alarm period (a minute or more) making this unlikely. - If system calls are automatically restarted, the
read
is not interrupted when theSIGALRM
signal handler returns. In this case, the timeout does nothing.
Implementing a timeout with alarm
and longjmp
¶
#include "apue.h" #include <setjmp.h> static void sig_alrm(int); static jmp_buf env_alrm; int main(void) { int n; char line[MAXLINE]; if (signal(SIGALRM, sig_alrm) == SIG_ERR) err_sys("signal(SIGALRM) error"); if (setjmp(env_alrm) != 0) err_quit("read timeout"); alarm(10); if ((n = read(STDIN_FILENO, line, MAXLINE)) < 0) err_sys("read error"); alarm(0); write(STDOUT_FILENO, line, n); exit(0); } static void sig_alrm(int signo) { longjmp(env_alrm, 1); }
This version works as expected, regardless of whether the system restarts interrupted system calls. However, we still have the problem of interactions with other signal handlers, as described previously.
If we want to set a time limit on an I/O operation, we need to use longjmp
, as shown previously, while recognizing its possible interaction with other signal handlers. Another option is to use the select
or poll
functions described in Section 14.4.
Signal Sets¶
A signal set is a data type to represent multiple signals. This data type is used with functions like sigprocmask
to tell the kernel not to allow any of the signals in the set to occur. As mentioned earlier, the number of different signals can exceed the number of bits in an integer, so in general we can’t use an integer to represent the set with one bit per signal.
POSIX.1 defines the data type sigset_t
to contain a signal set and the following five functions to manipulate signal sets.
#include <signal.h> int sigemptyset(sigset_t *set); int sigfillset(sigset_t *set); int sigaddset(sigset_t *set, int signo); int sigdelset(sigset_t *set, int signo); /* All four return: 0 if OK, −1 on error */ int sigismember(const sigset_t *set, int signo); /* Returns: 1 if true, 0 if false, −1 on error */
sigemptyset
initializes the signal set pointed to by set so that all signals are excluded.sigfillset
initializes the signal set so that all signals are included.sigaddset
adds a single signal to an existing set.sigdelset
removes a single signal from a set.
All applications have to call either sigemptyset
or sigfillset
once for each signal set, before using the signal set, because we cannot assume that the C initialization for external and static variables (0) corresponds to the implementation of signal sets on a given system.
In all the functions that take a signal set as an argument, we always pass the address of the signal set as the argument.
Implementation of signal sets¶
If the implementation has fewer signals than bits in an integer,asignal set can be implemented using one bit per signal. This section assumes that an implementation has 31 signals and 32-bit integers. The sigemptyset
function zeros the integer, and the sigfillset
function turns on all the bits in the integer. These two functions can be implemented as macros in the <signal.h>
header:
#define sigemptyset(ptr) (*(ptr) = 0) #define sigfillset(ptr) (*(ptr) = ~(sigset_t)0, 0)
Note that sigfillset
must return 0, in addition to setting all the bits on in the signal set, so we use C’s comma operator, which returns the value after the comma as the value of the expression.
Using this implementation, sigaddset
turns on a single bit and sigdelset
turns off a single bit; sigismember
tests a certain bit. Since no signal is ever numbered 0, we subtract 1 from the signal number to obtain the bit to manipulate.
#include <signal.h> #include <errno.h> /* * <signal.h> usually defines NSIG to include signal number 0. */ #define SIGBAD(signo) ((signo) <= 0 || (signo) >= NSIG) int sigaddset(sigset_t *set, int signo) { if (SIGBAD(signo)) { errno = EINVAL; return(-1); } *set |= 1 << (signo - 1); /* turn bit on */ return(0); } int sigdelset(sigset_t *set, int signo) { if (SIGBAD(signo)) { errno = EINVAL; return(-1); } *set &= ~(1 << (signo - 1)); /* turn bit off */ return(0); } int sigismember(const sigset_t *set, int signo) { if (SIGBAD(signo)) { errno = EINVAL; return(-1); } return((*set & (1 << (signo - 1))) != 0); }
sigprocmask
Function¶
As discussed in Section 10.8, the signal mask of a process is the set of signals currently blocked from delivery to that process. A process can examine its signal mask, change its signal mask, or perform both operations in one step by calling the following function.
#include <signal.h> int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset); /* Returns: 0 if OK, −1 on error */
- If oset is a non-null pointer, the current signal mask for the process is returned through oset.
- If set is a non-null pointer, the how argument indicates how the current signal mask is modified; if set is a null pointer, the signal mask of the process is not changed, and how is ignored.
-
The how argument is one in the following table:
how Description SIG_BLOCK
The new signal mask for the process is the union of its current signal mask and the signal set pointed to by set. That is, set contains the additional signals that we want to block. SIG_UNBLOCK
The new signal mask for the process is the intersection of its current signal mask and the complement of the signal set pointed to by set. That is, set contains the signals that we want to unblock. SIG_SETMASK
The new signal mask for the process is replaced by the value of the signal set pointed to by set.
After calling sigprocmask
, if any unblocked signals are pending, at least one of these signals is delivered to the process before sigprocmask
returns.
Note that the sigprocmask
function is defined only for single-threaded processes. A separate function, discussed in Section 12.8 is provided to manipulate a thread’s signal mask in a multithreaded process.
The following example shows a function that prints the names of the signals in the signal mask of the calling process.
#include "apue.h" #include <errno.h> void pr_mask(const char *str) { sigset_t sigset; int errno_save; errno_save = errno; /* we can be called by signal handlers */ if (sigprocmask(0, NULL, &sigset) < 0) { err_ret("sigprocmask error"); } else { printf("%s", str); if (sigismember(&sigset, SIGINT)) printf(" SIGINT"); if (sigismember(&sigset, SIGQUIT)) printf(" SIGQUIT"); if (sigismember(&sigset, SIGUSR1)) printf(" SIGUSR1"); if (sigismember(&sigset, SIGALRM)) printf(" SIGALRM"); /* remaining signals can go here */ printf("\n"); } errno = errno_save; /* restore errno */ }
sigpending
Function¶
The sigpending
function returns the set of signals that are blocked from delivery and currently pending for the calling process. The set of signals is returned through the set argument.
#include <signal.h> int sigpending(sigset_t *set); /* Returns: 0 if OK, −1 on error */
Example of sigpending
and other signal features¶
The example below shows many of the signal features that have been described.
#include "apue.h" static void sig_quit(int); int main(void) { sigset_t newmask, oldmask, pendmask; if (signal(SIGQUIT, sig_quit) == SIG_ERR) err_sys("can't catch SIGQUIT"); /* * Block SIGQUIT and save current signal mask. */ sigemptyset(&newmask); sigaddset(&newmask, SIGQUIT); if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); sleep(5); /* SIGQUIT here will remain pending */ if (sigpending(&pendmask) < 0) err_sys("sigpending error"); if (sigismember(&pendmask, SIGQUIT)) printf("\nSIGQUIT pending\n"); /* * Restore signal mask which unblocks SIGQUIT. */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); printf("SIGQUIT unblocked\n"); sleep(5); /* SIGQUIT here will terminate with core file */ exit(0); } static void sig_quit(int signo) { printf("caught SIGQUIT\n"); if (signal(SIGQUIT, SIG_DFL) == SIG_ERR) err_sys("can't reset SIGQUIT"); }
Run this program:
$ ./a.out ˆ\ # generate signal once (before 5 seconds are up) SIGQUIT # pending after return from sleep caught SIGQUIT # in signal handler SIGQUIT unblocked # after return from sigprocmask ˆ\Quit(coredump) # generate signal again $ ./a.out ˆ\ˆ\ˆ\ˆ\ˆ\ˆ\ˆ\ˆ\ˆ\ˆ\ # generate signal 10 times (before 5 seconds are up) SIGQUIT pending caught SIGQUIT # signal is generated only once SIGQUIT unblocked ˆ\Quit(coredump) # generate signal again
[p349]
Some notes from this example:
- We saved the old mask when we blocked the signal. To unblock the signal, we did a
SIG_SETMASK
of the old mask. Alternatively, we couldSIG_UNBLOCK
only the signal that we had blocked. Be aware, however, if we write a function that can be called by others and if we need to block a signal in our function, we can’t useSIG_UNBLOCK
to unblock the signal. In this case, we have to useSIG_SETMASK
and restore the signal mask to its prior value, because it’s possible that the caller had specifically blocked this signal before calling our function. We’ll see an example of this in thesystem
function in Section 10.18. - When we run the program the second time, we generate the quit signal ten times while the process is asleep, yet the signal is delivered only once to the process when it’s unblocked. This demonstrates that signals are not queued on this system
sigaction
Function¶
The sigaction
function allows us to examine or modify (or both) the action associated with a particular signal. This function supersedes the signal
function from earlier releases of the UNIX System. Indeed, at the end of this section, we show an implementation of signal
using sigaction
.
#include <signal.h> int sigaction(int signo, const struct sigaction *restrict act, struct sigaction *restrict oact); /* Returns: 0 if OK, −1 on error */
- The argument signo is the signal number whose action we are examining or modifying.
- If the act pointer is non-null, we are modifying the action.
- If the oact pointer is non-null, the system returns the previous action for the signal through the oact pointer
This function uses the following structure:
struct sigaction { void (*sa_handler)(int); /* addr of signal handler, */ /* or SIG_IGN, or SIG_DFL */ sigset_t sa_mask; /* additional signals to block */ int sa_flags; /* signal options, Figure 10.16 */ /* alternate handler */ void (*sa_sigaction)(int, siginfo_t *, void *); };
If the sa_handler
field contains the address of a signal-catching function (rather than SIG_IGN
or SIG_DFL
), then the sa_mask
field specifies a set of signals that are added to the signal mask of the process before the signal-catching function is called. If and when the signal-catching function returns, the signal mask of the process is reset to its previous value. This enables us to block certain signals whenever a signal handler is invoked. The operating system includes the signal being delivered in the signal mask when the handler is invoked. Hence, we are guaranteed that whenever we are processing a given signal, another occurrence of that same signal is blocked until we’re finished processing the first occurrence. Additional occurrences of the same signal are usually not queued (Section 10.8). If the signal occurs five times while it is blocked, when we unblock the signal, the signal-handling function for that signal will usually be invoked only one time.
Once we install an action for a given signal, that action remains installed until we explicitly change it by calling sigaction
. [p350]
The sa_flags
field of the act structure specifies various options for the handling of this signal. The table below details the meaning of these options when set.
The sa_sigaction
field is an alternative signal handler used when the SA_SIGINFO
flag is used with sigaction
. Implementations might use the same storage for both the sa_sigaction
field and the sa_handler
field, so applications can use only one of these fields at a time.
Option flags (sa_flags
) for the handling of each signal:
Option | SUS | FreeBSD | Linux | Mac OS X | Solaris | Description |
---|---|---|---|---|---|---|
SA_INTERRUPT |
x | System calls interrupted by this signal are not automatically restarted (the XSI default for sigaction ). See Section 10.5. |
||||
SA_NOCLDSTOP |
x | x | x | x | x | If signo is SIGCHLD , do not generate this signal when a child process stops (job control). This signal is still generated, of course, when a child terminates (but see the SA_NOCLDWAIT option below). When the XSI option is supported, SIGCHLD won’t be sent when a stopped child continues if this flag is set. |
SA_NOCLDWAIT |
x | x | x | x | x | If signo is SIGCHLD , this option prevents the system from creating zombie processes when children of the calling process terminate. If it subsequently calls wait , the calling process blocks until all its child processes have terminated and then returns −1 with errno set to ECHILD . (Section 10.7) |
SA_NODEFER |
x | x | x | x | x | When this signal is caught, the signal is not automatically blocked by the system while the signal-catching function executes (unless the signal is also included in sa_mask ). Note that this type of operation corresponds to the earlier unreliable signals. |
SA_ONSTACK |
XSI | x | x | x | x | If an alternative stack has been declared with sigaltstack(2) , this signal is delivered to the process on the alternative stack. |
SA_RESETHAND |
x | x | x | x | x | The disposition for this signal is reset to SIG_DFL , and the SA_SIGINFO flag is cleared on entry to the signal-catching function. Note that this type of operation corresponds to the earlier unreliable signals. The disposition for the two signals SIGILL and SIGTRAP can’t be reset automatically, however. Setting this flag can optionally cause sigaction to behave as if SA_NODEFER is also set. |
SA_RESTART |
x | x | x | x | x | System calls interrupted by this signal are automatically restarted. (Section 10.5) |
SA_SIGINFO |
x | x | x | x | x | This option provides additional information to a signal handler: a pointer to a siginfo structure and a pointer to an identifier for the process context. |
Normally, the signal handler is called as:
void handler(int signo);
If the SA_SIGINFO
flag is set, the signal handler is called as:
void handler(int signo, siginfo_t *info, void *context);
The siginfo
structure contains information about why the signal was generated. All POSIX.1-compliant implementations must include at least the si_signo
and si_code
members. Additionally, implementations that are XSI compliant contain at least the following fields:
struct siginfo { int si_signo; /* signal number */ int si_errno; /* if nonzero, errno value from errno.h */ int si_code; /* additional info (depends on signal) */ pid_t si_pid; /* sending process ID */ uid_t si_uid; /* sending process real user ID */ void *si_addr; /* address that caused the fault */ int si_status; /* exit value or signal number */ union sigval si_value; /* application-specific value */ /* possibly other fields also */ };
The sigval
union contains the following fields:
int sival_int; void *sival_ptr;
Applications pass an integer value in si_value.sival_int
or pass a pointer value in si_value.sival_ptr
when delivering signals.
If the signal is SIGCHLD
, then the si_pid
, si_status
, and si_uid
fields will be set.
If the signal is SIGBUS
, SIGILL
, SIGFPE
, or SIGSEGV
, then the si_addr
contains the address responsible for the fault.
The si_errno
field contains the error number corresponding to the condition that caused the signal to be generated.
The table shows values of si_code
for various signals, as defined by the Single UNIX Specification:
The context argument to the signal handler is a typeless pointer that can be cast to a ucontext_t
structure identifying the process context at the time of signal delivery. This structure contains at least the following fields:
ucontext_t *uc_link; /* pointer to context resumed when */ /* this context returns */ sigset_t uc_sigmask; /* signals blocked when this context */ /* is active */ stack_t uc_stack; /* stack used by this context */ mcontext_t uc_mcontext; /* machine-specific representation of */ /* saved context */
The uc_stack
field describes the stack used by the current context. It contains at least the following members:
void *ss_sp; /* stack base or pointer */ size_t ss_size; /* stack size */ int ss_flags; /* flags */
When an implementation supports the real-time signal extensions, signal handlers established with the SA_SIGINFO
flag will result in signals being queued reliably. A separate range of reserved signal numbers is available for real-time application use. Applications can pass information along with the signal by using the sigqueue function (Section 10.20).
Example: signal
Function¶
The following is the implementation of the signal
function using sigaction
. [p354]
#include "apue.h" /* Reliable version of signal(), using POSIX sigaction(). */ Sigfunc * signal(int signo, Sigfunc *func) { struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; if (signo == SIGALRM) { #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif } else { act.sa_flags |= SA_RESTART; } if (sigaction(signo, &act, &oact) < 0) return(SIG_ERR); return(oact.sa_handler); }
- We must use
sigemptyset
to initialize thesa_mask
member of the structure since we’re not guaranteed thatact.sa_mask = 0
does the same thing. - We intentionally set the
SA_RESTART
flag for all signals other thanSIGALRM
, so that any system call interrupted by these other signals will be automatically restarted. The reason we don’t wantSIGALRM
restarted is to allow us to set a timeout for I/O operations. (Figure 10.10) - Some older systems, such as SunOS, define the
SA_INTERRUPT
flag. These systems restart interrupted system calls by default, so specifying this flag causes system calls to be interrupted. Linux defines theSA_INTERRUPT
flag for compatibility with applications that use it, but by default does not restart system calls when the signal handler is installed withsigaction
. The Single UNIX Specification specifies that thesigaction
function not restart interrupted system calls unless theSA_RESTART
flag is specified.
Example: signal_intr
Function¶
The following code shows a version of the signal
function that tries to prevent any interrupted system calls from being restarted.
#include "apue.h" Sigfunc * signal_intr(int signo, Sigfunc *func) { struct sigaction act, oact; act.sa_handler = func; sigemptyset(&act.sa_mask); act.sa_flags = 0; #ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; #endif if (sigaction(signo, &act, &oact) < 0) return(SIG_ERR); return(oact.sa_handler); }
For improved portability, we specify the SA_INTERRUPT
flag, if defined by the system, to prevent interrupted system calls from being restarted.
sigsetjmp
and siglongjmp
Functions¶
setjmp
and longjmp
functions (Section 7.10) can be used for nonlocal branching. The longjmp
function is often called from a signal handler to return to the main loop of a program, instead of returning from the handler. (Figure 10.8 and Figure 10.11).
However, there is a problem in calling longjmp
. When a signal is caught, the signal-catching function is entered, with the current signal automatically being added to the signal mask of the process. This prevents subsequent occurrences of that signal from interrupting the signal handler. If we longjmp
out of the signal handler, what happens to the signal mask for the process depends on the platform:
- Under FreeBSD 8.0 and Mac OS X 10.6.8,
setjmp
andlongjmp
save and restore the signal mask. - Linux 3.2.0 and Solaris 10, however, do not save and restore the signal mask, although Linux supports an option to provide BSD behavior.
- FreeBSD and Mac OS X provide the functions
_setjmp
and_longjmp
, which do not save and restore the signal mask.
To allow either form of behavior, POSIX.1 does not specify the effect of setjmp
and longjmp
on signal masks. Instead, two new functions, sigsetjmp
and siglongjmp
, are defined by POSIX.1. These two functions should always be used when branching from a signal handler.
#include <setjmp.h> int sigsetjmp(sigjmp_buf env, int savemask); /* Returns: 0 if called directly, nonzero if returning from a call to siglongjmp */ void siglongjmp(sigjmp_buf env, int val);
The only difference between these functions and the setjmp
and longjmp
functions is that sigsetjmp
has an additional argument. If savemask is nonzero, then sigsetjmp
also saves the current signal mask of the process in env. When siglongjmp
is called, if the env argument was saved by a call to sigsetjmp
with a nonzero savemask, then siglongjmp
restores the saved signal mask.
#include "apue.h" #include <setjmp.h> #include <time.h> static void sig_usr1(int); static void sig_alrm(int); static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump; int main(void) { if (signal(SIGUSR1, sig_usr1) == SIG_ERR) err_sys("signal(SIGUSR1) error"); if (signal(SIGALRM, sig_alrm) == SIG_ERR) err_sys("signal(SIGALRM) error"); pr_mask("starting main: "); /* {Prog prmask} */ if (sigsetjmp(jmpbuf, 1)) { pr_mask("ending main: "); exit(0); } canjump = 1; /* now sigsetjmp() is OK */ for ( ; ; ) pause(); } static void sig_usr1(int signo) { time_t starttime; if (canjump == 0) return; /* unexpected signal, ignore */ pr_mask("starting sig_usr1: "); alarm(3); /* SIGALRM in 3 seconds */ starttime = time(NULL); for ( ; ; ) /* busy wait for 5 seconds */ if (time(NULL) > starttime + 5) break; pr_mask("finishing sig_usr1: "); canjump = 0; siglongjmp(jmpbuf, 1); /* jump back to main, don't return */ } static void sig_alrm(int signo) { pr_mask("in sig_alrm: "); }
- We set the variable
canjump
to a nonzero value only after we’ve calledsigsetjmp
. This variable is examined in the signal handler, andsiglongjmp
is called only if the flagcanjump
is nonzero. This technique provides protection against the signal handler being called at some earlier or later time, when the jump buffer hasn’t been initialized bysigsetjmp
. This technique should be used wheneversiglongjmp
is called from a signal handler, but is not required with longjmp in normal C code. Since a signal can occur at any time, therefore we need the added protection in a signal handler. - We use the data type
sig_atomic_t
, which is defined by the ISO C standard to be the type of variable that can be written without being interrupted.- This means that a variable of type
sig_atomic_t
should not extend across page boundaries on a system with virtual memory and can be accessed with a single machine instruction, for example. - We always include the ISO type qualifier
volatile
for these data types as well, since the variable is being accessed by two different threads of control: themain
function and the asynchronously executing signal handler.
- This means that a variable of type
We can divide the following figure into three parts:
- Left part (corresponding to
main
) - Center part (
sig_usr1
) - Right part (
sig_alrm
).
While the process is executing in the left part, its signal mask is 0 (no signals are blocked). While executing in the center part, its signal mask is SIGUSR1
. While executing in the right part, its signal mask is SIGUSR1
|SIGALRM
.
The output of the program:
$ ./a.out & # start process in background starting main: [1] 531 # the job-control shell prints its process ID $ kill -USR1 531 # send the process SIGUSR1 starting sig_usr1: SIGUSR1 $ in sig_alrm: SIGUSR1 SIGALRM finishing sig_usr1: SIGUSR1 ending main: # just press RETURN [1] + Done ./a.out &
The output is what we expect: when a signal handler is invoked, the signal being caught is added to the current signal mask of the process. The original mask is restored when the signal handler returns. Also, siglongjmp
restores the signal mask that was saved by sigsetjmp
.
sigsuspend
Function¶
We have seen how we can change the signal mask for a process to block and unblock selected signals. We can use this technique to protect critical regions of code that we don’t want interrupted by a signal. But what if we want to unblock a signal and then pause
, waiting for the previously blocked signal to occur? Assuming that the signal is SIGINT
, the incorrect way to do this is:
sigset_t newmask, oldmask; sigemptyset(&newmask); sigaddset(&newmask, SIGINT); /* block SIGINT and save current signal mask */ if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); /* critical region of code */ /* restore signal mask, which unblocks SIGINT */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); /* window is open */ pause(); /* wait for signal to occur */ /* continue processing */
If the signal is sent to the process while it is blocked, the signal delivery will be deferred until the signal is unblocked. To the application, this can look as if the signal occurs between the unblocking and the pause
(depending on how the kernel implements signals). If this happens, or if the signal does occur between the unblocking and the pause
, we have a problem. Any occurrence of the signal in this window of time is lost, in the sense that we might not see the signal again, in which case the pause
will block indefinitely. This is another problem with the earlier unreliable signals.
To correct this problem, we need a way to both restore the signal mask and put the process to sleep in a single atomic operation. This feature is provided by the sigsuspend
function.
#include <signal.h> int sigsuspend(const sigset_t *sigmask); /* Returns: −1 with errno set to EINTR */
The signal mask of the process is set to the value pointed to by sigmask. Then the process is suspended until a signal is caught or until a signal occurs that terminates the process. If a signal is caught and if the signal handler returns, then sigsuspend
returns, and the signal mask of the process is set to its value before the call to sigsuspend
.
Example of sigsuspend
to protect a critial region¶
The following code shows the correct way to protect a critical region of code from a specific signal.
#include "apue.h" static void sig_int(int); int main(void) { sigset_t newmask, oldmask, waitmask; pr_mask("program start: "); if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); sigemptyset(&waitmask); sigaddset(&waitmask, SIGUSR1); sigemptyset(&newmask); sigaddset(&newmask, SIGINT); /* * Block SIGINT and save current signal mask. */ if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); /* * Critical region of code. */ pr_mask("in critical region: "); /* * Pause, allowing all signals except SIGUSR1. */ if (sigsuspend(&waitmask) != -1) err_sys("sigsuspend error"); pr_mask("after return from sigsuspend: "); /* * Reset signal mask which unblocks SIGINT. */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); /* * And continue processing ... */ pr_mask("program exit: "); exit(0); } static void sig_int(int signo) { pr_mask("\nin sig_int: "); }
When sigsuspend
returns, it sets the signal mask to its value before the cal SIGINT signal will be blocked, so we restore the signal mask to the value
that we saved earlier (oldmask).
Running the program produces the following output:
$ ./a.out program start: in critical region: SIGINT ˆC # type the interrupt character in sig_int: SIGINT SIGUSR1 after return from sigsuspend: SIGINT program exit:
Example of sigsuspend
to wait for a signal handler to set a global variable¶
In the following program, we catch both the interrupt signal and the quit signal, but want to wake up the main routine only when the quit signal is caught.
#include "apue.h" volatile sig_atomic_t quitflag; /* set nonzero by signal handler */ static void sig_int(int signo) /* one signal handler for SIGINT and SIGQUIT */ { if (signo == SIGINT) printf("\ninterrupt\n"); else if (signo == SIGQUIT) quitflag = 1; /* set flag for main loop */ } int main(void) { sigset_t newmask, oldmask, zeromask; if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); if (signal(SIGQUIT, sig_int) == SIG_ERR) err_sys("signal(SIGQUIT) error"); sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask, SIGQUIT); /* * Block SIGQUIT and save current signal mask. */ if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); while (quitflag == 0) sigsuspend(&zeromask); /* * SIGQUIT has been caught and is now blocked; do whatever. */ quitflag = 0; /* * Reset signal mask which unblocks SIGQUIT. */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); exit(0); }
Sample output from this program is:
$ ./a.out ˆC # type the interrupt character interrupt ˆC # type the interrupt character again interrupt ˆC # and again interrupt ˆ\ $ # now terminate with the quit character
Example of signals that synchronize a parent and child¶
This example shows how signals can be used to synchronize a parent and child. The following example shows implementations of the five routines TELL_WAIT
, TELL_PARENT
, TELL_CHILD
, WAIT_PARENT
, and WAIT_CHILD
from Section 8.9.
#include "apue.h" static volatile sig_atomic_t sigflag; /* set nonzero by sig handler */ static sigset_t newmask, oldmask, zeromask; static void sig_usr(int signo) /* one signal handler for SIGUSR1 and SIGUSR2 */ { sigflag = 1; } void TELL_WAIT(void) { if (signal(SIGUSR1, sig_usr) == SIG_ERR) err_sys("signal(SIGUSR1) error"); if (signal(SIGUSR2, sig_usr) == SIG_ERR) err_sys("signal(SIGUSR2) error"); sigemptyset(&zeromask); sigemptyset(&newmask); sigaddset(&newmask, SIGUSR1); sigaddset(&newmask, SIGUSR2); /* Block SIGUSR1 and SIGUSR2, and save current signal mask */ if (sigprocmask(SIG_BLOCK, &newmask, &oldmask) < 0) err_sys("SIG_BLOCK error"); } void TELL_PARENT(pid_t pid) { kill(pid, SIGUSR2); /* tell parent we're done */ } void WAIT_PARENT(void) { while (sigflag == 0) sigsuspend(&zeromask); /* and wait for parent */ sigflag = 0; /* Reset signal mask to original value */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); } void TELL_CHILD(pid_t pid) { kill(pid, SIGUSR1); /* tell child we're done */ } void WAIT_CHILD(void) { while (sigflag == 0) sigsuspend(&zeromask); /* and wait for child */ sigflag = 0; /* Reset signal mask to original value */ if (sigprocmask(SIG_SETMASK, &oldmask, NULL) < 0) err_sys("SIG_SETMASK error"); }
In the example, two user-defined signals are used: SIGUSR1
is sent by the parent to the child, and SIGUSR2
is sent by the child to the parent.
The sigsuspend
function is fine if we want to go to sleep while we’re waiting for a signal to occur. If we want to call other system functions while we’re waiting, the only solution is to use multiple threads and dedicate a separate thread to handling signals (Section 12.8).
Without using threads, the best we can do is to set a global variable in the signal handler when the signal occurs. For example, if we catch both SIGINT
and SIGALRM
and install the signal handlers using the signal_intr
function, the signals will interrupt any slow system call that is blocked. The signals are most likely to occur when we’re blocked in a call to the read
function waiting for input from a slow device. (This is especially true for SIGALRM
, since we set the alarm clock to prevent us from waiting forever for input.) The code to handle this looks similar to the following:
if (intr_flag) /* flag set by our SIGINT handler */ handle_intr(); if (alrm_flag) /* flag set by our SIGALRM handler */ handle_alrm(); /* signals occurring in here are lost */ while (read( ... ) < 0) { if (errno == EINTR) { if (alrm_flag) handle_alrm(); else if (intr_flag) handle_intr(); } else { /* some other error */ } } else if (n == 0) { /* end of file */ } else { /* process input */ }
We test each of the global flags before calling read
and again if read
returns an interrupted system call error. The problem occurs if either signal is caught between the first two if statements and the subsequent call to read. Signals occurring in here are lost, as indicated by the code comment. The signal handlers are called, and they set the appropriate global variable, but the read
never returns (unless some data is ready to be read).
What we would like to be able to do is the following sequence of steps, in order.
- Block
SIGINT
andSIGALRM
. - Test the two global variables to see whether either signal has occurred and, if so, handle the condition.
- Call
read
(or any other system function) and unblock the two signals, as an atomic operation.
The sigsuspend
function helps us only if step 3 is a pause
operation.
abort
Function¶
The abort
function causes abnormal program termination.
#include <stdlib.h> void abort(void); /* This function never returns */
The abort
function sends the SIGABRT
signal to the caller. Processes should not ignore this signal. ISO C states that calling abort will deliver an unsuccessful termination notification to the host environment by calling raise(SIGABRT)
.
[p365]
ISO C:
- If the signal is caught and the signal handler returns,
abort
still doesn’t return to its caller. If this signal is caught, the only way the signal handler can’t return is if it callsexit
,_exit
,_Exit
,longjmp
, orsiglongjmp
. - It is up to the implementation as to whether output streams are flushed and whether temporary files (Section 5.13) are deleted.
POSIX.1:
abort
overrides the blocking or ignoring of the signal by the process.- The intent of letting the process catch the
SIGABRT
is to allow it to perform any cleanup that it wants to do before the process terminates. - If the process doesn’t terminate itself from this signal handler, when the signal handler returns,
abort
terminates the process. - An implementation is allowed to call
fclose
on open standard I/O streams before terminating if the call to abort terminates the process.
Since most UNIX System implementations of tmpfile
call unlink
immediately after creating the file, the ISO C warning about temporary files does not usually concern us. [p366]
The following is an implementation of the abort
function as specified by POSIX.1:
#include <signal.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> void abort(void) /* POSIX-style abort() function */ { sigset_t mask; struct sigaction action; /* Caller can't ignore SIGABRT, if so reset to default */ sigaction(SIGABRT, NULL, &action); if (action.sa_handler == SIG_IGN) { action.sa_handler = SIG_DFL; sigaction(SIGABRT, &action, NULL); } if (action.sa_handler == SIG_DFL) fflush(NULL); /* flush all open stdio streams */ /* Caller can't block SIGABRT; make sure it's unblocked */ sigfillset(&mask); sigdelset(&mask, SIGABRT); /* mask has only SIGABRT turned off */ sigprocmask(SIG_SETMASK, &mask, NULL); kill(getpid(), SIGABRT); /* send the signal */ /* If we're here, process caught SIGABRT and returned */ fflush(NULL); /* flush all open stdio streams */ action.sa_handler = SIG_DFL; sigaction(SIGABRT, &action, NULL); /* reset to default */ sigprocmask(SIG_SETMASK, &mask, NULL); /* just in case ... */ kill(getpid(), SIGABRT); /* and one more time */ exit(1); /* this should never be executed ... */ }
- This implementation of
abort
first check whether the default action will occur; if so, it flush all the standard I/O streams. This is not equivalent to callingfclose
on all the open streams (since it just flushes them and doesn’t close them), but when the process terminates, the system closes all open files. - If the process catches the signal and returns, we flush all the streams again, since the process could have generated more output.
- The only condition we don’t handle is the case where the process catches the signal and calls
_exit
or_Exit
. In this case, any unflushed standard I/O buffers in memory are discarded. (We assume that a caller that does this doesn’t want the buffers flushed.) - As mentioned in Section 10.9, if calling
kill
causes the signal to be generated for the caller, and if the signal is not blocked, then the signal (or some other pending, unlocked signal) is delivered to the process beforekill
returns. We block all signals exceptSIGABRT
, so we know that if the call tokill
returns, the process caught the signal and the signal handler returned.
system
Function¶
Section 8.13 showed an implementation of the system
function, which did not do any signal handling. POSIX.1 requires that system ignore SIGINT
and SIGQUIT
and block SIGCHLD
. Before showing a version that handles these signals correctly, let’s see why we need to worry about signal handling.
Example of system
invoking ed
editor¶
The program (Figure 10.26) shown below uses the system
function from Section 8.13 to invoke the ed(1)
editor. It is an interactive program that catches the interrupt and quit signals. If ed
is invoked from a shell and type the interrupt character, it catches the interrupt signal and prints a question mark. The ed
program also sets the disposition of the quit signal so that it is ignored.
#include "apue.h" static void sig_int(int signo) { printf("caught SIGINT\n"); } static void sig_chld(int signo) { printf("caught SIGCHLD\n"); } int main(void) { if (signal(SIGINT, sig_int) == SIG_ERR) err_sys("signal(SIGINT) error"); if (signal(SIGCHLD, sig_chld) == SIG_ERR) err_sys("signal(SIGCHLD) error"); if (system("/bin/ed") < 0) err_sys("system() error"); exit(0); }
This program catches both SIGINT
and SIGCHLD
. If we invoke the program, we get:
$ ./a.out a # append text to the editor’s buffer Here is one line of text . # period on a line by itself stops append mode 1,$p # print first through last lines of buffer to see what’s there Here is one line of text w temp.foo # write the buffer to a file 25 # editor says it wrote 25 bytes q # and leave the editor caught SIGCHLD
- When the editor terminates, the kernel sends the
SIGCHLD
signal to the parent (thea.out
process) and it is caught and returned from the signal handler. The parent should be catching theSIGCHLD
signal because it has created its own children, so that it knows when its children have terminated. - The delivery of the
SIGCHLD
signal in the parent should be blocked while thesystem
function is executing, as specified by POSIX.1. Otherwise, when the child created bysystem
terminates, it would fool the caller ofsystem
into thinking that one of its own children terminated. The caller would then use one of thewait
functions to get the termination status of the child, thereby preventing thesystem
function from being able to obtain the child’s termination status for its return value.
If we run the program again, this time sending the editor an interrupt signal, we get
$ ./a.out a # append text to the editor’s buffer hello, world . # period on a line by itself stops append mode 1,$p # print first through last lines to see what’s there hello, world w temp.foo # write the buffer to a file 13 # editor says it wrote 13 bytes ˆC # type the interrupt character ? # editor catches signal, prints question mark caught SIGINT # and so does the parent process q # leave editor caught SIGCHLD
As mentioned in Section 9.6, typing the interrupt character causes the interrupt signal to be sent to all the processes in the foreground process group. The following figure shows the arrangement of the processes when the editor is running.
In this example:
SIGINT
is sent to all three foreground processes (the shell ignores it) and both the a.out
process and the editor catch the signal. When running another program with the system
function, we shouldn’t have both the parent and the child catching the two terminal-generated signals: interrupt and quit. Instead, these two signals should be sent to the program that is running: the child. Since the command that is executed by system can be an interactive command (the ed
program in this example) and since the caller of system
gives up control while the program executes, waiting for it to finish, the caller of system
should not be receiving these two terminal-generated signals. For this reason, POSIX.1 specifies that the system
function should ignore the SIGINT
and SIGQUIT
signals while waiting for the command to complete.
Implementation of system
with signal handling¶
The program below shows an implementation of the system function with the required signal handling.
#include <sys/wait.h> #include <errno.h> #include <signal.h> #include <unistd.h> int system(const char *cmdstring) /* with appropriate signal handling */ { pid_t pid; int status; struct sigaction ignore, saveintr, savequit; sigset_t chldmask, savemask; if (cmdstring == NULL) return(1); /* always a command processor with UNIX */ ignore.sa_handler = SIG_IGN; /* ignore SIGINT and SIGQUIT */ sigemptyset(&ignore.sa_mask); ignore.sa_flags = 0; if (sigaction(SIGINT, &ignore, &saveintr) < 0) return(-1); if (sigaction(SIGQUIT, &ignore, &savequit) < 0) return(-1); sigemptyset(&chldmask); /* now block SIGCHLD */ sigaddset(&chldmask, SIGCHLD); if (sigprocmask(SIG_BLOCK, &chldmask, &savemask) < 0) return(-1); if ((pid = fork()) < 0) { status = -1; /* probably out of processes */ } else if (pid == 0) { /* child */ /* restore previous signal actions & reset signal mask */ sigaction(SIGINT, &saveintr, NULL); sigaction(SIGQUIT, &savequit, NULL); sigprocmask(SIG_SETMASK, &savemask, NULL); execl("/bin/sh", "sh", "-c", cmdstring, (char *)0); _exit(127); /* exec error */ } else { /* parent */ while (waitpid(pid, &status, 0) < 0) if (errno != EINTR) { status = -1; /* error other than EINTR from waitpid() */ break; } } /* restore previous signal actions & reset signal mask */ if (sigaction(SIGINT, &saveintr, NULL) < 0) return(-1); if (sigaction(SIGQUIT, &savequit, NULL) < 0) return(-1); if (sigprocmask(SIG_SETMASK, &savemask, NULL) < 0) return(-1); return(status); }
This implementation of system
differs from the previous flawed one in the following ways:
- No signal is sent to the calling process when we type the interrupt or quit character.
- When the
ed
command exits,SIGCHLD
is not sent to the calling process. Instead, it is blocked until we unblock it in the last call tosigprocmask
, after thesystem
function retrieves the child’s termination status by callingwaitpid
.
POSIX.1 states that if wait
or waitpid
returns the status of a child process while SIGCHLD
is pending, then SIGCHLD
should not be delivered to the process unless the status of another child process is also available. FreeBSD 8.0, Mac OS X 10.6.8, and Solaris 10 all implement this semantic, while Linux 3.2.0 doesn’t. In Linux, SIGCHLD
remains pending after the system
function calls waitpid
; when the signal is unblocked, it is delivered to the caller. If we called wait
in the sig_chld
function in Figure 10.26, a Linux system would return −1 with errno
set to ECHILD
, since the system
function already retrieved the termination status of the child.
Many older texts show the ignoring of the interrupt and quit signals as follows:
if ((pid = fork()) < 0) { err_sys("fork error"); } else if (pid == 0) { /* child */ execl(...); _exit(127); } /* parent */ old_intr = signal(SIGINT, SIG_IGN); old_quit = signal(SIGQUIT, SIG_IGN); waitpid(pid, &status, 0) signal(SIGINT, old_intr); signal(SIGQUIT, old_quit);
The problem with this sequence of code is that we have no guarantee after the fork
regarding whether the parent or child runs first. If the child runs first and the parent doesn’t run for some time after, an interrupt signal might be generated before the parent is able to change its disposition to be ignored. For this reason, in the implementation of system
, we change the disposition of the signals before the fork
.
Note that we have to reset the dispositions of these two signals in the child before the call to execl
. This allows execl to change their dispositions to the default, based on the caller’s dispositions, as described in Section 8.10.
Return Value from system
¶
The return value from system
is the termination status of the shell, which isn’t always the termination status of the command string. [p371]
Run the program in Figure 8.24 and send some signals to the command that’s executing:
$ tsys "sleep 30" ˆCnormal termination, exit status = 130 # we press the interrupt key $ tsys "sleep 30" ˆ\sh: 946 # Quit we press the quit key normal termination, exit status = 131
When we terminate the sleep
call with the interrupt signal, the pr_exit
function (Figure 8.5) thinks that it terminated normally. The same thing happens when we kill the sleep
call with the quit key. This is because the Bourne shell has a poorly documented feature in which its termination status is 128 plus the signal number, when the command it was executing is terminated by a signal. [p372]
Try a similar example, but this time we’ll send a signal directly to the shell and see what is returned by system:
$ tsys "sleep 30" & # start it in background this time 9257 $ ps -f # look at the process IDs UID PID PPID TTY TIME CMD sar 9260 949 pts/5 0:00 ps -f sar 9258 9257 pts/5 0:00 sh -c sleep 30 sar 949 947 pts/5 0:01 /bin/sh sar 9257 949 pts/5 0:00 tsys sleep 30 sar 9259 9258 pts/5 0:00 sleep 30 $ kill -KILL 9258 # kill the shell itself abnormal termination, signal number = 9
We can see that the return value from system reports an abnormal termination only when the shell itself terminates abnormally.
Other shells behave differently when handling terminal-generated signals, such as SIGINT
and SIGQUIT
. With bash
and dash
, for example, pressing the interrupt or quit key will result in an exit status indicating abnormal termination with the corresponding signal number. However, if we find our process executing sleep
and send it a signal directly, so that the signal goes only to the individual process instead of the entire foreground process group, we will find that these shells behave like the Bourne shell and exit with a normal termination status of 128 plus the signal number.
When writing programs that use the system
function, be sure to interpret the return value correctly. If you call fork
, exec
, and wait
yourself, the termination status is not the same as if you call system.
sleep
, nanosleep
, and clock_nanosleep
Functions¶
#include <unistd.h> unsigned int sleep(unsigned int seconds); /* Returns: 0 or number of unslept seconds */
The sleep
function causes the calling process to be suspended until either:
- The amount of wall clock time specified by seconds has elapsed. In this case, the return value is 0.
- A signal is caught by the process and the signal handler returns. In this case the return value is the number of unslept seconds (the requested time minus the actual time slept).
As with an alarm
signal, the actual return may occur at a time later than requested because of other system activity.
Although sleep can be implemented with the alarm
function (Section 10.10), this isn’t required. If alarm
is used, there can be interactions between the two functions. The POSIX.1 standard leaves all these interactions unspecified.
FreeBSD 8.0, Linux 3.2.0, Mac OS X 10.6.8, and Solaris 10 implement sleep
using the nanosleep
function, which allows the implementation to be independent of signals and the alarm timer.
The follow example shows an implementation of the POSIX.1 sleep
function. This function is a modification of Figure 10.7, which handles signals reliably, avoiding the race condition in the earlier implementation, but does not handle any interactions with previously set alarms.
#include "apue.h" static void sig_alrm(int signo) { /* nothing to do, just returning wakes up sigsuspend() */ } unsigned int sleep(unsigned int seconds) { struct sigaction newact, oldact; sigset_t newmask, oldmask, suspmask; unsigned int unslept; /* set our handler, save previous information */ newact.sa_handler = sig_alrm; sigemptyset(&newact.sa_mask); newact.sa_flags = 0; sigaction(SIGALRM, &newact, &oldact); /* block SIGALRM and save current signal mask */ sigemptyset(&newmask); sigaddset(&newmask, SIGALRM); sigprocmask(SIG_BLOCK, &newmask, &oldmask); alarm(seconds); suspmask = oldmask; /* make sure SIGALRM isn't blocked */ sigdelset(&suspmask, SIGALRM); /* wait for any signal to be caught */ sigsuspend(&suspmask); /* some signal has been caught, SIGALRM is now blocked */ unslept = alarm(0); /* reset previous action */ sigaction(SIGALRM, &oldact, NULL); /* reset signal mask, which unblocks SIGALRM */ sigprocmask(SIG_SETMASK, &oldmask, NULL); return(unslept); }
This code doesn't use any form of nonlocal branching (as in Figure 10.8 to avoid the race condition between alarm and pause), so there is no effect on other signal handlers that may be executing when the SIGALRM
is handled.
The nanosleep
function is similar to the sleep
function, but provides nanosecond-level granularity.
#include <time.h> int nanosleep(const struct timespec *reqtp, struct timespec *remtp); /* Returns: 0 if slept for requested time or −1 on error */
This function suspends the calling process until either the requested time has elapsed or the function is interrupted by a signal.
Arguments:
- The reqtp parameter specifies the amount of time to sleep in seconds and nanoseconds.
- The remtp parameter. If the sleep interval is interrupted by a signal and the process doesn’t terminate, the
timespec
structure pointed to by the remtp parameter will be set to the amount of time left in the sleep interval. We can set this parameter to NULL if we are uninterested in the time unslept.
Notes on nanosleep
:
- If the system doesn’t support nanosecond granularity, the requested time is rounded up.
- Because the nanosleep function doesn’t involve the generation of any signals, we can use it without worrying about interactions with other functions.
The clock_nanosleep
function provides the capability to suspend the calling thread using a delay time relative to a particular clock, using multiple system clocks (Section 6.10)
#include <time.h> int clock_nanosleep(clockid_t clock_id, int flags, const struct timespec *reqtp, struct timespec *remtp); /* Returns: 0 if slept for requested time or error number on failure */
Arguments:
- The clock_id argument specifies the clock against which the time delay is evaluated. Identifiers for clocks are listed in Figure 6.8.
- The flags argument is used to control whether the delay is absolute or relative.
- When flags is set to 0, the sleep time is relative (how long we want to sleep).
- When it is set to
TIMER_ABSTIME
, the sleep time is absolute (we want to sleep until the clock reaches the specified time).
- The reqtp and remtp arguments are the same as in the
nanosleep
function. However:- When we use an absolute time, the remtp argument is unused, because it isn’t needed.
- We can reuse the same value for the reqtp argument for additional calls to
clock_nanosleep
until the clock reaches the specified absolute time value.
Except for error returns, the call:
clock_nanosleep(CLOCK_REALTIME, 0, reqtp, remtp);
has the same effect as the call:
nanosleep(reqtp, remtp);
Some applications require precision with how long they sleep, and a relative sleep time can lead to sleeping longer than desired. Using an absolute time improves the precision, even though a time-sharing process scheduler makes no guarantee that our task will execute immediately after our sleep time has ended. [p375]
sigqueue
Function¶
As discussed in Section 10.8, most UNIX systems don’t queue signals. With the real-time extensions to POSIX.1, some systems began adding support for queueing signals. With SUSv4, the queued signal functionality has moved from the real-time extensions to the base specification.
These extensions allow applications to pass more information along with the delivery (Section 10.14). This information is embedded in a siginfo
structure.
To use queued signals we have to do the following:
- Specify the
SA_SIGINFO
flag when we install a signal handler using thesigaction
function. Without this flag, it is left up to the implementation whether the signal is queued. - Provide a signal handler in the
sa_sigaction
member of thesigaction
structure instead of using the usualsa_handler
field. Implementations might allow us to use thesa_handler
field, but we won’t be able to obtain the extra information sent with thesigqueue
function. - Use the
sigqueue
function to send signals.
#include <signal.h> int sigqueue(pid_t pid, int signo, const union sigval value) /* Returns: 0 if OK, −1 on error */
The sigqueue
function is similar to the kill
function, except that:
- We can only direct signals to a single process
- We can use the value argument to transmit either an integer or a pointer value to the signal handler.
Signals can’t be queued infinitely. When the SIGQUEUE_MAX limit (POSIX Limits) is reached, sigqueue
can fail with errno
set to
EAGAIN
.
With the real-time signal enhancements, other signal numbers between SIGRTMIN
and SIGRTMAX
inclusive can be queued, and the default action for these signals is to terminate the process. [p376]
The table below summarizes behavior of queued signals on various platforms:
Behavior | SUS | FreeBSD | Linux | Mac OS X | Solaris |
---|---|---|---|---|---|
supports sigqueue |
x | x | x | x | x |
queues other signals besides SIGRTMIN to SIGRTMAX | optional | x | x | ||
queues signals even if the caller doesn’t use the SA_SIGINFO |
optional | x | x |
Job-Control Signals¶
POSIX.1 considers six to be job-control signals:
SIGCHLD |
Child process has stopped or terminated. |
SIGCONT |
Continue process, if stopped. |
SIGSTOP |
Stop signal (can’t be caught or ignored). |
SIGTSTP |
Interactive stop signal. |
SIGTTIN |
Read from controlling terminal by background process group member. |
SIGTTOU |
Write to controlling terminal by a background process group member. |
The following program demonstrates the normal sequence of code used when a program handles job control.
#include "apue.h" #define BUFFSIZE 1024 static void sig_tstp(int signo) /* signal handler for SIGTSTP */ { sigset_t mask; /* ... move cursor to lower left corner, reset tty mode ... */ /* * Unblock SIGTSTP, since it's blocked while we're handling it. */ sigemptyset(&mask); sigaddset(&mask, SIGTSTP); sigprocmask(SIG_UNBLOCK, &mask, NULL); signal(SIGTSTP, SIG_DFL); /* reset disposition to default */ kill(getpid(), SIGTSTP); /* and send the signal to ourself */ /* we won't return from the kill until we're continued */ signal(SIGTSTP, sig_tstp); /* reestablish signal handler */ /* ... reset tty mode, redraw screen ... */ } int main(void) { int n; char buf[BUFFSIZE]; /* * Only catch SIGTSTP if we're running with a job-control shell. */ if (signal(SIGTSTP, SIG_IGN) == SIG_DFL) signal(SIGTSTP, sig_tstp); while ((n = read(STDIN_FILENO, buf, BUFFSIZE)) > 0) if (write(STDOUT_FILENO, buf, n) != n) err_sys("write error"); if (n < 0) err_sys("read error"); exit(0); }
This program does the following:
- When the program starts, it arranges to catch the
SIGTSTP
signal only if the signal’s disposition isSIG_DFL
. The reason is that when the program is started by a shell that doesn’t support job control (/bin/sh
, for example), the signal’s disposition should be set toSIG_IGN
. In fact, the shell doesn’t explicitly ignore this signal;init
sets the disposition of the three job-control signals (SIGTSTP
,SIGTTIN
, andSIGTTOU
) toSIG_IGN
. This disposition is then inherited by all login shells. Only a job-control shell should reset the disposition of these three signals toSIG_DFL
. - When we type the suspend character, the process receives the
SIGTSTP
signal and the signal handler is invoked. At this point, we would do any terminal-related processing: move the cursor to the lower-left corner, restore the terminal mode, etc. - After resetting the disposition of
SIGSTOP
to its default (stop the process) and unblocking it, we send ourself theSIGSTOP
signal (kill(getpid(), SIGTSTP);
). We have to unblock it since we’re currently handling that same signal, and the system blocks it automatically while it’s being caught. - At this point, the system stops the process. It is continued only when it receives (usually from the job-control shell, in response to an interactive
fg
command) aSIGCONT
signal. - We don’t catch
SIGCONT
since its default disposition is to continue the stopped process; when this happens, the program continues as though it returned from thekill
function. When the program is continued, we reset the disposition for theSIGTSTP
signal and do any terminal processing (as indicated in the comments).
Signal Names and Numbers¶
This section discusses how to map between signal numbers and names. Some systems provide the array:
extern char *sys_siglist[];
The array index is the signal number, giving a pointer to the character string name of the signal. [p379]
To print the signal's character string in a portable manner, use the psignal
function:
#include <signal.h> void psignal(int signo, const char *msg);
The string msg (which normally includes the name of the program) is output to the standard error, followed by a colon and a space, followed by a description of the signal, followed by a newline. If msg is NULL
, then only the description is written to the standard error. This function is similar to perror
(Section 1.7).
If you have a siginfo
structure from an alternative sigaction
signal handler, you can print the signal information with the psiginfo
function.
#include <signal.h> void psiginfo(const siginfo_t *info, const char *msg);
You can use the strsignal
function if you only need the string description of the signal. This function is similar to strerror
(Section 1.7).
#include <string.h> char *strsignal(int signo); /* Returns: a pointer to a string describing the signal */
Summary¶
An understanding of signal handling is essential to advanced UNIX System programming. This chapter has taken a long and thorough look at UNIX System signals, from previous implementations to the POSIX.1 reliable-signal concept and all the related functions, followed by POSIX.1 abort
, system
, and sleep
functions.
Doubts and Solutions¶
Verbatim¶
p324 on some macro constants in <signal.h>
¶
These constants can be used in place of the "pointer to a function that takes an integer argument and returns nothing", the second argument to
signal
, and the return value fromsignal
. The three values used for these constants need not be −1, 0, and 1. They must be three values that can never be the address of any declarable function. Most UNIX systems use the values shown.
Question: Why is the macro in the form SIG_ERR (void (*)())-1
and the like?
Solution: They are integer that cast into an address which means the "pointer to a function that takes an integer argument and returns nothing". void (*)()
tells the compiler to ignore type-checking for the parameters. See:
p359 on unblocking signals¶
If the signal is sent to the process while it is blocked, the signal delivery will be deferred until the signal is unblocked. To the application, this can look as if the signal occurs between the unblocking and the
pause
(depending on how the kernel implements signals). If this happens, or if the signal does occur between the unblocking and thepause
, we have a problem. Any occurrence of the signal in this window of time is lost, in the sense that we might not see the signal again, in which case thepause
will block indefinitely. This is another problem with the earlier unreliable signals.
Question: What is exactly the window? Shouldn't be the unblocked signals delivered to the process? Not fully understood.