8.2 Generating Signals
Every signal has a symbolic name starting with SIG. The signal names are defined in signal.h, which must be included by any C program that uses signals. The names of the signals represent small integers greater than 0. Table 8.1 describes the required POSIX signals and lists their default actions. Two signals, SIGUSR1 and SIGUSR2, are available for users and do not have a preassigned use. Some signals such as SIGFPE or SIGSEGV are generated when certain errors occur; other signals are generated by specific calls such as alarm.
Table 8.1. The POSIX required signals.|
SIGABRT | process abort | implementation dependent | SIGALRM | alarm clock | abnormal termination | SIGBUS | access undefined part of memory object | implementation dependent | SIGCHLD | child terminated, stopped or continued | ignore | SIGCONT | execution continued if stopped | continue | SIGFPE | error in arithmetic operation as in division by zero | implementation dependent | SIGHUP | hang-up (death) on controlling terminal (process) | abnormal termination | SIGILL | invalid hardware instruction | implementation dependent | SIGINT | interactive attention signal (usually Ctrl-C) | abnormal termination | SIGKILL | terminated (cannot be caught or ignored) | abnormal termination | SIGPIPE | write on a pipe with no readers | abnormal termination | SIGQUIT | interactive termination: core dump (usually Ctrl-|) | implementation dependent | SIGSEGV | invalid memory reference | implementation dependent | SIGSTOP | execution stopped (cannot be caught or ignored) | stop | SIGTERM | termination | abnormal termination | SIGTSTP | terminal stop | stop | SIGTTIN | background process attempting to read | stop | SIGTTOU | background process attempting to write | stop | SIGURG | high bandwidth data available at a socket | ignore | SIGUSR1 | user-defined signal 1 | abnormal termination | SIGUSR2 | user-defined signal 2 | abnormal termination |
Generate signals from the shell with the kill command. The name kill derives from the fact that, historically, many signals have the default action of terminating the process. The signal_name parameter is a symbolic name for the signal formed by omitting the leading SIG from the corresponding symbolic signal name.
SYNOPSIS
kill -s signal_name pid...
kill -l [exit_status]
kill [-signal_name] pid...
kill [-signal_number] pid...
POSIX:Shell and Utilities
The last two lines of the synopsis list the traditional forms of the kill command. Despite the fact that these two forms do not follow the POSIX guidelines for command-line arguments, they continue to be included in the POSIX standard because of their widespread use. The last form of kill supports only the signal_number values of 0 for signal 0, 1 for signal SIGHUP, 2 for signal SIGINT, 3 for signal SIGQUIT, 6 for signal SIGABRT, 9 for signal SIGKILL, 14 for signal SIGALRM and 15 for signal SIGTERM.
Example 8.1
The following command is the traditional way to send signal number 9 (SIGKILL) to process 3423.
kill -9 3423
Example 8.2
The following command sends the SIGUSR1 signal to process 3423.
kill -s USR1 3423
Example 8.3
The kill -l command gives a list of the available symbolic signal names. A system running Sun Solaris produced the following sample output.
% kill -l
HUP INT QUIT ILL TRAP ABRT EMT FPE
KILL BUS SEGV SYS PIPE ALRM TERM USR1
USR2 CLD PWR WINCH URG POLL STOP TSTP
CONT TTIN TTOU VTALRM PROF XCPU XFSZ WAITING
LWP FREEZE THAW CANCEL LOST XRES RTMIN RTMIN+1
RTMIN+2 RTMIN+3 RTMAX-3 RTMAX-2 RTMAX-1 RTMAX
Call the kill function in a program to send a signal to a process. The kill function takes a process ID and a signal number as parameters. If the pid parameter is greater than zero, kill sends the signal to the process with that ID. If pid is 0, kill sends the signal to members of the caller's process group. If the pid parameter is -1, kill sends the signal to all processes for which it has permission to send. If the pid parameter is another negative value, kill sends the signal to the process group with group ID equal to |pid|. Section 11.5 discusses process groups.
SYNOPSIS
#include <signal.h>
int kill(pid_t pid, int sig);
POSIX:CX
If successful, kill returns 0. If unsuccessful, kill returns 1 and sets errno. The following table lists the mandatory errors for kill.
|
EINVAL | sig is an invalid or unsupported signal | EPERM | caller does not have the appropriate privileges | ESRCH | no process or process group corresponds to pid |
A user may send a signal only to processes that he or she owns. For most signals, kill determines permissions by comparing the user IDs of caller and target. SIGCONT is an exception. For SIGCONT, user IDs are not checked if kill is sent to a process that is in the same session. Section 11.5 discusses sessions. For security purposes, a system may exclude an unspecified set of processes from receiving the signal.
Example 8.4
The following code segment sends SIGUSR1 to process 3423.
if (kill(3423, SIGUSR1) == -1)
perror("Failed to send the SIGUSR1 signal");
Normally, programs do not hardcode specific process IDs such as 3423 in the kill function call. The usual way to find out relevant process IDs is with getpid, getppid, getgpid or by saving the return value from fork.
Example 8.5
This scenario sounds grim, but a child process can kill its parent by executing the following code segment.
if (kill(getppid(), SIGTERM) == -1)
perror ("Failed to kill parent");
A process can send a signal to itself with the raise function. The raise function takes just one parameter, a signal number.
SYNOPSIS
#include <signal.h>
int raise(int sig);
POSIX:CX
If successful, raise returns 0. If unsuccessful, raise returns a nonzero error value and sets errno. The raise function sets errno to EINVAL if sig is invalid.
Example 8.6
The following statement causes a process to send the SIGUSR1 signal to itself.
if (raise(SIGUSR1) != 0)
perror("Failed to raise SIGUSR1");
A key press causes a hardware interrupt that is handled by the device driver for the keyboard. This device driver and its associated modules may perform buffering and editing of the keyboard input. Two special characters, the INTR and QUIT characters, cause the device driver to send a signal to the foreground process group. A user can send the SIGINT signal to the foreground process group by entering the INTR character. This user-settable character is often Ctrl-C. The user-settable QUIT character sends the SIGQUIT signal.
Example 8.7
The stty -a command reports on the characteristics of the device associated with standard input, including the settings of the signal-generating characters. A system running Sun Solaris produced the following output.
% stty -a
speed 9600 baud;
rows = 57; columns = 103; ypixels = 0; xpixels = 0;
eucw 1:0:0:0, scrw 1:0:0:0
intr = ^c; quit = ^|; erase = ^?; kill = ^u;
eof = ^d; eol = <undef>; eol2 = <undef>; swtch = <undef>;
start = ^q; stop = ^s; susp = ^z; dsusp = ^y;
rprnt = ^r; flush = ^o; werase = ^w; lnext = ^v;
-parenb -parodd cs8 -cstopb hupcl cread -clocal -loblk -crtscts
-parext -ignbrk brkint ignpar -parmrk -inpck -istrip -inlcr -igncr
icrnl -iuclc ixon -ixany -ixoff imaxbel
isig icanon -xcase echo echoe echok -echonl -noflsh
-tostop echoctl -echoprt echoke -defecho -flusho -pendin iexten
opost -olcuc onlcr -ocrnl -onocr -onlret -ofill -ofdel
The terminal in Example 8.7 interprets Ctrl-C as the INTR character. The QUIT character (Ctrl-| above) generates SIGQUIT. The SUSP character (Ctrl-Z above) generates SIGSTOP, and the DSUSP character (Ctrl-Y above) generates SIGCONT.
The alarm function causes a SIGALRM signal to be sent to the calling process after a specified number of real seconds has elapsed. Requests to alarm are not stacked, so a call to alarm before the previous timer expires causes the alarm to be reset to the new value. Call alarm with a zero value for seconds to cancel a previous alarm request.
SYNOPSIS
#include <unistd.h>
unsigned alarm(unsigned seconds);
POSIX
The alarm function returns the number of seconds remaining on the alarm before the call reset the value, or 0 if no previous alarm was set. The alarm function never reports an error.
Example 8.8 simplealarm.c
Since the default action for SIGALRM is to terminate the process, the following program runs for approximately ten seconds of wall-clock time.
#include <unistd.h>
int main(void) {
alarm(10);
for ( ; ; ) ;
}
8.3 Manipulating Signal Masks and Signal Sets
A process can temporarily prevent a signal from being delivered by blocking it. Blocked signals do not affect the behavior of the process until they are delivered. The process signal mask gives the set of signals that are currently blocked. The signal mask is of type sigset_t.
Blocking a signal is different from ignoring a signal. When a process blocks a signal, the operating system does not deliver the signal until the process unblocks the signal. A process blocks a signal by modifying its signal mask with sigprocmask. When a process ignores a signal, the signal is delivered and the process handles it by throwing it away. The process sets a signal to be ignored by calling sigaction with a handler of SIG_IGN, as described in Section 8.4.
Specify operations (such as blocking or unblocking) on groups of signals by using signal sets of type sigset_t. Signal sets are manipulated by the five functions listed in the following synopsis box. The first parameter for each function is a pointer to a sigset_t. The sigaddset adds signo to the signal set, and the sigdelset removes signo from the signal set. The sigemptyset function initializes a sigset_t to contain no signals; sigfillset initializes a sigset_t to contain all signals. Initialize a signal set by calling either sigemptyset or sigfillset before using it. The sigismember reports whether signo is in a sigset_t.
SYNOPSIS
#include <signal.h>
int sigaddset(sigset_t *set, int signo);
int sigdelset(sigset_t *set, int signo);
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigismember(const sigset_t *set, int signo);
POSIX:CX
The sigismember function returns 1 if signo is in *set and 0 if signo is not in *set. If successful, the other functions return 0. If unsuccessful, these other functions return 1 and set errno. POSIX does not define any mandatory errors for these functions.
Example 8.9
The following code segment initializes signal set twosigs to contain exactly the two signals SIGINT and SIGQUIT.
if ((sigemptyset(&twosigs) == -1) ||
(sigaddset(&twosigs, SIGINT) == -1) ||
(sigaddset(&twosigs, SIGQUIT) == -1))
perror("Failed to set up signal mask");
A process can examine or modify its process signal mask with the sigprocmask function. The how parameter is an integer specifying the manner in which the signal mask is to be modified. The set parameter is a pointer to a signal set to be used in the modification. If set is NULL, no modification is made. If oset is not NULL, the sigprocmask returns in *oset the signal set before the modification.
SYNOPSIS
#include <signal.h>
int sigprocmask(int how, const sigset_t *restrict set,
sigset_t *restrict oset);
POSIX:CX
If successful, sigprocmask returns 0. If unsuccessful, sigprocmask returns 1 and sets errno. The sigprocmask function sets errno to EINVAL if how is invalid. The sigprocmask function should only be used by a process with a single thread. When multiple threads exist, the pthread_sigmask function (page 474) should be used.
The how parameter, which specifies the manner in which the signal mask is to be modified, can take on one of the following three values.
SIG_BLOCK: | add a collection of signals to those currently blocked | SIG_UNBLOCK: | delete a collection of signals from those currently blocked | SIG_SETMASK: | set the collection of signals being blocked to the specified set |
Keep in mind that some signals, such as SIGSTOP and SIGKILL, cannot be blocked. If an attempt is made to block these signals, the system ignores the request without reporting an error.
Example 8.10
The following code segment adds SIGINT to the set of signals that the process has blocked.
sigset_t newsigset;
if ((sigemptyset(&newsigset) == -1) ||
(sigaddset(&newsigset, SIGINT) == -1))
perror("Failed to initialize the signal set");
else if (sigprocmask(SIG_BLOCK, &newsigset, NULL) == -1)
perror("Failed to block SIGINT");
If SIGINT is already blocked, the call to sigprocmask has no effect.
Program 8.1 displays a message, blocks the SIGINT signal while doing some useless work, unblocks the signal, and does more useless work. The program repeats this sequence continually in a loop.
If a user enters Ctrl-C while SIGINT is blocked, Program 8.1 finishes the calculation and prints a message before terminating. If a user types Ctrl-C while SIGINT is unblocked, the program terminates immediately.
Program 8.1 blocktest.c
A program that blocks and unblocks SIGINT.
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int i;
sigset_t intmask;
int repeatfactor;
double y = 0.0;
if (argc != 2) {
fprintf(stderr, "Usage: %s repeatfactor\n", argv[0]);
return 1;
}
repeatfactor = atoi(argv[1]);
if ((sigemptyset(&intmask) == -1) || (sigaddset(&intmask, SIGINT) == -1)){
perror("Failed to initialize the signal mask");
return 1;
}
for ( ; ; ) {
if (sigprocmask(SIG_BLOCK, &intmask, NULL) == -1)
break;
fprintf(stderr, "SIGINT signal blocked\n");
for (i = 0; i < repeatfactor; i++)
y += sin((double)i);
fprintf(stderr, "Blocked calculation is finished, y = %f\n", y);
if (sigprocmask(SIG_UNBLOCK, &intmask, NULL) == -1)
break;
fprintf(stderr, "SIGINT signal unblocked\n");
for (i = 0; i < repeatfactor; i++)
y += sin((double)i);
fprintf(stderr, "Unblocked calculation is finished, y=%f\n", y);
}
perror("Failed to change signal mask");
return 1;
}
The function makepair of Program 8.2 takes two pathnames as parameters and creates two named pipes with these names. If successful, makepair returns 0. If unsuccessful, makepair returns 1 and sets errno. The function blocks all signals during the creation of the two pipes to be sure that it can deallocate both pipes if there is an error. The function restores the original signal mask before the return. The if statement relies on the conditional left-to-right evaluation of && and ||.
Exercise 8.11
Is it possible that after a call to makepair, pipe1 exists but pipe2 does not?
Answer:
Yes. This could happen if pipe1 already exists but pipe2 does not and the user does not have write permission to the directory. It could also happen if the SIGKILL signal is delivered between the two calls to mkfifo.
Program 8.2 makepair.c
A function that blocks signals while creating two pipes. (See Exercise 8.11 and Exercise 8.12 for a discussion of some flaws.)
#include <errno.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#define R_MODE (S_IRUSR | S_IRGRP | S_IROTH)
#define W_MODE (S_IWUSR | S_IWGRP | S_IWOTH)
#define RW_MODE (R_MODE | W_MODE)
int makepair(char *pipe1, char *pipe2) {
sigset_t blockmask;
sigset_t oldmask;
int returncode = 0;
if (sigfillset(&blockmask) == -1)
return -1;
if (sigprocmask(SIG_SETMASK, &blockmask, &oldmask) == -1)
return -1;
if (((mkfifo(pipe1, RW_MODE) == -1) && (errno != EEXIST)) ||
((mkfifo(pipe2, RW_MODE) == -1) && (errno != EEXIST))) {
returncode = errno;
unlink(pipe1);
unlink(pipe2);
}
if ((sigprocmask(SIG_SETMASK, &oldmask, NULL) == -1) && !returncode)
returncode = errno;
if (returncode) {
errno = returncode;
return -1;
}
return 0;
}
Exercise 8.12
Does a makepair return value of 0 guarantee that FIFOs corresponding to pipe1 and pipe2 are available on return?
Answer:
If one of the files already exists, mkfifo returns 1 and sets errno to EEXIST. The makepair function assumes that the FIFO exists without checking whether the file was a FIFO or an ordinary file. Thus, it is possible for makepair to indicate success even if this previously existing file is not a FIFO.
In Program 8.3, the parent blocks all signals before forking a child process to execute an ls command. Processes inherit the signal mask after both fork and exec, so the ls command executes with signals blocked. The child created by fork in Program 8.3 has a copy of the original signal mask saved in oldmask. An exec command overwrites all program variables, so an executed process cannot restore the original mask once exec takes place. The parent restores the original signal mask and then waits for the child.
Program 8.3 blockchild.c
A program that blocks signals before calling fork and execl.
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "restart.h"
int main(void) {
pid_t child;
sigset_t mask, omask;
if ((sigfillset(&mask) == -1) ||
(sigprocmask(SIG_SETMASK, &mask, &omask) == -1)) {
perror("Failed to block the signals");
return 1;
}
if ((child = fork()) == -1) {
perror("Failed to fork child");
return 1;
}
if (child == 0) { /* child code */
execl("/bin/ls", "ls", "-l", NULL);
perror("Child failed to exec");
return 1;
}
if (sigprocmask(SIG_SETMASK, &omask, NULL) == -1){ /* parent code */
perror("Parent failed to restore signal mask");
return 1;
}
if (r_wait(NULL) == -1) {
perror("Parent failed to wait for child");
return 1;
}
return 0;
}
Exercise 8.13
Run Program 8.3 from a working directory with a large number of files. Experiment with entering Ctrl-C at various points during the execution and explain what happens.
Answer:
The main program can be interrupted while the listing is being displayed, and the prompt will appear in the middle of the listing. The execution of ls will not be interrupted by the signal.
Program 8.4 password.c
A function that retrieves a user password.
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <termios.h>
#include <unistd.h>
#include "restart.h"
int setecho(int fd, int onflag);
int password(const char *prompt, char *passbuf, int passmax) {
int fd;
int firsterrno = 0;
sigset_t signew, sigold;
char termbuf[L_ctermid];
if (ctermid(termbuf) == NULL) { /* find the terminal name */
errno = ENODEV;
return -1;
}
if ((fd = open(termbuf, O_RDONLY)) == -1) /* open descriptor to terminal */
return -1;
if ((sigemptyset(&signew) == -1) || /* block SIGINT, SIGQUIT and SIGTSTP */
(sigaddset(&signew, SIGINT) == -1) ||
(sigaddset(&signew, SIGQUIT) == -1) ||
(sigaddset(&signew, SIGTSTP) == -1) ||
(sigprocmask(SIG_BLOCK, &signew, &sigold) == -1) ||
(setecho(fd, 0) == -1)) { /* set terminal echo off */
firsterrno = errno;
sigprocmask(SIG_SETMASK, &sigold, NULL);
r_close(fd);
errno = firsterrno;
return -1;
}
if ((r_write(STDOUT_FILENO, (char *)prompt, strlen(prompt)) == -1) ||
(readline(fd, passbuf, passmax) == -1)) /* read password */
firsterrno = errno;
else
passbuf[strlen(passbuf) - 1] = 0; /* remove newline */
if ((setecho(fd, 1) == -1) && !firsterrno) /* turn echo back on */
firsterrno = errno;
if ((sigprocmask(SIG_SETMASK, &sigold, NULL) == -1) && !firsterrno )
firsterrno = errno;
if ((r_close(fd) == -1) && !firsterrno) /* close descriptor to terminal */
firsterrno = errno;
return firsterrno ? errno = firsterrno, -1: 0;
}
Program 8.4 shows an improvement on the passwordnosigs function of Program 6.13 on page 208. The password function blocks SIGINT, SIGQUIT and SIGTSTP while terminal echo is set off, preventing the terminal from being placed in an unusable state if one of these signals is delivered to the process while this function is executing.
|
8.4 Catching and Ignoring Signalssigaction
The sigaction function allows the caller to examine or specify the action associated with a specific signal. The sig parameter of sigaction specifies the signal number for the action. The act parameter is a pointer to a struct sigaction structure that specifies the action to be taken. The oact parameter is a pointer to a struct sigaction structure that receives the previous action associated with the signal. If act is NULL, the call to sigaction does not change the action associated with the signal. If oact is NULL, the call to sigaction does not return the previous action associated with the signal.
SYNOPSIS
#include <signal.h>
int sigaction(int sig, const struct sigaction *restrict act,
struct sigaction *restrict oact);
POSIX:CX
If successful, sigaction returns 0. If unsuccessful, sigaction returns 1 and sets errno. The following table lists the mandatory errors for sigaction.
|
EINVAL | sig is an invalid signal number, or attempt to catch a signal that cannot be caught, or attempt to ignore a signal that cannot be ignored | ENOTSUP | SA_SIGINFO bit of the sa_flags is set and the implementation does not support POSIX:RTS or POSIX:XSI |
The struct sigaction structure must have at least the following members.
struct sigaction {
void (*sa_handler)(int); /* SIG_DFL, SIG_IGN or pointer to function */
sigset_t sa_mask; /* additional signals to be blocked
during execution of handler */
int sa_flags; /* special flags and options */
void(*sa_sigaction) (int, siginfo_t *, void *); /* realtime handler */
};
The storage for sa_handler and sa_sigaction may overlap, and an application should use only one of these members to specify the action. If the SA_SIGINFO flag of the sa_flags field is cleared, the sa_handler specifies the action to be taken for the specified signal. If the SA_SIGINFO flag of the sa_flags field is set and the implementation supports either the POSIX:RTS or the POSIX:XSI Extension, the sa_sigaction field specifies a signal-catching function.
Example 8.14
The following code segment sets the signal handler for SIGINT to mysighand.
struct sigaction newact;
newact.sa_handler = mysighand; /* set the new handler */
newact.sa_flags = 0; /* no special options */
if ((sigemptyset(&newact.sa_mask) == -1) ||
(sigaction(SIGINT, &newact, NULL) == -1))
perror("Failed to install SIGINT signal handler");
In the POSIX base standard, a signal handler is an ordinary function that returns void and has one integer parameter. When the operating system delivers the signal, it sets this parameter to the number of the signal that was delivered. Most signal handlers ignore this value, but it is possible to have a single signal handler for many signals. The usefulness of signal handlers is limited by the inability to pass values to them. This capability has been added to the POSIX:RTS and POSIX:XSI Extensions, which can use the alternative sa_sigaction field of the struct sigaction structure to specify a handler. This section describes using the sa_handler field of sigaction to set up the handler; Section 9.4 describes using the sa_sigaction field for the handler.
Two special values of the sa_handler member of struct sigaction are SIG_DFL> and SIG_IGN. The SIG_DFL value specifies that sigaction should restore the default action for the signal. The SIG_IGN value specifies that the process should handle the signal by ignoring it (throwing it away).
Example 8.15
The following code segment causes the process to ignore SIGINT if the default action is in effect for this signal.
struct sigaction act;
if (sigaction(SIGINT, NULL, &act) == -1) /* Find current SIGINT handler */
perror("Failed to get old handler for SIGINT");
else if (act.sa_handler == SIG_DFL) { /* if SIGINT handler is default */
act.sa_handler = SIG_IGN; /* set new SIGINT handler to ignore */
if (sigaction(SIGINT, &act, NULL) == -1)
perror("Failed to ignore SIGINT");
}
Example 8.16
The following code segment sets up a signal handler that catches the SIGINT signal generated by Ctrl-C.
void catchctrlc(int signo) {
char handmsg[] = "I found Ctrl-C\n";
int msglen = sizeof(handmsg);
write(STDERR_FILENO, handmsg, msglen);
}
...
struct sigaction act;
act.sa_handler = catchctrlc;
act.sa_flags = 0;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGINT, &act, NULL) == -1))
perror("Failed to set SIGINT to handle Ctrl-C");
Exercise 8.17
Why didn't Example 8.16 use fprintf or strlen in the signal handler?
Answer:
POSIX guarantees that write is async-signal safe, meaning that it can be called safely from inside a signal handler. There are no similar guarantees for fprintf or strlen, but they may be async-signal safe in some implementations. Table 8.2 on page 285 lists the functions that POSIX guarantees are async-signal safe.
Example 8.18
The following code segment sets the action of SIGINT to the default.
struct sigaction newact;
newact.sa_handler = SIG_DFL; /* new handler set to default */
newact.sa_flags = 0; /* no special options */
if ((sigemptyset(&newact.sa_mask) == -1) ||
(sigaction(SIGINT, &newact, NULL) == -1))
perror("Failed to set SIGINT to the default action");
Example 8.19 testignored.c
The following function takes a signal number parameter and returns 1 if that signal is ignored and 0 otherwise.
#include <signal.h>
#include <stdio.h>
int testignored(int signo) {
struct sigaction act;
if ((sigaction(signo, NULL, &act) == -1) || (act.sa_handler != SIG_IGN))
return 0;
return 1;
}
Program 8.5 estimates the average value of sin(x) on the interval from 0 to 1 by computing the average of the sine of randomly picked values. The main program loop chooses a random value, x, between 0 and 1, adds sin(x) to a running sum, increments the count of the values, and prints the current count and average. The program illustrates the use of a signal handler to gracefully terminate a program. When the user enters Ctrl-C at standard input, the signal handler sets doneflag to signify that the program should terminate. On each iteration of the computation loop, the program tests doneflag to see whether it should drop out of the loop and print a final message.
Program 8.5 signalterminate.c
A program that terminates gracefully when it receives a Ctrl-C.
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
static volatile sig_atomic_t doneflag = 0;
/* ARGSUSED */
static void setdoneflag(int signo) {
doneflag = 1;
}
int main (void) {
struct sigaction act;
int count = 0;
double sum = 0;
double x;
act.sa_handler = setdoneflag; /* set up signal handler */
act.sa_flags = 0;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGINT, &act, NULL) == -1)) {
perror("Failed to set SIGINT handler");
return 1;
}
while (!doneflag) {
x = (rand() + 0.5)/(RAND_MAX + 1.0);
sum += sin(x);
count++;
printf("Count is %d and average is %f\n", count, sum/count);
}
printf("Program terminating ...\n");
if (count == 0)
printf("No values calculated yet\n");
else
printf("Count is %d and average is %f\n", count, sum/count);
return 0;
}
Code that accesses doneflag is a critical section because the signal handler can modify this variable while the main program examines it. (See Chapter 14 for a discussion of critical sections and atomic operations.) We handle the problem here by declaring doneflag to be sig_atomic_t, an integral type that is small enough to be accessed atomically. The volatile qualifier on doneflag informs the compiler that the variable may be changed asynchronously to program execution. Otherwise, the compiler might assume that doneflag is not modified in the while loop and generate code that only tests the condition on the first iteration of the loop.
Exercise 8.20
Why is it okay to use perror and printf in Program 8.5 even though these functions are not "signal safe"?
Answer:
Signal safety is a problem when both the signal handler and the main program use these functions. In this case, only the main program uses these functions.
When both a signal handler and the main program need to access data that is larger than sig_atomic_t, care must be taken so that the data is not modified in one part of the program while being read in another. Program 8.6 also calculates the average value of sin(x) over the interval from 0 to 1, but it does not print the result on each iteration. Instead, the main program loop generates a string containing the results every 10,000th iteration. A signal handler for SIGUSR1 outputs the string when the user sends SIGUSR1 to the process.
Program 8.6 averagesin.c
A program to estimate the average values of sin(x) over the interval from 0 to 1.
#include <errno.h>
#include <limits.h>
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BUFSIZE 100
static char buf[BUFSIZE];
static int buflen = 0;
/* ARGSUSED */
static void handler(int signo) { /* handler outputs result string */
int savederrno;
savederrno = errno;
write(STDOUT_FILENO, buf, buflen);
errno = savederrno;
}
static void results(int count, double sum) { /* set up result string */
double average;
double calculated;
double err;
double errpercent;
sigset_t oset;
sigset_t sigset;
if ((sigemptyset(&sigset) == -1) ||
(sigaddset(&sigset, SIGUSR1) == -1) ||
(sigprocmask(SIG_BLOCK, &sigset, &oset) == -1) )
perror("Failed to block signal in results");
if (count == 0)
snprintf(buf, BUFSIZE, "No values calculated yet\n");
else {
calculated = 1.0 - cos(1.0);
average = sum/count;
err = average - calculated;
errpercent = 100.0*err/calculated;
snprintf(buf, BUFSIZE,
"Count = %d, sum = %f, average = %f, error = %f or %f%%\n",
count, sum, average, err, errpercent);
}
buflen = strlen(buf);
if (sigprocmask(SIG_SETMASK, &oset, NULL) == -1)
perror("Failed to unblock signal in results");
}
int main(void) {
int count = 0;
double sum = 0;
double x;
struct sigaction act;
act.sa_handler = handler;
act.sa_flags = 0;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGUSR1, &act, NULL) == -1) ) {
perror("Failed to set SIGUSR1 signal handler");
return 1;
}
fprintf(stderr, "Process %ld starting calculation\n", (long)getpid());
for ( ; ; ) {
if ((count % 10000) == 0)
results(count, sum);
x = (rand() + 0.5)/(RAND_MAX + 1.0);
sum += sin(x);
count++;
if (count == INT_MAX)
break;
}
results(count, sum);
handler(0); /* call handler directly to write out the results */
return 0;
}
The signal handler uses write instead of printf, since printf may not be safe to use in a signal handler. The handler avoids strlen for the same reason. The string and its length are global variables accessible to both the main program and the signal handler. Modifying the string in the main program and writing the string to standard output in the signal handler are critical sections for this program. The main program protects its critical section by having results block the signal while modifying the string and its length. Notice also that handler saves and restores errno, since write may change it.
Legacy programs sometimes use signal instead of sigaction to specify signal handlers. Although signal is part of ISO C, it is unreliable even when used in a program with a single thread. Always use sigaction to set up your handlers.
|
8.5 Waiting for Signalspause, sigsuspend and sigwait
Signals provide a method for waiting for an event without busy waiting. Busy waiting means continually using CPU cycles to test for the occurrence of an event. Typically, a program does this testing by checking the value of a variable in a loop. A more efficient approach is to suspend the process until the waited-for event occurs; that way, other processes can use the CPU productively. The POSIX pause, sigsuspend and sigwait functions provide three mechanisms for suspending a process until a signal occurs.
8.5.1 The pause function
The pause function suspends the calling thread until the delivery of a signal whose action is either to execute a user-defined handler or to terminate the process. If the action is to terminate, pause does not return. If a signal is caught by the process, pause returns after the signal handler returns.
SYNOPSIS
#include <unistd.h>
int pause(void);
POSIX
The pause function always returns 1. If interrupted by a signal, pause sets errno to EINTR.
To wait for a particular signal by using pause, you must determine which signal caused pause to return. This information is not directly available, so the signal handler must set a flag for the program to check after pause returns.
Exercise 8.21
The following code segment uses pause to cause a process to wait for a particular signal by having the signal handler set the sigreceived variable to 1. What happens if a signal is delivered between the test of sigreceived and pause?
static volatile sig_atomic_t sigreceived = 0;
while(sigreceived == 0)
pause();
Answer:
The previously delivered signal does not affect pause. The pause function does not return until some other signal or another occurrence of the same signal is delivered to the process. A workable solution must test the value of sigreceived while the signal is blocked.
Exercise 8.22
What is wrong with the following attempt to prevent a signal from being delivered between the test of sigreceived and the execution of pause in Exercise 8.21?
static volatile sig_atomic_t sigreceived = 0;
int signum;
sigset_t sigset;
sigemptyset(&sigset);
sigaddset(&sigset, signum);
sigprocmask(SIG_BLOCK, &sigset, NULL);
while(sigreceived == 0)
pause();
Answer:
Unfortunately, the code segment executes pause while the signal is blocked. As a result, the program never receives the signal and pause never returns. If the program unblocks the signal before executing pause, it might receive the signal between the unblocking and the execution of pause. This event is actually more likely than it seems. If a signal is generated while the process has the signal blocked, the process receives the signal right after unblocking it.
8.5.2 The sigsuspend function
The delivery of a signal before pause was one of the major problems with the original UNIX signals, and there was no simple, reliable way to get around the problem. The program must do two operations "at once"unblock the signal and start pause. Another way of saying this is that the two operations together should be atomic (i.e., the program cannot be logically interrupted between execution of the two operations). The sigsuspend function provides a method of achieving this.
The sigsuspend function sets the signal mask to the one pointed to by sigmask and suspends the process until a signal is caught by the process. The sigsuspend function returns when the signal handler of the caught signal returns. The sigmask parameter can be used to unblock the signal the program is looking for. When sigsuspend returns, the signal mask is reset to the value it had before the sigsuspend function was called.
SYNOPSIS
#include <signal.h>
int sigsuspend(const sigset_t *sigmask);
POSIX:CX
The sigsuspend function always returns 1 and sets errno. If interrupted by a signal, sigsuspend sets errno to EINTR.
Exercise 8.23
What is wrong with the following code that uses sigsuspend to wait for a signal?
sigfillset(&sigmost);
sigdelset(&sigmost, signum);
sigsuspend(&sigmost);
Answer:
The sigmost signal set contains all signals except the one to wait for. When the process suspends, only the signal signum is unblocked and so it seems that only this signal can cause sigsuspend to return. However, the code segment has the same problem that the solution using pause had. If the signal is delivered before the start of the code segment, the process still suspends itself and deadlocks if another signum signal is not generated.
Example 8.24
The following code segment shows a correct way to wait for a single signal. Assume that a signal handler has been set up for the signum signal and that the signal handler sets sigreceived to 1.
1 static volatile sig_atomic_t sigreceived = 0;
2
3 sigset_t maskall, maskmost, maskold;
4 int signum = SIGUSR1;
5
6 sigfillset(&maskall);
7 sigfillset(&maskmost);
8 sigdelset(&maskmost, signum);
9 sigprocmask(SIG_SETMASK, &maskall, &maskold);
10 if (sigreceived == 0)
11 sigsuspend(&maskmost);
12 sigprocmask(SIG_SETMASK, &maskold, NULL);
The code omits error checking for clarity.
Example 8.24 uses three signal sets to control the blocking and unblocking of signals at the appropriate time. Lines 6 through 8 set maskall to contain all signals and maskmost to contain all signals but signum. Line 9 blocks all signals. Line 10 tests sigreceived, and line 11 suspends the process if the signal has not yet been received. Note that no signals can be caught between the testing and the suspending, since the signal is blocked at this point. The process signal mask has the value maskmost while the process is suspended, so only signum is not blocked. When sigsuspend returns, the signal must have been received.
Example 8.25
The following code segment shows a modification of Example 8.24 that allows other signals to be handled while the process is waiting for signum.
1 static volatile sig_atomic_t sigreceived = 0;
2
3 sigset_t maskblocked, maskold, maskunblocked;
4 int signum = SIGUSR1;
5
6 sigprocmask(SIG_SETMASK, NULL, &maskblocked);
7 sigprocmask(SIG_SETMASK, NULL, &maskunblocked);
8 sigaddset(&maskblocked, signum);
9 sigdelset(&maskunblocked, signum);
10 sigprocmask(SIG_BLOCK, &maskblocked, &maskold);
11 while(sigreceived == 0)
12 sigsuspend(&maskunblocked);
13 sigprocmask(SIG_SETMASK, &maskold, NULL);
The code omits error checking for clarity.
Instead of blocking all signals and then unblocking only signum, Example 8.25 does not change the other signals in the signal mask. As before, the sigreceived variable declared in line 1 is declared outside any block and has static storage class. The code assumes that sigreceived is modified only in the signal handler for signum and that signal handler sets the value to 1. Thus, only the delivery of signum can make this variable nonzero. The rest of the code starting with line 3 is assumed to be inside some function.
The three signal sets declared in line 3 are initialized to contain the currently blocked signals in lines 6, 7 and 10. Line 8 adds the signal signum to the set maskblocked if it was not already blocked, and line 9 removes signum from maskunblocked if it was not already unblocked. The consequence of these two lines is that maskblocked contains exactly those signals that were blocked at the start of the code segment, except that signum is guaranteed to be in this set. Similarly, maskunblocked contains exactly those signals that were blocked at the start of the code segment, except that signum is guaranteed not to be in this set.
Line 10 guarantees that the signum signal is blocked while the value of sigreceived is being tested. No other signals are affected. The code ensures that sigreceived does not change between its testing in line 11 and the suspending of the process in line 12. Using maskunblocked in line 12 guarantees that the signal will not be blocked while the process is suspended, allowing a generated signal to be delivered and to cause sigsuspend to return. When sigsuspend does return, the while in line 11 executes again and tests sigreceived to see if the correct signal came in. Signals other than signum may have been unblocked before entry to the code segment and delivery of these signals causes sigsuspend to return. The code tests sigreceived each time and suspends the process again until the right signal is delivered. When the while condition is false, the signal has been received and line 13 executes, restoring the signal mask to its original state.
Example 8.26
The following code segment shows a shorter, but equivalent, version of the code in Example 8.25.
1 static volatile sig_atomic_t sigreceived = 0;
2
3 sigset_t masknew, maskold;
4 int signum = SIGUSR1;
5
6 sigprocmask(SIG_SETMASK, NULL, &masknew);
7 sigaddset(&masknew, signum);
8 sigprocmask(SIG_SETMASK, &masknew, &maskold);
9 sigdelset(&masknew, signum);
10 while(sigreceived == 0)
11 sigsuspend(&masknew);
12 sigprocmask(SIG_SETMASK, &maskold, NULL);
This code omits error checking for clarity.
Lines 6 and 7 set masknew to contain the original signal mask plus signum. Line 8 modifies the signal mask to block signum. Line 9 modifies masknew again so that now it does not contain signum. This operation does not change the process signal mask or the signals that are currently blocked. The signal signum is still blocked when line 10 tests sigreceived, but it is unblocked when line 11 suspends the process because of the change made to masknew on line 9.
The code segment in Example 8.26 assumes that sigreceived is initially 0 and that the handler for signum sets sigreceived to 1. It is important that the signal be blocked when the while is testing sigreceived. Otherwise, the signal can be delivered between the test of sigreceived and the call to sigsuspend. In this case, the process blocks until another signal causes the sigsuspend to return.
Exercise 8.27
Suppose the sigsuspend in Example 8.26 returns because of a different signal. Is the signum signal blocked when the while tests sigreceived again?
Answer:
Yes, when sigsuspend returns, the signal mask has been restored to the state it had before the call to sigsuspend. The call to sigprocmask before the while guarantees that this signal is blocked.
Program 8.7 simplesuspend.c
An object that allows a program to safely block on a specific signal.
#include <errno.h>
#include <signal.h>
#include <unistd.h>
static int isinitialized = 0;
static struct sigaction oact;
static int signum = 0;
static volatile sig_atomic_t sigreceived = 0;
/* ARGSUSED */
static void catcher (int signo) {
sigreceived = 1;
}
int initsuspend (int signo) { /* set up the handler for the pause */
struct sigaction act;
if (isinitialized)
return 0;
act.sa_handler = catcher;
act.sa_flags = 0;
if ((sigfillset(&act.sa_mask) == -1) ||
(sigaction(signo, &act, &oact) == -1))
return -1;
signum = signo;
isinitialized = 1;
return 0;
}
int restore(void) {
if (!isinitialized)
return 0;
if (sigaction(signum, &oact, NULL) == -1)
return -1;
isinitialized = 0;
return 0;
}
int simplesuspend(void) {
sigset_t maskblocked, maskold, maskunblocked;
if (!isinitialized) {
errno = EINVAL;
return -1;
}
if ((sigprocmask(SIG_SETMASK, NULL, &maskblocked) == -1) ||
(sigaddset(&maskblocked, signum) == -1) ||
(sigprocmask(SIG_SETMASK, NULL, &maskunblocked) == -1) ||
(sigdelset(&maskunblocked, signum) == -1) ||
(sigprocmask(SIG_SETMASK, &maskblocked, &maskold) == -1))
return -1;
while(sigreceived == 0)
sigsuspend(&maskunblocked);
sigreceived = 0;
return sigprocmask(SIG_SETMASK, &maskold, NULL);
}
Program 8.7 shows an object implementation of functions to block on a specified signal. Before calling simplesuspend, the program calls initsuspend to set up the handler for the signal to pause on. The program calls restore to reset signal handling to the prior state.
Program 8.8 uses the functions of Program 8.7 to wait for SIGUSR1.
Program 8.8 simplesuspendtest.c
A program that waits for SIGUSR1.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int initsuspend(int signo);
int restore(void);
int simplesuspend(void);
int main(void) {
fprintf(stderr, "This is process %ld\n", (long)getpid());
for ( ; ; ) {
if (initsuspend(SIGUSR1)) {
perror("Failed to setup handler for SIGUSR1");
return 1;
}
fprintf(stderr, "Waiting for signal\n");
if (simplesuspend()) {
perror("Failed to suspend for signal");
return 1;
}
fprintf(stderr, "Got signal\n");
if (restore()) {
perror("Failed to restore original handler");
return 1;
}
}
return 1;
}
Program 8.9, which is based on the strategy of Example 8.25, uses two signals to control the setting or clearing of a flag. To use the service, a program calls initnotify with the two signals that are to be used for control. The signo1 signal handler sets the notifyflag; the signo2 signal handler clears the notifyflag. After the initialization, the program can call waitnotifyon to suspend until the notification is turned on by the delivery of a signo1 signal.
Program 8.9 notifyonoff.c
An object that provides two-signal control for turning on or off a service.
#include <errno.h>
#include <signal.h>
#include <stdio.h>
static volatile sig_atomic_t notifyflag = 1;
static int signal1 = 0;
static int signal2 = 0;
/* ARGSUSED */
static void turnon(int s) {
notifyflag = 1;
}
/* ARGSUSED */
static void turnoff(int s) {
notifyflag = 0;
}
/* ---------------------------Public functions --------------------------*/
int initnotify(int signo1, int signo2) { /* set up for the notify */
struct sigaction newact;
signal1 = signo1;
signal2 = signo2;
newact.sa_handler = turnon; /* set up signal handlers */
newact.sa_flags = 0;
if ((sigemptyset(&newact.sa_mask) == -1) ||
(sigaddset(&newact.sa_mask, signo1) == -1) ||
(sigaddset(&newact.sa_mask, signo2) == -1) ||
(sigaction(signo1, &newact, NULL) == -1))
return -1;
newact.sa_handler = turnoff;
if (sigaction(signo2, &newact, NULL) == -1)
return -1;
return 0;
}
int waitnotifyon(void) { /* Suspend until notifyflag is nonzero */
sigset_t maskblocked, maskold, maskunblocked;
if ((sigprocmask(SIG_SETMASK, NULL, &maskblocked) == -1) ||
(sigprocmask(SIG_SETMASK, NULL, &maskunblocked) == -1) ||
(sigaddset(&maskblocked, signal1) == -1) ||
(sigaddset(&maskblocked, signal2) == -1) ||
(sigdelset(&maskunblocked, signal1) == -1) ||
(sigdelset(&maskunblocked, signal2) == -1) ||
(sigprocmask(SIG_BLOCK, &maskblocked, &maskold) == -1))
return -1;
while (notifyflag == 0)
sigsuspend(&maskunblocked);
if (sigprocmask(SIG_SETMASK, &maskold, NULL) == -1)
return -1;
return 0;
}
Section 5.6 presented a simplebiff program to notify a user when mail is present. Program 8.10 shows a more sophisticated version that uses stat to determine when the size of the mail file increases. The program outputs the bell character to inform the user that new mail has arrived. This program uses the service of Program 8.9 to turn mail notification on or off without killing the process. The user sends a SIGUSR1 signal to turn on mail notification and a SIGUSR2 signal to turn off mail notification.
Program 8.10 biff.c
A biff program that uses the notifyonoff service.
#include <errno.h>
#include <limits.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include "notifyonoff.h"
#define MAILDIR "/var/mail/"
static int checkmail(char *filename) { /* is there new mail ? */
struct stat buf;
int error = 0;
static long newsize = 0;
static long oldsize = 0;
error = stat(filename, &buf); /* check the file status */
if ((error == -1) && (errno != ENOENT))
return -1; /* real error indicated by -1 return */
if (!error)
newsize = (long)buf.st_size;
else
newsize = 0;
if (newsize > oldsize)
error = 1; /* return 1 to indicate new mail */
else
error = 0; /* return 0 to indicate no new mail */
oldsize = newsize;
return error;
}
int main(int argc, char *argv[]) {
int check;
char mailfile[PATH_MAX];
struct passwd *pw;
int sleeptime;
if (argc != 2) {
fprintf(stderr, "Usage: %s sleeptime\n", argv[0]);
return 1;
}
sleeptime = atoi(argv[1]);
if ((pw = getpwuid(getuid())) == NULL) {
perror("Failed to determine login name");
return 1;
}
if (initnotify(SIGUSR1, SIGUSR2) == -1) {
perror("Failed to set up turning on/off notification");
return 1;
}
snprintf(mailfile, PATH_MAX,"%s%s",MAILDIR,pw->pw_name);
for( ; ; ) {
waitnotifyon();
sleep(sleeptime);
if ((check = checkmail(mailfile)) == -1) {
perror("Failed to check mail file");
break;
}
if (check)
fprintf(stderr, "\007");
}
return 1;
}
8.5.3 The sigwait function
The sigwait function blocks until any of the signals specified by *sigmask is pending and then removes that signal from the set of pending signals and unblocks. When sigwait returns, the number of the signal that was removed from the pending signals is stored in the location pointed to by signo.
SYNOPSIS
#include <signal.h>
int sigwait(const sigset_t *restrict sigmask,
int *restrict signo);
POSIX:CX
If successful, sigwait returns 0. If unsuccessful, sigwait returns 1 and sets errno. No mandatory errors are defined for sigwait.
Note the differences between sigwait and sigsuspend. Both functions have a first parameter that is a pointer to a signal set (sigset_t *). For sigsuspend, this set holds the new signal mask and so the signals that are not in the set are the ones that can cause sigsuspend to return. For sigwait, this parameter holds the set of signals to be waited for, so the signals in the set are the ones that can cause the sigwait to return. Unlike sigsuspend, sigwait does not change the process signal mask. The signals in sigmask should be blocked before sigwait is called.
Program 8.11 uses sigwait to count the number of times the SIGUSR1 signal is delivered to the process. Notice that no signal handler is necessary, since the signal is always blocked.
Program 8.11 countsignals.c
A program that counts the number of SIGUSR1 signals sent to it.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
int main(void) {
int signalcount = 0;
int signo;
int signum = SIGUSR1;
sigset_t sigset;
if ((sigemptyset(&sigset) == -1) ||
(sigaddset(&sigset, signum) == -1) ||
(sigprocmask(SIG_BLOCK, &sigset, NULL) == -1))
perror("Failed to block signals before sigwait");
fprintf(stderr, "This process has ID %ld\n", (long)getpid());
for ( ; ; ) {
if (sigwait(&sigset, &signo) == -1) {
perror("Failed to wait using sigwait");
return 1;
}
signalcount++;
fprintf(stderr, "Number of signals so far: %d\n", signalcount);
}
}
|
8.6 Handling Signals: Errors and Async-signal Safety
Be aware of three difficulties that can occur when signals interact with function calls. The first concerns whether POSIX functions that are interrupted by signals should be restarted. Another problem occurs when signal handlers call nonreentrant functions. A third problem involves the handling of errors that use errno.
What happens when a process catches a signal while it is executing a library function? The answer depends on the type of call. Terminal I/O can block the process for an undetermined length of time. There is no limit on how long it takes to get a key value from a keyboard or to read from a pipe. Function calls that perform such operations are sometimes characterized as "slow". Other operations, such as disk I/O, can block for short periods of time. Still others, such as getpid, do not block at all. Neither of these last types is considered to be "slow".
The slow POSIX calls are the ones that are interrupted by signals. They return when a signal is caught and the signal handler returns. The interrupted function returns 1 with errno set to EINTR. Look in the ERRORS section of the man page to see if a given function can be interrupted by a signal. If a function sets errno and one of the possible values is EINTR, the function can be interrupted. The program must handle this error explicitly and restart the system call if desired. It is not always possible to logically determine which functions fit into this category, so be sure to check the man page.
It was originally thought that the operating system needs to interrupt slow calls to allow-the user the option of canceling a blocked call. This traditional treatment of handling blocked functions has been found to add unneeded complexity to many programs. The POSIX committee decided that new functions (such as those in the POSIX threads extension) would never set errno to EINTR. However, the behavior of traditional functions such as read and write was not changed. Appendix B gives a restart library of wrappers that restart common interruptible functions such as read and write.
Recall that a function is async-signal safe if it can be safely called from within a signal handler. Many POSIX library functions are not async-signal safe because they use static data structures, call malloc or free, or use global data structures in a nonreentrant way. Consequently, a single process might not correctly execute concurrent calls to these functions.
Normally this is not a problem, but signals add concurrency to a program. Since signals occur asynchronously, a process may catch a signal while it is executing a library function. (For example, suppose the program interrupts a strtok call and executes another strtok in the signal handler. What happens when the first call resumes?) You must therefore be careful when calling library functions from inside signal handlers. Table 8.2 lists the functions that POSIX guarantees are safe to call from a signal handler. Notice that functions such as fprintf from the C standard I/O library are not on the list.
Signal handlers can be entered asynchronously, that is, at any time. Care must be taken so that they do not interfere with error handling in the rest of the program. Suppose a function reports an error by returning -1 and setting errno. What happens if a signal is caught before the error message is printed? If the signal handler calls a function that changes errno, an incorrect error might be reported. As a general rule, signal handlers should save and restore errno if they call functions that might change errno.
Example 8.28
The following function can be used as a signal handler. The myhandler saves the value of errno on entry and restores it on return.
void myhandler(int signo) {
int esaved;
esaved = errno;
write(STDOUT_FILENO, "Got a signal\n", 13);
errno = esaved;
}
Table 8.2. Functions that POSIX guarantees to be async-signal safe._Exit | getpid | sigaddset | _exit | getppid | sigdelset | accept | getsockname | sigemptyset | access | getsockopt | sigfillset | aio_error | getuid | sigismember | aio_return | kill | signal | aio_suspend | link | sigpause | alarm | listen | sigpending | bind | lseek | sigprocmask | cfgetispeed | lstat | sigqueue | cfgetospeed | mkdir | sigset | cfsetispeed | mkfifo | sigsuspend | cfsetospeed | open | sleep | chdir | pathconf | socket | chmod | pause | socketpair | chown | pipe | stat | clock_gettime | poll | symlink | close | posix_trace_event | sysconf | connect | pselect | tcdrain | creat | raise | tcflow | dup | read | tcflush | dup2 | readlink | tcgetattr | execle | recv | tcgetpgrp | execve | recvfrom | tcsendbreak | fchmod | recvmsg | tcsetattr | fchown | rename | tcsetpgrp | fcntl | rmdir | time | fdatasync | select | timer_getoverrun | fork | sem_post | timer_gettime | fpathconf | send | timer_settime | fstat | sendmsg | times | fsync | sendto | umask | ftruncate | setgid | uname | getegid | setpgid | unlink | geteuid | setsid | utime | getgid | setsockopt | wait | getgroups | setuid | waitpid | getpeername | shutdown | write | getpgrp | sigaction | |
Signal handling is complicated, but here are a few useful rules.
When in doubt, explicitly restart library calls within a program or use the restart library of Appendix B. Check each library function used in a signal handler to make sure that it is on the list of async-signal safe functions. Carefully analyze the potential interactions between a signal handler that changes an external variable and other program code that accesses the variable. Block signals to prevent unwanted interactions. Save and restore errno when appropriate.
8.7 Program Control with siglongjmp and sigsetjmp
Programs sometimes use signals to handle errors that are not fatal but that can occur in many places in a program. For example, a user might want to avoid terminating a program while aborting a long calculation or an I/O operation that has blocked for a long time. The program's response to Ctrl-C should be to start over at the beginning (or at some other specified location). A similar situation occurs when the program has nested prompts or menus and should start over when a user misenters a response. Object-oriented languages often handle these situations by throwing exceptions that are caught elsewhere. C programs can use signals indirectly or directly to handle this type of problem.
In the indirect approach, the signal handler for SIGINT sets a flag in response to Ctrl-C. The program tests the flag in strategic places and returns to the desired termination point if the flag is set. The indirect approach is complicated, since the program might have to return through several layers of functions. At each return layer, the program tests the flag for this special case.
In the direct approach, the signal handler jumps directly back to the desired termination point. The jump requires unraveling the program stack. A pair of functions, sigsetjmp and siglongjmp, provides this capability. The sigsetjmp function is analogous to a statement label, and siglongjmp function is analogous to a goto statement. The main difference is that the sigsetjmp and siglongjmp pair cleans up the stack and signal states as well as doing the jump.
Call the sigsetjmp at the point the program is to return to. The sigsetjmp provides a marker in the program similar to a statement label. The caller must provide a buffer, env, of type sigjmp_buf that sigsetjmp initializes to the collection of information needed for a jump back to that marker. If savemask is nonzero, the current state of the signal mask is saved in the env buffer. When the program calls sigsetjmp directly, it returns 0. To jump back to the sigsetjmp point from a signal handler, execute siglongjmp with the same sigjmp_buf variable. The call makes it appear that the program is returning from sigsetjmp with a return value of val.
SYNOPSIS
#include <setjmp.h>
void siglongjmp(sigjmp_buf env, int val);
int sigsetjmp(sigjmp_buf env, int savemask);
POSIX:CX
No errors are defined for siglongjmp. The sigsetjmp returns 0 when invoked directly and the val parameter value when invoked by calling siglongjmp.
The C standard library provides functions setjmp and longjmp for the types of jumps referred to above, but the action of these functions on the signal mask is system dependent. The sigsetjmp function allows the program to specify whether the signal mask should be reset when a signal handler calls this function. The siglongjmp function causes the signal mask to be restored if and only if the value of savemask is nonzero. The val parameter of siglongjmp specifies the value that is to be returned at the point set by sigsetjmp.
Program 8.12 sigjmp.c
Code to set up a signal handler that returns to the main loop when Ctrl-C is typed.
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static sigjmp_buf jmpbuf;
static volatile sig_atomic_t jumpok = 0;
/* ARGSUSED */
static void chandler(int signo) {
if (jumpok == 0) return;
siglongjmp(jmpbuf, 1);
}
int main(void) {
struct sigaction act;
act.sa_flags = 0;
act.sa_handler = chandler;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGINT, &act, NULL) == -1)) {
perror("Failed to set up SIGINT handler");
return 1;
}
/* stuff goes here */
fprintf(stderr, "This is process %ld\n", (long)getpid());
if (sigsetjmp(jmpbuf, 1))
fprintf(stderr, "Returned to main loop due to ^c\n");
jumpok = 1;
for ( ; ; )
; /* main loop goes here */
}
Program 8.12 shows how to set up a SIGINT handler that causes the program to return to the main loop when Ctrl-C is typed. It is important to execute sigsetjmp before calling siglongjmp in order to establish a point of return. The call to sigaction should appear before the sigsetjmp so that it is called only once. To prevent the signal handler from calling siglongjmp before the program executes sigsetjmp, Program 8.12 uses the flag jumpok. The signal handler tests this flag before calling siglongjmp.
|
8.8 Programming with Asynchronous I/O
Normally, when performing a read or write, a process blocks until the I/O completes. Some types of performance-critical applications would rather initiate the request and continue executing, allowing the I/O operation to be processed asynchronously with program execution. The older method of asynchronous I/O uses either SIGPOLL or SIGIO to notify a process when I/O is available. The mechanism for using these signals is set up with ioctl. This section discusses the newer version which is part of the POSIX:AIO Asynchronous I/O Extension that was introduced with the POSIX:RTS Realtime Extension.
The POSIX:AIO Extension bases its definition of asynchronous I/O on four main functions. The aio_read function allows a process to queue a request for reading on an open file descriptor. The aio_write function queues requests for writing. The aio_return function returns the status of an asynchronous I/O operation after it completes, and the aio_error function returns the error status. A fifth function, aio_cancel, allows cancellation of asynchronous I/O operations that are already in progress.
The aio_read and aio_write functions take a single parameter, aiocbp, which is a pointer to an asynchronous I/O control block. The aio_read function reads aiocbp->aio_bytes from the file associated with aiocbp->aio_fildes into the buffer specified by aiocbp->aio_buf. The function returns when the request is queued. The aio_write function behaves similarly.
SYNOPSIS
#include <aio.h>
int aio_read(struct aiocb *aiocbp);
int aio_write(struct aiocb *aiocbp);
POSIX:AIO
If the request was successfully queued, aio_read and aio_write return 0. If unsuccessful, these functions return 1 and set errno. The following table lists the mandatory errors for these functions that are specific to asynchronous I/O.
|
EAGAIN | system did not have the resources to queue request (B) | EBADF | aiocbp->aio_fildes invalid (BA) | EFBIG | aiocbp->aio_offset exceeds maximum (aio_write) (BA) | ECANCELED | request canceled because of explicit aio_cancel (A) | EINVAL | invalid member of aiocbp (BA) | EOVERFLOW | aiocbp->aio_offset exceeds maximum (aio_read) (BA) |
Errors that occur before the return of aio_read or aio_write have a B tag. These are values that errno can have if the call returns 1. The errors that may occur after the return have an A tag. These errors are returned by a subsequent call to aio_error. The aio_read and aio_write functions also have the mandatory errors of their respective read and write counterparts.
The struct aiocb structure has at least the following members.
int aio_fildes; /* file descriptor */
volatile void *aio_buf; /* buffer location */
size_t aio_nbytes; /* length of transfer */
off_t aio_offset; /* file offset */
int aio_reqprio; /* request priority offset */
struct sigevent aio_sigevent; /* signal number and value */
int aio_lio_opcode; /* listio operation */
The first three members of this structure are similar to the parameters in an ordinary read or write function. The aio_offset specifies the starting position in the file for the I/O. If the implementation supports user scheduling (_POSIX_PRIORITIZED_IO and _POSIX_PRIORITY_SCHEDULING are defined), aio_reqprio lowers the priority of the request. The aio_sigevent field specifies how the calling process is notified of the completion. If aio_sigevent.sigev_notify has the value SIGEV_NONE, the operating system does not generate a signal when the I/O completes. If aio_sigevent.sigev_notify is SIGEV_SIGNAL, the operating system generates the signal specified in aio_sigevent.sigev_signo. The aio_lio_opcode function is used by the lio_listio function (not discussed here) to submit multiple I/O requests.
The aio_error and aio_return functions return the status of the I/O operation designated by aiocbp. Monitor the progress of the asynchronous I/O operation with aio_error. When the operation completes, call aio_return to retrieve the number of bytes read or written.
SYNOPSIS
#include <aio.h>
ssize_t aio_return(struct aiocb *aiocbp);
int aio_error(const struct aiocb *aiocbp);
POSIX:AIO
The aio_error function returns 0 when the I/O operation has completed successfully or EINPROGRESS if the I/O operation is still executing. If the operation fails, aio_error returns the error code associated with the failure. This error status corresponds to the value of errno that would have been set by the corresponding read or write function. The aio_return function returns the status of a completed underlying I/O operation. If the operation was successful, the return value is the number of bytes read or written. Once aio_return has been called, neither aio_return nor aio_error should be called for the same struct aiocb until another asynchronous operation is started with this buffer. The results of aio_return are undefined if the asynchronous I/O has not yet completed.
POSIX asynchronous I/O can be used either with or without signals, depending on the setting of the sigev_notify field of the struct aiocb. Programs 8.13 and 8.14 illustrate how to do asynchronous I/O with signals. The general idea is to set up a signal handler that does all the work after the initial I/O operation is started.
Program 8.13 is a program for copying one file to another. The reading from the first file is done with asynchronous I/O, and the writing to the second file is done with ordinary I/O. This approach is appropriate if the input is from a pipe or a network connection that might block for long periods of time and if the output is to an ordinary file. Program 8.13 takes two filenames as command-line arguments and opens the first for reading and the second for writing. The program then calls the initsignal function to set up a signal handler and initread to start the first read. The signal is set up as a realtime signal as described in Section 9.4. The main program's loop calls dowork and checks to see if the asynchronous copy has completed with a call to getdone. When the copying is done, the program displays the number of bytes copied or an error message.
Program 8.14 contains the signal handler for the asynchronous I/O as well as initialization routines. The initread function sets up a struct aiocb structure for reading asynchronously and saves the output file descriptor in a global variable. It initializes three additional global variables and starts the first read with a call to readstart.
Program 8.14 keeps track of the first error that occurs in globalerror and the total number of bytes transferred in totalbytes. A doneflag has type sig_atomic_t so that it can be accessed atomically. This is necessary since it is modified asynchronously by the signal handler and can be read from the main program with a call to getdone. The variables globalerror and totalbytes are only available after the I/O is complete, so they are never accessed concurrently by the signal handler and the main program.
The signal handler in Program 8.14 uses the struct aiocb that is stored in the global variable aiocb. The signal handler starts by saving errno so that it can be restored when the handler returns. If the handler detects an error, it calls seterror to store errno in the variable globalerror, provided that this was the first error detected. The signal handler sets the doneflag if an error occurs or end-of-file is detected. Otherwise, the signal handler does a write to the output file descriptor and starts the next read.
Program 8.13 asyncsignalmain.c
A main program that uses asynchronous I/O with signals to copy a file while doing other work.
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include "asyncmonitorsignal.h"
#define BLKSIZE 1024
#define MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)
void dowork(void);
int main(int argc, char *argv[]) {
char buf[BLKSIZE];
int done = 0;
int error;
int fd1;
int fd2;
/* open the file descriptors for I/O */
if (argc != 3) {
fprintf(stderr, "Usage: %s filename1 filename2\n", argv[0]);
return 1;
}
if ((fd1 = open(argv[1], O_RDONLY)) == -1) {
fprintf(stderr, "Failed to open %s:%s\n", argv[1], strerror(errno));
return 1;
}
if ((fd2 = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, MODE)) == -1) {
fprintf(stderr, "Failed to open %s: %s\n", argv[2], strerror(errno));
return 1;
}
if (initsignal(SIGRTMAX) == -1) {
perror("Failed to initialize signal");
return 1;
}
if (initread(fd1, fd2, SIGRTMAX, buf, BLKSIZE) == -1) {
perror("Failed to initate the first read");
return 1;
}
for ( ; ; ) {
dowork();
if (!done)
if (done = getdone())
if (error = geterror())
fprintf(stderr, "Failed to copy file:%s\n", strerror(error));
else
fprintf(stderr, "Copy successful, %d bytes\n", getbytes());
}
}
Program 8.14 asyncmonitorsignal.c
Utility functions for handling asynchronous I/O with signals.
#include <aio.h>
#include <errno.h>
#include <signal.h>
#include "restart.h"
static struct aiocb aiocb;
static sig_atomic_t doneflag;
static int fdout;
static int globalerror;
static int totalbytes;
static int readstart();
static void seterror(int error);
/* ARGSUSED */
static void aiohandler(int signo, siginfo_t *info, void *context) {
int myerrno;
int mystatus;
int serrno;
serrno = errno;
myerrno = aio_error(&aiocb);
if (myerrno == EINPROGRESS) {
errno = serrno;
return;
}
if (myerrno) {
seterror(myerrno);
errno = serrno;
return;
}
mystatus = aio_return(&aiocb);
totalbytes += mystatus;
if (mystatus == 0)
doneflag = 1;
else if (r_write(fdout, (char *)aiocb.aio_buf, mystatus) == -1)
seterror(errno);
else if (readstart() == -1)
seterror(errno);
errno = serrno;
}
static int readstart() { /* start an asynchronous read */
int error;
if (error = aio_read(&aiocb))
seterror(errno);
return error;
}
static void seterror(int error) { /* update globalerror if zero */
if (!globalerror)
globalerror = error;
doneflag = 1;
}
/* --------------------------Public Functions ---------------------------- */
int getbytes() {
if (doneflag)
return totalbytes;
errno = EINVAL;
return -1;
}
int getdone() { /* check for done */
return doneflag;
}
int geterror() { /* return the globalerror value if doneflag */
if (doneflag)
return globalerror;
errno = EINVAL;
return errno;
}
int initread(int fdread, int fdwrite, int signo, char *buf, int bufsize) {
aiocb.aio_fildes = fdread; /* set up structure */
aiocb.aio_offset = 0;
aiocb.aio_buf = (void *)buf;
aiocb.aio_nbytes = bufsize;
aiocb.aio_sigevent.sigev_notify = SIGEV_SIGNAL;
aiocb.aio_sigevent.sigev_signo = signo;
aiocb.aio_sigevent.sigev_value.sival_ptr = &aiocb;
fdout = fdwrite;
doneflag = 0;
globalerror = 0;
totalbytes = 0;
return readstart(); /* start first read */
}
int initsignal(int signo) { /* set up the handler for the async I/O */
struct sigaction newact;
newact.sa_sigaction = aiohandler;
newact.sa_flags = SA_SIGINFO;
if ((sigemptyset(&newact.sa_mask) == -1) ||
(sigaction(signo, &newact, NULL) == -1))
return -1;
return 0;
}
int suspenduntilmaybeready() { /* return 1 if done, 0 otherwise */
const struct aiocb *aiocblist;
aiocblist = &aiocb;
aio_suspend(&aiocblist, 1, NULL);
return doneflag;
}
The r_write function from the restart library in Appendix B guarantees that all the bytes requested are written if possible. Program 8.14 also contains the suspenduntilmaybeready function, which is not used in Program 8.13 but will be described later.
The signal handler does not output any error messages. Output from an asynchronous signal handler can interfere with I/O operations in the main program, and the standard library routines such as fprintf and perror may not be safe to use in signal handlers. Instead, the signal handler just keeps track of the errno value of the first error that occurred. The main program can then print an error message, using strerror.
Example 8.29
The following command line calls Program 8.13 to copy from pipe1 to pipe2.
asyncsignalmain pipe1 pipe2
Asynchronous I/O can be used without signals if the application has to do other work that can be broken into small pieces. After each piece of work, the program calls aio_error to see if the I/O operation has completed and handles the result if it has. This procedure is called polling.
Program 8.15 shows a main program that takes a number of filenames as parameters. The program reads each file, using asynchronous I/O, and calls processbuffer to process each input. While this is going on, the program calls dowork in a loop.
Program 8.15 uses utility functions from Program 8.16. The main program starts by opening each file and calling initaio to set up the appropriate information for each descriptor as an entry in the static array defined in Program 8.16. Each element of the array contains a struct aiocb structure for holding I/O and control information. Next, the first read for each file is started with a call to readstart. The program does not use signal handlers. The main program executes a loop in which it calls readcheck to check the status of each operation after each piece of dowork. If a read has completed, the main program calls processbuffer to handle the bytes read and starts a new asynchronous read operation. The main program keeps track of which file reads have completed (either successfully or due to an error) in an array called done.
Program 8.15 asyncpollmain.c
A main program that uses polling with asynchronous I/O to process input from multiple file descriptors while doing other work.
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include "asyncmonitorpoll.h"
void dowork(void);
void processbuffer(int which, char *buf, int bufsize);
int main(int argc, char *argv[]) {
char *buf;
int done[NUMOPS];
int fd[NUMOPS];
int i;
int numbytes, numfiles;
if (argc < 2) {
fprintf(stderr, "Usage: %s filename1 filename2 ...\n", argv[0]);
return 1;
} else if (argc > NUMOPS + 1) {
fprintf(stderr, "%s: only supports %d simultaneous operations\n",
argv[0], NUMOPS);
return 1;
}
numfiles = argc - 1;
for (i = 0; i < numfiles; i++) { /* set up the I/O operations */
done[i] = 0;
if ((fd[i] = open(argv[i+1], O_RDONLY)) == -1) {
fprintf(stderr, "Failed to open %s:%s\n", argv[i+1], strerror(errno));
return 1;
}
if (initaio(fd[i], i) == -1) {
fprintf(stderr, "Failed to setup I/O op %d:%s\n", i, strerror(errno));
return 1;
}
if (readstart(i) == -1) {
fprintf(stderr, "Failed to start read %d:%s\n", i, strerror(errno));
return 1;
}
}
for ( ; ; ) { /* loop and poll */
dowork();
for (i = 0; i < numfiles; i++) {
if (done[i])
continue;
numbytes = readcheck(i, &buf);
if ((numbytes == -1) && (errno == EINPROGRESS))
continue;
if (numbytes <= 0) {
if (numbytes == 0)
fprintf(stderr, "End of file on %d\n", i);
else
fprintf(stderr, "Failed to read %d:%s\n", i, strerror(errno));
done[i] = 1;
continue;
}
processbuffer(i, buf, numbytes);
reinit(i);
if (readstart(i) == -1) {
fprintf(stderr, "Failed to start read %d:%s\n", i, strerror(errno));
done[i] = 1;
}
}
}
}
Program 8.16 asyncmonitorpoll.c
Utility functions for handling asynchronous I/O with polling.
#include <aio.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include "asyncmonitorpoll.h"
#define BLKSIZE 1024 /* size of blocks to be read */
typedef struct {
char buf[BLKSIZE];
ssize_t bytes;
struct aiocb control;
int doneflag;
int startedflag;
} aio_t;
static aio_t iops[NUMOPS]; /* information for the op */
/* -------------------------- Public Functions ----------------------------- */
int initaio(int fd, int handle) { /* set up control structure */
if (handle >= NUMOPS) {
errno = EINVAL;
return -1;
}
iops[handle].control.aio_fildes = fd; /* I/O operation on fd */
iops[handle].control.aio_offset = 0;
iops[handle].control.aio_buf = (void *)iops[handle].buf;
iops[handle].control.aio_nbytes = BLKSIZE;
iops[handle].control.aio_sigevent.sigev_notify = SIGEV_NONE;
iops[handle].doneflag = 0;
iops[handle].startedflag = 0;
iops[handle].bytes = 0;
return 0;
}
/* return -1 if not done or error
errno = EINPROGRESS if not done
otherwise, return number of bytes read with *buf pointing to buffer
*/
int readcheck(int handle, char **bufp) { /* see if read for handle is done */
int error;
ssize_t numbytes;
struct aiocb *thisp;
thisp = &(iops[handle].control); /* get a pointer to the aiocp */
if (iops[handle].doneflag) { /* done already, don't call aio_return */
numbytes = iops[handle].bytes;
*bufp = (char *)iops[handle].control.aio_buf; /* set pointer to buffer */
return numbytes;
}
error = aio_error(thisp);
if (error) {
errno = error;
return -1;
}
numbytes = aio_return(thisp);
iops[handle].bytes = numbytes;
*bufp = (char *)iops[handle].control.aio_buf; /* set pointer to buffer */
iops[handle].doneflag = 1;
return numbytes;
}
int readstart(int handle) { /* start read for I/O corresponding to handle */
int error;
struct aiocb *thisp;
thisp = &(iops[handle].control); /* get a pointer to the aiocp */
if (iops[handle].startedflag) { /* already started */
errno = EINVAL;
return -1;
}
if ((error = aio_read(thisp)) == -1) {
errno = error;
return -1;
}
iops[handle].startedflag = 1;
return 0;
}
void reinit(int handle) { /* must be called before doing another readstart */
iops[handle].doneflag = 0;
iops[handle].startedflag = 0;
iops[handle].bytes = 0;
}
Example 8.30
The following command line calls Program 8.15 for inputs pipe1, pipe2 and pipe3.
asyncpollmain pipe1 pipe2 pipe3
What if a program starts asynchronous I/O operations as in Program 8.13 and runs out of other work to do? Here are several options for avoiding busy waiting.
Switch to using standard blocking I/O with select. Use signals as in Program 8.13, or use pause or sigsuspend in a loop. Do not use sigwait, since this function requires the signals to be blocked. Switch to using signals as in Program 8.15 by blocking the signal and calling sigwait in a loop. Use aio_suspend.
The aio_suspend function takes three parameters, an array of pointers to struct aiocb structures, the number of these structures and a timeout specification. If the timeout specification is not NULL, aio_suspend may return after the specified time. Otherwise, it returns when at least one of the I/O operations has completed and aio_error no longer returns EINPROGRESS. Any of the entries in the array may be NULL, in which case they are ignored.
SYNOPSIS
#include <aio.h>
int aio_suspend(const struct aiocb * const list[], int nent,
const struct timespec *timeout);
POSIX:AIO
If successful, aio_suspend returns 0. If unsuccessful, aio_suspend returns 1 and sets errno. The following table lists the mandatory errors for aio_suspend.
|
EAGAIN | timeout occurred before asynchronous I/O completed | EINTR | a signal interrupted aio_suspend |
Program 8.14 has a suspenduntilmaybeready function that uses aio_suspend to suspend the calling process until the asynchronous I/O operation is ready. It can be called from the main program of Program 8.13 in place of dowork when there is no other work to be done. In this case, there is only one asynchronous I/O operation and the function returns 1 if it has completed, and 0 otherwise.
The aio_cancel function attempts to cancel one or more asynchronous I/O requests on the file descriptor fildes. The aiocbp parameter points to the control block for the request to be canceled. If aiocbp is NULL, the aio_cancel function attempts to cancel all pending requests on fildes.
SYNOPSIS
#include <aio.h>
int aio_cancel(int fildes, struct aioch *aiocbp);
POSIX:AIO
The aio_cancel function returns AIO_CANCELED if the requested operations were successfully canceled or AIO_NOTCANCELED if at least one of the requested operations could not be canceled because it was in progress. It returns AIO_ALLDONE if all the operations have already completed. Otherwise, the aio_cancel function returns 1 and sets errno. The aio_cancel function sets errno to EBADF if the fildes parameter does not correspond to a valid file descriptor.
Exercise 8.31
How would you modify Programs 8.15 and 8.16 so that a SIGUSR1 signal cancels all the asynchronous I/O operations without affecting the rest of the program?
Answer:
Set up a signal handler for SIGUSR1 in asyncmonitorpoll that cancels all pending operations using aio_cancel. Also set a flag signifying that all I/O has been canceled. The readcheck function checks this flag. If the flag is set, readcheck returns 1 with errno set to ECANCELED.
8.9 Exercise: Dumping Statistics
The atexit function showtimes of Program 2.10 on page 53 can almost work as a signal handler to report the amount of CPU time used. It needs an unused parameter for the signal number, and the functions used in showtimes must be async-signal safe. Implement a signal handler for SIGUSR1 that outputs this information to standard error. The program probably produces correct output most of the time, even though it calls functions such as perror and fprintf that are not async-signal safe.
Read your system documentation and try to find out if these functions are async-signal safe on your system. This information may be difficult to find. If you are using unsafe functions, try to make your program fail. This may not be easy to do, as it may happen very rarely. In any case, write a version that uses only those functions that POSIX requires to be async-signal safe as listed in Table 8.2 on page 285. You can avoid using perror by producing your own error messages. You will need to write your own functions for converting a double value to a string. Section 13.7 gives a signal-safe implementation of perror that uses mutex locks from the POSIX:THR Threads Extension.
|
8.10 Exercise: Spooling a Slow Device
This exercise uses asynchronous I/O to overlap the handling of I/O from a slow device with other program calculations. Examples include printing or performing a file transfer over a slow modem. Another example is a program that plays an audio file in the background while doing something else. In these examples, a program reads from a disk file and writes to a slow device.
Write a program that uses aio_read and aio_write to transfer data to a slow device. The source of information is a disk file. Model your program after Programs 8.13 and 8.14. Pass the name of the input and output files as command-line arguments.
The main program still initiates the first read. However, now the signal handler initiates an aio_write if the asynchronous read completes. Similarly, when the asynchronous write completes, the signal handler initiates another aio_read.
Begin testing with two named pipes for the input and the output. Then, use a disk file for the output. Redirect the output from the pipe to a file and use diff to check that they are the same. If a workstation with a supported audio device is available, use an audio file on disk as input and "/dev/audio" as the output device.
Keep statistics on the number of bytes transferred and the number of write operations needed. Add a signal handler that outputs this information when the program receives a SIGUSR1 signal. The statistics can be kept in global variables. Block signals when necessary to prevent different signal handlers from accessing these shared variables concurrently.
This program is particularly interesting when the output goes to the audio device. It is possible to tell when the program is computing by the gaps that occur in the audio output. Estimate the percentage of time spent handling I/O as compared with calculation time.
8.11 Additional Reading
Advanced Programming in the UNIX Environment by Stevens [112] has a good historical overview of signals. Beginning Linux Programming, 2nd ed. by Stones and Matthew discusses signals in Linux [117]. The article "Specialization tools and techniques for systematic optimization of system software" by McNamee et al. [80] introduces a toolkit for writing efficient system code and uses signal handling as a principal case study for the toolkit.
|
Chapter 9. Times and Timers
Operating systems use timers for purposes such as process scheduling, timeouts for network protocols, and periodic updates of system statistics. Applications access system time and timer functions to measure performance or to identify the time when events occur. Applications also use timers to implement protocols and to control interaction with users such as that needed for rate-limited presentations. This chapter discusses representations of time in the POSIX base standard as well as interval timers in the POSIX:XSI Extension and POSIX:TMR Extension. The chapter also explores concepts such as timer drift and timer overrun and demonstrates how to use POSIX realtime signals with timers.
Learn how time is represented Experiment with interval timers Explore interactions of timers and signals Use timers to assess performance Understand POSIX realtime signals
|
|
9.1 POSIX Times
POSIX specifies that systems should keep time in terms of seconds since the Epoch and that each day be accounted for by exactly 86,400 seconds. The Epoch is defined as 00:00 (midnight), January 1, 1970, Coordinated Universal Time (also called UTC, Greenwich Mean Time or GMT). POSIX does not specify how an implementation should align its system time with the actual time and date.
Most operations need to be measured with timers with greater than one-second resolution. Two POSIX extensions, the POSIX:XSI Extension and the POSIX:TMR Extension, define time resolutions of microseconds and nanoseconds, respectively.
9.1.1 Expressing time in seconds since the Epoch
The POSIX base standard supports only a time resolution of seconds and expresses time since the Epoch using a time_t type, which is usually implemented as a long. A program can access the system time (expressed in seconds since the Epoch) by calling the time function. If tloc is not NULL, the time function also stores the time in *tloc.
SYNOPSIS
#include <time.h>
time_t time(time_t *tloc);
POSIX:CX
If successful, time returns the number of seconds since the Epoch. If unsuccessful, time returns (time_t) 1. POSIX does not define any mandatory errors for time.
Exercise 9.1
The time_t type is usually implemented as a long. If a long is 32 bits, at approximately what date would time_t overflow? (Remember that one bit is used for the sign.) What date would cause an overflow if an unsigned long were used? What date would cause an overflow if a 64-bit data type were used?
Answer:
For a 32-bit long, time would overflow in approximately 68 years from January 1, 1970, so the system would not have a "Y2K" problem until the year 2038. For a time_t value that is an unsigned long, the overflow would occur in the year 2106, but this would not allow time to return an error. For a 64-bit data type, the overflow would not occur for another 292 billion years, long after the sun has died!
The difftime function computes the difference between two calendar times of type time_t, making it convenient for calculations involving time. The difftime function has two time_t parameters and returns a double containing the first parameter minus the second.
SYNOPSIS
#include <time.h>
double difftime(time_t time1, time_t time0);
POSIX:CX
No errors are defined for difftime.
Example 9.2 simpletiming.c
The following program calculates the wall-clock time that it takes to execute function_to_time.
# include <stdio.h>
# include <time.h>
void function_to_time(void);
int main(void) {
time_t tstart;
tstart = time(NULL);
function_to_time();
printf("function_to_time took %f seconds of elapsed time\n",
difftime(time(NULL), tstart));
return 0;
}
Example 9.2 uses a time resolution of one second, which may not be accurate enough unless function_to_time involves substantial computation or waiting. Also, the time function measures wall-clock or elapsed time, which may not meaningfully reflect the amount of CPU time used. Section 9.1.5 presents alternative methods of timing code.
9.1.2 Displaying date and time
The time_t type is convenient for calculations requiring the difference between times, but it is cumbersome for printing dates. Also, a program should adjust dates and times to account for factors such as time zone, daylight-saving time and leap seconds.
The localtime function takes a parameter specifying the seconds since the Epoch and returns a structure with the components of the time (such as day, month and year) adjusted for local requirements. The asctime function converts the structure returned by localtime to a string. The ctime function is equivalent to asctime(localtime(clock)). The gmtime function takes a parameter representing seconds since the Epoch and returns a structure with the components of time expressed as Coordinated Universal Time (UTC).
SYNOPSIS
#include <time.h>
char *asctime(const struct tm *timeptr);
char *ctime(const time_t *clock);
struct tm *gmtime(const time_t *timer);
struct tm *localtime(const time_t *timer);
POSIX:CX
No errors are defined for these functions.
The ctime function takes one parameter, a pointer to a variable of type time_t, and returns a pointer to a 26-character English-language string. The ctime function takes into account both the time zone and daylight saving time. Each of the fields in the string has a constant width. The string might be stored as follows.
Sun Oct 06 02:21:35 1986\n\0
Example 9.3 timeprint.c
The following program prints the date and time. The printf format did not include '\n' because ctime returns a string that ends in a newline.
#include <stdio.h>
#include <time.h>
int main(void) {
time_t tcurrent;
tcurrent = time(NULL);
printf("The current time is %s", ctime(&tcurrent));
return 0;
}
Exercise 9.4 badtiming.c
What is wrong with the following program that prints the time before and after the function function_to_time executes?
#include <stdio.h>
#include <time.h>
void function_to_time(void);
int main(void) {
time_t tend, tstart;
tstart = time(NULL);
function_to_time();
tend = time(NULL);
printf("The time before was %sThe time after was %s",
ctime(&tstart), ctime(&tend));
return 0;
}
Answer:
The ctime function uses static storage to hold the time string. Both calls to ctime store the string in the same place, so the second call may overwrite the first value before it is used. Most likely, both times will be printed as the same value.
The gmtime and localtime functions break the time into separate fields to make it easy for programs to output components of the date or time. ISO C defines the struct tm structure to have the following members.
int tm_sec; /* seconds after the minute [0,60] */
int tm_min; /* minutes after the hour [0,59] */
int tm_hour; /* hours since midnight [0,23] */
int tm_mday; /* day of the month [1,31] */
int tm_mon; /* months since January [0,11] */
int tm_year; /* years since 1900 */
int tm_wday; /* days since Sunday [0,6] */
int tm_yday; /* days since January 1 [0,365] */
int tm_isdst; /* flag indicating daylight-saving time */
Example 9.5
The following code segment prints the number of days since the beginning of the year.
struct tm *tcurrent;
tcurrent = localtime(time(NULL));
printf("%d days have elapsed since Jan 1\n", tcurrent->tm_yday);
Unfortunately, the asctime, ctime and localtime are not thread-safe. The POSIX:TSF Thread Safe Extension specifies thread-safe alternatives that have a caller-supplied buffer as an additional parameter.
SYNOPSIS
#include <time.h>
char *asctime_r(const struct tm *restrict timeptr, char *restrict buf);
char *ctime_r(const time_t *clock, char *buf);
struct tm *gmtime_r(const time_t *restrict timer,
struct tm *restrict result);
struct tm *localtime_r(const time_t *restrict timer,
struct tm *restrict result);
POSIX:TSF
If successful, these functions return a pointer to the parameter holding the result. For asctime_r and ctime_r, the result is in buf. For gmtime_r and localtime_r, the result is in result. If unsuccessful, these functions return a NULL pointer.
Example 9.6
The following code segment prints the number of days since the beginning of the year, using the thread-safe localtime_r function.
struct tm tbuffer;
if (localtime_r(time(NULL), &tbuffer) != NULL)
printf("%d days have elapsed since Jan 1\n", tbuffer.tm_yday);
9.1.3 Using struct timeval to express time
A time scale of seconds is too coarse for timing programs or controlling program events. The POSIX:XSI Extension uses the struct timeval structure to express time on a finer scale. The struct timeval structure includes the following members.
time_t tv_sec; /* seconds since the Epoch */
time_t tv_usec; /* and microseconds */
Certain POSIX functions that support a timeout option (e.g., select) specify the timeout values by using variables of type struct timeval. In this case, the structure holds the length of the interval in seconds and microseconds.
The gettimeofday function retrieves the system time in seconds and microseconds since the Epoch. The struct timeval structure pointed to by tp receives the retrieved time. The tzp pointer must be NULL and is included for historical reasons.
SYNOPSIS
#include <sys/time.h>
int gettimeofday(struct timeval *restrict tp, void *restrict tzp);
POSIX:XSI
The gettimeofday function returns 0. No values are reserved to indicate an error. However, many systems have implemented gettimeofday so that it returns 1 and sets errno if unsuccessful. Our programs check to make sure gettimeofday returns 0.
Program 9.1 shows how to measure the running time of function_to_time by using gettimeofday. The gettimeofdaytiming program reads the time before and after calling function_to_time and prints the time difference as a number of microseconds.
Exercise 9.7
What is the maximum duration that can be timed by the method of Program 9.1? How could you extend this?
Answer:
If a long is 32 bits, the maximum duration is 231 1 microseconds, or approximately 35 minutes. You could extend this by using a long long (usually 64 bits) for timedif. Changes must be made in the declaration of timedif, the definition of MILLION (1000000LL) and the format specifier (lld).
Program 9.1 gettimeofdaytiming.c
A program that measures the running time of a function by using gettimeofday.
#include <stdio.h>
#include <sys/time.h>
#define MILLION 1000000L
void function_to_time(void);
int main(void) {
long timedif;
struct timeval tpend;
struct timeval tpstart;
if (gettimeofday(&tpstart, NULL)) {
fprintf(stderr, "Failed to get start time\n");
return 1;
}
function_to_time(); /* timed code goes here */
if (gettimeofday(&tpend, NULL)) {
fprintf(stderr, "Failed to get end time\n");
return 1;
}
timedif = MILLION*(tpend.tv_sec - tpstart.tv_sec) +
tpend.tv_usec - tpstart.tv_usec;
printf("The function_to_time took %ld microseconds\n", timedif);
return 0;
}
The gettimeofdaytest program shown in Program 9.2 tests gettimeofday resolution by calling gettimeofday in a loop until it produces 20 differences. Program 9.2 displays the differences along with the average difference and the number of calls made to gettimeofday. On most systems, the resolution will be a small number of microseconds. If the number of calls to gettimeofday is not much more than 21, then the limiting factor on the resolution is the time it takes to execute gettimeofday. On most modern systems, many consecutive calls to gettimeofday will return the same value. Often, one of the values displayed will be much greater than the others. This can happen if a context switch occurs while the timing loop is executing.
9.1.4 Using realtime clocks
A clock is a counter that increments at fixed intervals called the clock resolution. The POSIX:TMR Timers Extension contains clocks that are represented by variables of type clockid_t. POSIX clocks may be systemwide or only visible within a process. All implementations must support a systemwide clock with a clockid_t value of CLOCK_REALTIME corresponding to the system realtime clock. Only privileged users may set this clock, but any user can read it.
Program 9.2 gettimeofdaytest.c
A program to test the resolution of gettimeofday.
#include <stdio.h>
#include <sys/time.h>
#define MILLION 1000000L
#define NUMDIF 20
int main(void) {
int i;
int numcalls = 1;
int numdone = 0;
long sum = 0;
long timedif[NUMDIF];
struct timeval tlast;
struct timeval tthis;
if (gettimeofday(&tlast, NULL)) {
fprintf(stderr, "Failed to get first gettimeofday.\n");
return 1;
}
while (numdone < NUMDIF) {
numcalls++;
if (gettimeofday(&tthis, NULL)) {
fprintf(stderr, "Failed to get a later gettimeofday.\n");
return 1;
}
timedif[numdone] = MILLION*(tthis.tv_sec - tlast.tv_sec) +
tthis.tv_usec - tlast.tv_usec;
if (timedif[numdone] != 0) {
numdone++;
tlast = tthis;
}
}
printf("Found %d differences in gettimeofday:\n", NUMDIF);
printf("%d calls to gettimeofday were required\n", numcalls);
for (i = 0; i < NUMDIF; i++) {
printf("%2d: %10ld microseconds\n", i, timedif[i]);
sum += timedif[i];
}
printf("The average nonzero difference is %f\n", sum/(double)NUMDIF);
return 0;
}
The struct timespec structure specifies time for both POSIX:TMR clocks and timers, as well as the timeout values for the POSIX thread functions that support timeouts. The struct timespec structure has at least the following members.
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
POSIX provides functions to set the clock time (clock_settime), to retrieve the clock time (clock_gettime), and to determine the clock resolution (clock_getres). Each of these functions takes two parameters: a clockid_t used to identify the particular clock and a pointer to a struct timespec structure.
SYNOPSIS
#include <time.h>
int clock_getres(clockid_t clock_id, struct timespec *res);
int clock_gettime(clockid_t clock_id, struct timespec *tp);
int clock_settime(clockid_t clock_id, const struct timespec *tp);
POSIX:TMR
If successful, these functions return 0. If unsuccessful, these functions return 1 and set errno. All three functions set errno to EINVAL if clockid_t does not specify a known clock. The clock_settime also sets errno to EINVAL if tp is out of the range of clock_id or if tp->tv_nsec is not in the range [0, 109).
Example 9.8 clockrealtimetiming.c
The following program measures the running time of function_to_time by using the POSIX:TMR clocks.
#include <stdio.h>
#include <time.h>
#define MILLION 1000000L
void function_to_time(void);
int main (void) {
long timedif;
struct timespec tpend, tpstart;
if (clock_gettime(CLOCK_REALTIME, &tpstart) == -1) {
perror("Failed to get starting time");
return 1;
}
function_to_time(); /* timed code goes here */
if (clock_gettime(CLOCK_REALTIME, &tpend) == -1) {
perror("Failed to get ending time");
return 1;
}
timedif = MILLION*(tpend.tv_sec - tpstart.tv_sec) +
(tpend.tv_nsec - tpstart.tv_nsec)/1000;
printf("The function_to_time took %ld microseconds\n", timedif);
return 0;
}
The CLOCK_REALTIME typically has a higher resolution than gettimeofday. Program 9.3 which is similar to Program 9.2 tests the resolution of CLOCK_REALTIME by measuring the average of 20 changes in the clock reading. The program also calls clock_getres to display the nominal resolution in nanoseconds for setting the clock and for timer interrupts (Section 9.5). This nominal resolution is typically large, on the order of milliseconds, and is unrelated to the resolution of clock_gettime for timing. The resolution of clock_gettime is typically better than one microsecond.
Program 9.3 clockrealtimetest.c
A program to test the resolution of CLOCK_REALTIME.
#include <stdio.h>
#include <time.h>
#define BILLION 1000000000L
#define NUMDIF 20
int main(void) {
int i;
int numcalls = 1;
int numdone = 0;
long sum = 0;
long timedif[NUMDIF];
struct timespec tlast;
struct timespec tthis;
if (clock_getres(CLOCK_REALTIME, &tlast))
perror("Failed to get clock resolution");
else if (tlast.tv_sec != 0)
printf("Clock resolution no better than one second\n");
else
printf("Clock resolution: %ld nanoseconds\n", (long)tlast.tv_nsec);
if (clock_gettime(CLOCK_REALTIME, &tlast)) {
perror("Failed to get first time");
return 1;
}
while (numdone < NUMDIF) {
numcalls++;
if (clock_gettime(CLOCK_REALTIME, &tthis)) {
perror("Failed to get a later time");
return 1;
}
timedif[numdone] = BILLION*(tthis.tv_sec - tlast.tv_sec) +
tthis.tv_nsec - tlast.tv_nsec;
if (timedif[numdone] != 0) {
numdone++;
tlast = tthis;
}
}
printf("Found %d differences in CLOCK_REALTIME:\n", NUMDIF);
printf("%d calls to CLOCK_REALTIME were required\n", numcalls);
for (i = 0; i < NUMDIF; i++) {
printf("%2d: %10ld nanoseconds\n", i, timedif[i]);
sum += timedif[i];
}
printf("The average nonzero difference is %f\n", sum/(double)NUMDIF);
return 0;
}
9.1.5 Contrasting elapsed time to processor time
The time function measures real time, sometimes called elapsed time or wall-clock time. In a multiprogramming environment many processes share the CPU, so real time is not an accurate measure of execution time. The virtual time for a process is the amount of time that the process spends in the running state. Execution times are usually expressed in terms of virtual time rather than wall-clock time.
The times function fills the struct tms structure pointed to by its buffer parameter with time-accounting information.
SYNOPSIS
#include <sys/times.h>
clock_t times(struct tms *buffer);
POSIX
If successful, times returns the elapsed real time, in clock ticks, since an arbitrary point in the past such as system or process startup time. The return value may overflow its possible range. If times fails, it returns (clock_t) 1 and sets errno.
The struct tms structure contains at least the following members.
clock_t tms_utime; /* user CPU time of process */
clock_t tms_stime; /* system CPU time on behalf of process */
clock_t tms_cutime /* user CPU time of process and terminated children */
clock_t tms_cstime; /* system CPU time of process and terminated children */
Program 9.4 estimates the total of the amount of CPU time used by function_to_time as well as the fraction of the total CPU time used. It displays the total time in units of seconds expressed as a double. The resolution of the calculation is in clock ticks. A typical value for the number of ticks per second is 100. This number is suitable for accounting but does not have enough resolution for performance measurements of short events. If function_to_time takes only a few clock ticks to execute, you can obtain better resolution by calling it in a loop several times and dividing the resulting time by the number of iterations of the loop.
Program 9.4 calls sysconf as introduced in showtimes (Program 2.10 on page 53) to determine the number of clock ticks in a second. The calculation does not include any CPU time used by children of the process, but it does include both the user time and the system time used on behalf of the process. The fraction of the total CPU time may be inaccurate if a context switch occurs during the execution of the function.
Program 9.5, which is similar to the time shell command, prints the number of clock ticks and seconds used to execute an arbitrary program. The timechild function passes its own command-line argument array to execv in the same way as does Program 3.5 on page 81 and calculates the child's time by subtracting the process time from the total time. Since the process has only one child, what is left is the child's time.
Program 9.4 cpufraction.c
A program that calculates the CPU time in seconds for function_to_time and its fraction of total.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/times.h>
void function_to_time(void);
int main(void) {
double clockticks, cticks;
clock_t tcend, tcstart;
struct tms tmend, tmstart;
if ((clockticks = (double) sysconf(_SC_CLK_TCK)) == -1) {
perror("Failed to determine clock ticks per second");
return 1;
}
printf("The number of ticks per second is %f\n", clockticks);
if (clockticks == 0) {
fprintf(stderr, "The number of ticks per second is invalid\n");
return 1;
}
if ((tcstart = times(&tmstart)) == -1) {
perror("Failed to get start time");
return 1;
}
function_to_time();
if ((tcend = times(&tmend)) == -1) {
perror("Failed to get end times");
return 1;
}
cticks = tmend.tms_utime + tmend.tms_stime
- tmstart.tms_utime - tmstart.tms_stime;
printf("Total CPU time for operation is %f seconds\n",cticks/clockticks);
if ((tcend <= tcstart) || (tcend < 0) || (tcstart < 0)) {
fprintf(stderr, "Tick time wrapped, couldn't calculate fraction\n);
return 1;
}
printf("Fraction of CPU time used is %f\n", cticks/(tcend - tcstart));
return 0;
}
Example 9.9
The following command line uses timechild of Program 9.5 to time the execution of Program 9.4.
timechild cpufraction
Program 9.5 timechild.c
A program that executes its command-line argument array as a child process and returns the amount of time taken to execute the child.
#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/times.h>
#include <sys/types.h>
#include <sys/wait.h>
#include "restart.h"
int main(int argc, char *argv[]) {
pid_t child;
double clockticks;
double cticks;
struct tms tmend;
if (argc < 2){ /* check for valid number of command-line arguments */
fprintf (stderr, "Usage: %s command\n", argv[0]);
return 1;
}
if ((child = fork()) == -1) {
perror("Failed to fork");
return 1;
}
if (child == 0) { /* child code */
execvp(argv[1], &argv[1]);
perror("Child failed to execvp the command");
return 1;
}
if (r_wait(NULL) == -1) { /* parent code */
perror("Failed to wait for child");
return 1;
}
if (times(&tmend) == (clock_t)-1) {
perror("Failed to get end time");
return 1;
}
if ((clockticks = (double) sysconf(_SC_CLK_TCK)) == -1) {
perror("Failed to determine clock ticks per second");
return 1;
}
if (clockticks == 0) {
fprintf(stderr, "Invalid number of ticks per second\n");
return 1;
}
cticks = tmend.tms_cutime + tmend.tms_cstime
- tmend.tms_utime - tmend.tms_stime;
printf("%s used %ld clock ticks or %f seconds\n", argv[1],
(long)cticks, cticks/clockticks);
return 0;
}
|
9.2 Sleep Functions
A process that voluntarily blocks for a specified time is said to sleep. The sleep function causes the calling thread to be suspended either until the specified number of seconds has elapsed or until the calling thread catches a signal.
SYNOPSIS
#include <unistd.h>
#unsigned sleep(unsigned seconds);
POSIX
The sleep function returns 0 if the requested time has elapsed or the amount of unslept time if interrupted. The sleep function interacts with SIGALRM, so avoid using them concurrently in the same process.
Example 9.10 beeper.c
The following program beeps every n seconds, where n is passed as a command-line argument.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[]) {
int sleeptime;
if (argc != 2) {
fprintf(stderr, ";Usage:%s n\n", argv[0]);
return 1;
}
sleeptime = atoi(argv[1]);
fprintf(stderr, "Sleep time is %d\n", sleeptime);
for ( ; ; ) {
sleep(sleeptime);
printf("\007");
fflush(stdout);
}
}
The nanosleep function causes the calling thread to suspend execution until the time interval specified by rqtp has elapsed or until the thread receives a signal. If nanosleep is interrupted by a signal and rmtp is not NULL, the location pointed to by rmtp contains the time remaining, allowing nanosleep to be restarted. The system clock CLOCK_REALTIME determines the resolution of rqtp.
SYNOPSIS
#include <time.h>
int nanosleep(const struct timespec *rqtp, struct timespec *rmtp);
POSIX:TMR
If successful, nanosleep returns 0. If unsuccessful, nanosleep returns 1 and sets errno. The following table lists the mandatory errors for nanosleep.
|
EINTR | nanosleep interrupted by a signal | EINVAL | rqtp specifies a nanosecond value that is not in [0, 109) |
The data structures used by nanosleep allow for nanosecond resolution, but the resolution of CLOCK_REALTIME is typically much larger, on the order of 10 ms. The nanosleep function is meant to replace usleep, which is now considered obsolete. The main advantage of nanosleep over usleep is that nanosleep, unlike sleep or usleep, does not affect the use of any signals, including SIGALRM.
Program 9.6 tests the resolution of the nanosleep function. It executes 100 calls to nanosleep with a sleep time of 1000 nanoseconds. If nanosleep had a true resolution of 1 ns, this would complete in 100 msec. The program takes about one second to complete on a system with a 10 ms resolution.
|
9.3 POSIX:XSI Interval Timers
A timer generates a notification after a specified amount of time has elapsed. In contrast to a clock, which increments to track the passage of time, a timer usually decrements its value and generates a signal when the value becomes zero. A computer system typically has a small number of hardware interval timers, and the operating system implements multiple software timers by using these hardware timers.
Operating systems use interval timers in many ways. An interval timer can cause a periodic interrupt, triggering the operating system to increment a counter. This counter can keep the time since the operating system was booted. UNIX systems traditionally keep the time of day as the number of seconds since January 1, 1970. If an underlying interval timer generates an interrupt after 100 microseconds and is restarted each time it expires, the timer interrupt service routine can keep a local counter to measure the number of seconds since January 1, 1970, by incrementing this local counter after each 10,000 expirations of the interval timer.
Program 9.6 nanotest.c
A function that tests the resolution of nanosleep.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/time.h>
#define COUNT 100
#define D_BILLION 1000000000.0
#define D_MILLION 1000000.0
#define MILLION 1000000L
#define NANOSECONDS 1000
int main(void) {
int i;
struct timespec slptm;
long tdif;
struct timeval tend, tstart;
slptm.tv_sec = 0;
slptm.tv_nsec = NANOSECONDS;
if (gettimeofday(&tstart, NULL) == -1) {
fprintf(stderr, "Failed to get start time\n");
return 1;
}
for (i = 0; i < COUNT; i++)
if (nanosleep(&slptm, NULL) == -1) {
perror("Failed to nanosleep");
return 1;
}
if (gettimeofday(&tend, NULL) == -1) {
fprintf(stderr,"Failed to get end time\n");
return 1;
}
tdif = MILLION*(tend.tv_sec - tstart.tv_sec) +
tend.tv_usec - tstart.tv_usec;
printf("%d nanosleeps of %d nanoseconds\n", COUNT, NANOSECONDS);
printf("Should take %11d microseconds or %f seconds\n",
NANOSECONDS*COUNT/1000, NANOSECONDS*COUNT/D_BILLION);
printf("Actually took %11ld microseconds or %f seconds\n", tdif,
tdif/D_MILLION);
printf("Number of seconds per nanosleep was %f\n",
(tdif/(double)COUNT)/MILLION);
printf("Number of seconds per nanosleep should be %f\n,
NANOSECONDS/D_BILLION);
return 0;
}
Time-sharing operating systems can also use interval timers for process scheduling. When the operating system schedules a process, it starts an interval timer for a time interval called the scheduling quantum. If this timer expires and the process is still executing, the scheduler moves the process to a ready queue so that another process can execute. Multiprocessor systems need one of these interval timers for each processor.
Most scheduling algorithms have a mechanism for raising the priority of processes that have been waiting a long time to execute. The scheduler might use an interval timer for priority management. Every time the timer expires, the scheduler raises the priority of the processes that have not executed.
The interval timers of the POSIX:XSI Extension use a struct itimerval structure that contains the following members.
struct timeval it_value; /* time until next expiration */
struct timeval it_interval; /* value to reload into the timer */
Here it_value holds the time remaining before the timer expires, and it_interval holds the time interval to be used for resetting the timer after it expires. Recall that a struct timeval structure has fields for seconds and microseconds.
A conforming POSIX:XSI implementation must provide each process with the following three user interval timers.
ITIMER_REAL: | decrements in real time and generates a SIGALRM signal when it expires. | ITIMER_VIRTUAL: | decrements in virtual time (time used by the process) and generates a SIGVTALRM signal when it expires. | ITIMER_PROF: | decrements in virtual time and system time for the process and generates a SIGPROF signal when it expires. |
POSIX provides the getitimer function for retrieving the current time interval and the setitimer function for starting and stopping a user interval timer. The which parameter specifies the timer (i.e., ITIMER_REAL, ITIMER_VIRTUAL or ITIMER_PROF). The getitimer function stores the current value of the time for timer which in the location pointed to by value. The setitimer function sets the timer specified by which to the value pointed to by value. If ovalue is not NULL, setitimer places the previous value of the timer in the location pointed to by ovalue. If the timer was running, the it_value member of *ovalue is nonzero and contains the time remaining before the timer would have expired.
SYNOPSIS
#include <sys/time.h>
int getitimer(int which, struct itimerval *value);
int setitimer(int which, const struct itimerval *restrict value,
struct itimerval *restrict ovalue);
POSIX:XSI
If successful, these functions return 0. If unsuccessful, they return 1 and set errno. The setitimer function sets errno to EINVAL if the number of microseconds in value is not in the range [0, 106).
If the it_interval member of *value is not 0, the timer restarts with this value when it expires. If the it_interval of *value is 0, the timer does not restart after it expires. If the it_value of *value is 0, setitimer stops the timer if it is running.
Program 9.7 uses an ITIMER_PROF timer to print out an asterisk for each two seconds of CPU time used. The program first calls setupinterrupt to install myhandler as the signal handler for SIGPROF. Then, the program calls setupitimer to set up a periodic timer, using ITIMER_PROF, that expires every 2 seconds. The ITIMER_PROF timer generates a SIGPROF signal after every two seconds of CPU time used by the process. The process catches the SIGPROF signal and handles it with myhandler. This handler function outputs an asterisk to standard error.
Program 9.7 periodicasterisk.c
A program that prints an asterisk for each two seconds of CPU time used.
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>
/* ARGSUSED */
static void myhandler(int s) {
char aster = '*';
int errsave;
errsave = errno;
write(STDERR_FILENO, &aster, 1);
errno = errsave;
}
static int setupinterrupt(void) { /* set up myhandler for SIGPROF */
struct sigaction act;
act.sa_handler = myhandler;
act.sa_flags = 0;
return (sigemptyset(&act.sa_mask) || sigaction(SIGPROF, &act, NULL));
}
static int setupitimer(void) { /* set ITIMER_PROF for 2-second intervals */
struct itimerval value;
value.it_interval.tv_sec = 2;
value.it_interval.tv_usec = 0;
value.it_value = value.it_interval;
return (setitimer(ITIMER_PROF, &value, NULL));
}
int main(void) {
if (setupinterrupt() == -1) {
perror("Failed to set up handler for SIGPROF");
return 1;
}
if (setupitimer() == -1) {
perror("Failed to set up the ITIMER_PROF interval timer");
return 1;
}
for ( ; ; ); /* execute rest of main program here */
}
Exercise 9.11
Write a program that sets ITIMER_REAL to expire in two seconds and then sleeps for ten seconds. How long does it take for the program to terminate? Why?
Answer:
POSIX states that the interaction between setitimer and any of alarm, sleep or usleep is unspecified, so we can't predict how long it will take. Avoid this combination in your programs by using nanosleep instead of sleep.
Exercise 9.12
What is wrong with the following code, which should print out the number of seconds remaining on the ITIMER_VIRTUAL interval timer?
struct itimerval *value;
getitimer(ITIMER_VIRTUAL, value);
fprintf(stderr, "Time left is %ld seconds\n", value->it_value.tv_sec);
Answer:
Although the variable value is declared as a pointer to a struct itimerval structure, it does not point to anything. That is, there is no declaration of an actual struct itimerval structure that value represents.
Program 9.8 uses the interval timer ITIMER_VIRTUAL to measure the execution time of function_to_time. This example, unlike Program 9.1, uses virtual time. Remember that the value returned by getitimer is the time remaining, so the quantity is decreasing.
Exercise 9.13
How can you modify Program 9.8 to compensate for the overhead of calling setitimer and getitimer?
Answer:
Call the setitimer and getitimer pair with no intervening statements and use the time difference as an estimate of the timing overhead.
Exercise 9.14
What happens if we replace the final return in Program 9.8 with the infinite loop for( ; ; );?
Answer:
After using one million seconds of virtual time, the program receives a SIGVTALRM signal and terminates. One million seconds is approximately 12 days.
Program 9.8 xsitimer.c
A program that uses a POSIX:XSI interval timer to measure the execution time of a function.
#include <stdio.h>
#include <sys/time.h>
#define MILLION 1000000L
void function_to_time(void);
int main(void) {
long diftime;
struct itimerval ovalue, value;
ovalue.it_interval.tv_sec = 0;
ovalue.it_interval.tv_usec = 0;
ovalue.it_value.tv_sec = MILLION; /* a large number */
ovalue.it_value.tv_usec = 0;
if (setitimer(ITIMER_VIRTUAL, &ovalue, NULL) == -1) {
perror("Failed to set virtual timer");
return 1;
}
function_to_time(); /* timed code goes here */
if (getitimer(ITIMER_VIRTUAL, &value) == -1) {
perror("Failed to get virtual timer");
return 1;
}
diftime = MILLION*(ovalue.it_value.tv_sec - value.it_value.tv_sec) +
ovalue.it_value.tv_usec - value.it_value.tv_usec;
printf("The function_to_time took %ld microseconds or %f seconds.\n",
diftime, diftime/(double)MILLION);
return 0;
}
|
9.4 Realtime Signals
In the base POSIX standard, a signal handler is a function with a single integer parameter that represents the signal number of the generating signal. The POSIX:XSI Extension and the POSIX:RTS Realtime Signal Extension have expanded signal-handling capabilities to include the queueing of signals and the passing of information to signal handlers. The standard expands the sigaction structure to allow for additional parameters to the signal handler. If _POSIX_REALTIME_SIGNALS is defined, your implementation supports realtime signals.
Section 8.4 introduces the sigaction function for examining or specifying the action associated with a signal. The struct sigaction structure contains at least the fields given below and specifies the action taken by the sigaction function.
SYNOPSIS
#include <signal.h>
struct sigaction {
void (*sa_handler)(int); /* SIG_DFL, SIG_IGN, or pointer to function */
sigset_t sa_mask; /* additional signals to be blocked
during execution of handler */
int sa_flags; /* special flags and options */
void(*sa_sigaction) (int, siginfo_t *, void *); /* realtime handler */
};
POSIX:CX
The sa_sigaction member specifies an alternative type of signal handler. This handler is used if sa_flags & SA_SIGINFO is nonzero. The form of this handler must be as follows.
void func(int signo, siginfo_t *info, void *context);
The signo parameter, which is equivalent to the parameter of sa_handler, gives the number of the caught signal. The context is not currently defined by the POSIX standard. The siginfo_t structure has at least the following members.
int si_signo; /* signal number */
int si_code; /* cause of the signal */
union sigval si_value; /* signal value */
The si_signo parameter contains the signal number. This value is the same as the value passed by the signo parameter of func.
The si_code parameter reports the cause of the signal. POSIX defines the following values for si_code: SI_USER, SI_QUEUE, SI_TIMER, SI_ASYNCIO and SI_MESGQ. A value of SI_USER means that the signal was generated explicitly by a function such as kill, raise or abort. In these situations, there is no way of generating a value for si_value, so it is not defined. A value of SI_QUEUE means that the sigqueue function generated the signal. A value of SI_TIMER means that a POSIX:RTS timer expired and generated the signal. A value of SI_ASYNCIO means completion of asynchronous I/O, and a value of SI_MESGQ means the arrival of a message on an empty message queue. The si_code variable may have other, implementation-defined values.
POSIX defines the contents of si_value only when the implementation supports the POSIX:RTS Extension and the si_code is SI_QUEUE, SI_TIMER, SI_ASYNCIO or SI_MESGQ. In these cases, the si_value contains the application-specified signal value. The union sigval is defined as follows.
int sival_int;
void *sival_ptr;
According to this definition, either an integer or a pointer can be transmitted to the signal handler by the generator of the signal.
When multiple signals are pending, POSIX guarantees that at least one instance is delivered if the signal is unblocked. Additional instances may be lost. For applications in which it is important to receive every signal, use the POSIX:RTS signal queuing facility. The sigqueue function is an extension to kill that permits signals to be queued. Multiple instances of a signal generated with the kill function may not be queued, even if instances of the same signal generated by sigqueue are.
The sigqueue function sends signal signo with value value to the process with ID pid. If signo is zero, error checking is performed, but no signal is sent. If SA_SIGINFO in the sa_flags field of the struct sigaction structure was set when the handler for signo was installed, the signal is queued and sent to the receiving process. If SA_SIGINFO was not set for signo, the signal is sent at least once but might not be queued.
SYNOPSIS
#include <signal.h>
int sigqueue(pid_t pid, int signo, const union sigval value);
POSIX:RTS
If successful, sigqueue returns 0. If unsuccessful, sigqueue returns 1 and sets errno. The following table lists the mandatory errors for sigqueue.
|
EAGAIN | system does not have resources to queue this signal | EINVAL | signo is an invalid or unsupported signal | EPERM | caller does not have the appropriate privileges | ESRCH | no process corresponds to pid |
Example 9.15
The following code segment checks to see whether process ID mypid corresponds to a valid process.
pid_t mypid;
union sigval qval;
if ((sigqueue(mypid, 0, qval) == -1) && (errno == ESRCH))
fprintf(stderr, "%ld is not a valid process ID\n", (long)mypid);
Program 9.9 shows a program that sends queued signals to a process. The program behaves like the kill command, but it calls sigqueue instead of kill. The process ID, the signal number and the signal value are command-line arguments.
The union sigval union can hold either a pointer or an integer. When the signal is generated from the same process by sigqueue, a timer, asynchronous I/O or a message queue, the pointer can pass an arbitrary amount of information to the signal handler. It does not make sense to use sigqueue to send a pointer from another process unless the address space of the sending process is accessible to the receiver.
Program 9.9 sendsigqueue.c
A program that sends a queued signal to a process.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int pid;
int signo;
int sval;
union sigval value;
if (argc != 4) {
fprintf(stderr, "Usage: %s pid signal value\n", argv[0]);
return 1;
}
pid = atoi(argv[1]);
signo = atoi(argv[2]);
sval = atoi(argv[3]);
fprintf(stderr,"Sending signal %d with value %d to process %d\n",
signo, sval, pid);
value.sival_int = sval;
if (sigqueue(pid, signo, value) == -1) {
perror("Failed to send the signal");
return 1;
}
return 0;
}
Program 9.10 prints its process ID, sets up a signal handler for SIGUSR1, and suspends itself until a signal arrives. The signal handler just displays the values it receives from its parameters. Notice that the signal handler uses fprintf, which is not async-signal safe. This risky use works only because the main program does not use fprintf after it sets up the handler. The signal handler blocks other SIGUSR1 signals. Any other signal causes the process to terminate. You can use Program 9.9 in conjunction with Program 9.10 to experiment with POSIX realtime signals.
The asyncmonitorsignal.c module of Program 8.14 on page 292 showed how to use a realtime signal with asynchronous I/O. The read is started by initread. Three fields of the aio_sigevent structure are used to set up the signal. The sigev_notify field is set to SIGEV_SIGNAL, and the signal number is set in the sigev_signo field. Setting the sigev_value.sival_ptr field to &aiocb makes this pointer available to the signal handler in the si_value.sival_ptr field of the handler's second parameter. In Program 8.14, aiocb was a global variable, so it was accessed directly. Instead, aiocb could have been local to initread with a static storage class.
Program 9.10 sigqueuehandler.c
A program that receives SIGUSR1 signals and displays their values. See the text for comments about using fprintf in the signal handler.
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
static void my_handler(int signo, siginfo_t* info, void *context) {
char *code = NULL;
switch(info->si_code) {
case SI_USER: code = "USER"; break;
case SI_QUEUE: code = "QUEUE"; break;
case SI_TIMER: code = "TIMER"; break;
case SI_ASYNCIO: code = "ASYNCIO"; break;
case SI_MESGQ: code = "MESGQ"; break;
default: code = "Unknown";
}
fprintf(stderr, "Signal handler entered for signal number %d\n", signo);
fprintf(stderr, "Signal=%3d, si_signo=%3d, si_code=%d(%s), si_value=%d\n,"
signo, info->si_signo, info->si_code, code, info->si_value.sival_int);
}
int main(void) {
struct sigaction act;
fprintf(stderr, "Process ID is %ld\n", (long)getpid());
fprintf(stderr, "Setting up signal SIGUSR1 = %d ready\n", SIGUSR1);
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = my_handler;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGUSR1, &act, NULL) == -1)) {
perror("Failed to set up SIGUSR1 signal");
return 1;
}
/* no fprintf calls from here on */
for( ; ; )
pause();
}
|
9.5 POSIX:TMR Interval Timers
The interval timer facility of the POSIX:XSI Extension gives each process a small fixed number of timers, one of each of the types ITIMER_REAL, ITIMER_VIRTUAL, ITIMER_PROF and so on. The POSIX:TMR Extension takes an alternative approach in which there are a small number of clocks, such as CLOCK_REALTIME, and a process can create many independent timers for each clock.
POSIX:TMR timers are based on the struct itimerspec structure, which has the following members.
struct timespec it_interval; /* timer period */
struct timespec it_value; /* timer expiration */
As with POSIX:XSI timers, the it_interval is the time used for resetting the timer after it expires. The it_value member holds the time remaining before expiration. The struct timespec structure has the potential of offering better resolution than struct timeval since its fields measure seconds and nanoseconds rather than seconds and microseconds.
A process can create specific timers by calling timer_create. The timers are per-process timers that are not inherited on fork. The clock_id parameter of timer_create specifies which clock the timer is based on, and *timerid holds the ID of the created timer. The evp parameter specifies the asynchronous notification to occur when the timer expires. The timer_create function creates the timer and puts its ID in the location pointed to by timerid.
SYNOPSIS
#include <signal.h>
#include <time.h>
int timer_create(clockid_t clock_id, struct sigevent *restrict evp,
timer_t *restrict timerid);
struct sigevent {
int sigev_notify /* notification type */
int sigev_signo; /* signal number */
union sigval sigev_value; /* signal value */
};
union sigval {
int sival_int; /* integer value */
void *sival_ptr; /* pointer value */
};
POSIX:TMR
If successful, timer_create returns 0. If unsuccessful, timer_create returns 1 and sets errno. The following table lists the mandatory errors for timer_create.
|
EAGAIN | system does not have resources to honor request, or calling process already has maximum number of timers allowed | EINVAL | specified clock ID is not defined |
The members of the struct sigevent structure shown in the synopsis are required by the POSIX:TMR Extension. The standard does not prohibit an implementation from including additional members.
Example 9.16
The following code segment creates a POSIX:TMR timer based on the CLOCK_REALTIME.
timer_t timerid;
if (timer_create(CLOCK_REALTIME, NULL, &timerid) == -1)
perror("Failed to create a new timer);
The *evp parameter of timer_create specifies which signal should be sent to the process when the timer expires. If evp is NULL, the timer generates the default signal when it expires. For CLOCK_REALTIME, the default signal is SIGALRM. For the timer expiration to generate a signal other than the default signal, the program must set evp->sigev_signo to the desired signal number. The evp->sigev_notify member of the struct sigevent structure specifies the action to be taken when the timer expires. Normally, this member is SIGEV_SIGNAL, which specifies that the timer expiration generates a signal. The program can prevent the timer expiration from generating a signal by setting the evp->sigev_notify member to SIGEV_NONE.
The timer_delete function deletes the POSIX:TMR timer with ID timerid.
SYNOPSIS
#include <time.h>
int timer_delete(timer_t timerid);
POSIX:TMR
If successful, timer_delete returns 0. If unsuccessful, timer_delete returns 1 and sets errno. The timer_delete function sets errno to EINVAL if timerid does not correspond to a valid timer.
Exercise 9.17
What happens if a program calls timer_delete when there are pending signals for timerid?
Answer:
POSIX does not specify what happens to pending signals. You should not make any assumptions about their disposition when calling timer_delete.
If several timers generate the same signal, the handler can use evp->sigev_value to distinguish which timer generated the signal. To do this, the program must use the SA_SIGINFO flag in the sa_flags member of struct sigaction when it installs the handler for the signal. (See Program 9.13 for an example of how to do this.)
The following three functions manipulate the per-process POSIX:TMR timers. The timer_settime function starts or stops a timer that was created by timer_create. The flags parameter specifies whether the timer uses relative or absolute time. Relative time is similar to the scheme used by POSIX:XSI timers, whereas absolute time allows for greater accuracy and control of timer drift. Absolute time is further discussed in Section 9.6. The timer_settime function sets the timer specified by timerid to the value pointed to by value. If ovalue is not NULL, timer_settime places the previous value of the timer in the location pointed to by ovalue. If the timer was running, the it_value member of *ovalue is nonzero and contains the time remaining before the timer would have expired. Use timer_gettime like getitimer to get the time remaining on an active timer.
It is possible for a timer to expire while a signal is still pending from a previous expiration of the same timer. In this case, one of the signals generated may be lost. This is called timer overrun. A program can determine the number of such overruns for a particular timer by calling timer_getoverrun. Timer overruns occur only for signals generated by the same timer. Signals generated by multiple timers, even timers using the same clock and signal, are queued and not lost.
SYNOPSIS
#include <time.h>
int timer_getoverrun(timer_t timerid);
int timer_gettime(timer_t timerid, struct itimerspec *value);
int timer_settime(timer_t timerid, int flags,
const struct itimerspec *value, struct itimerspec *ovalue);
POSIX:TMR
If successful, the timer_settime and timer_gettime functions return 0, and the timer_getoverrun function returns the number of timer overruns. If unsuccessful, all three functions return 1 and set errno. All three functions set errno to EINVAL when timerid does not correspond to a valid POSIX:TMR timer. The timer_settime function also sets errno to EINVAL when the nanosecond field of value is not in the range [0, 109).
Program 9.11 shows how to create a timer that generates periodic interrupts. It generates a SIGALRM interrupt every two seconds of real time.
Exercise 9.18
Why didn't we use strlen in Program 9.11 to find the length of the message?
Answer:
The strlen function is not guaranteed to be async-signal safe.
Exercise 9.19
Program 9.11 uses pause in an infinite loop at the end of the program but Program 9.7 does not. What would happen if we used pause in Program 9.7?
Answer:
Nothing! There is no output.Program 9.7 measures virtual time and the process is not using any virtual time when it is suspended. Program 9.11 uses real time.
Program 9.11 periodicmessage.c
A program that displays a message every two seconds.
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#define BILLION 1000000000L
#define TIMER_MSG "Received Timer Interrupt\n"
/* ARGSUSED */
static void interrupt(int signo, siginfo_t *info, void *context) {
int errsave;
errsave = errno;
write(STDOUT_FILENO, TIMER_MSG, sizeof(TIMER_MSG) - 1);
errno = errsave;
}
static int setinterrupt() {
struct sigaction act;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = interrupt;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGALRM, &act, NULL) == -1))
return -1;
return 0;
}
static int setperiodic(double sec) {
timer_t timerid;
struct itimerspec value;
if (timer_create(CLOCK_REALTIME, NULL, &timerid) == -1)
return -1;
value.it_interval.tv_sec = (long)sec;
value.it_interval.tv_nsec = (sec - value.it_interval.tv_sec)*BILLION;
if (value.it_interval.tv_nsec >= BILLION) {
value.it_interval.tv_sec++;
value.it_interval.tv_nsec -= BILLION;
}
value.it_value = value.it_interval;
return timer_settime(timerid, 0, &value, NULL);
}
int main(void) {
if (setinterrupt() == -1) {
perror("Failed to setup SIGALRM handler");
return 1;
}
if (setperiodic(2.0) == -1) {
perror("Failed to setup periodic interrupt");
return 1;
}
for ( ; ; )
pause();
}
Program 9.12 creates a POSIX:TMR timer to measure the running time of function_to_time. The program is similar to Program 9.8, but it uses real time rather than virtual time.
Program 9.12 tmrtimer.c
A program that uses a POSIX:TMR timer to measure the running time of a function.
#include <stdio.h>
#include <time.h>
#define MILLION 1000000L
#define THOUSAND 1000
void function_to_time(void);
int main(void) {
long diftime;
struct itimerspec nvalue, ovalue;
timer_t timeid;
if (timer_create(CLOCK_REALTIME, NULL, &timeid) == -1) {
perror("Failed to create a timer based on CLOCK_REALTIME");
return 1;
}
ovalue.it_interval.tv_sec = 0;
ovalue.it_interval.tv_nsec = 0;
ovalue.it_value.tv_sec = MILLION; /* a large number */
ovalue.it_value.tv_nsec = 0;
if (timer_settime(timeid, 0, &ovalue, NULL) == -1) {
perror("Failed to set interval timer");
return 1;
}
function_to_time(); /* timed code goes here */
if (timer_gettime(timeid, &nvalue) == -1) {
perror("Failed to get interval timer value");
return 1;
}
diftime = MILLION*(ovalue.it_value.tv_sec - nvalue.it_value.tv_sec) +
(ovalue.it_value.tv_nsec - nvalue.it_value.tv_nsec)/THOUSAND;
printf("The function_to_time took %ld microseconds or %f seconds.\n",
diftime, diftime/(double)MILLION);
return 0;
}
9.6 Timer Drift, Overruns and Absolute Time
One of the problems associated with POSIX:TMR timers and POSIX:XSI timers, as described so far, is the way they are set according to relative time. Suppose you set a periodic interrupt with an interval of 2 seconds, as in Program 9.7 or Program 9.11. When the timer expires, the system automatically restarts the timer for another 2-second interval. Let's say the latency between when the timer was due to expire and when the timer was reset is 5 msec. The actual period of the timer is 2.000005 seconds. After 1000 interrupts the timer will be off by 5 ms. This inaccuracy is called timer drift.
The problem can be even more severe when the timer is restarted from the timer signal handler rather than from the it_interval field of struct itimerval or struct itimerspec. In this case, the latency depends on the scheduling of the processes and the timer resolution. A typical timer resolution is 10 ms. With a latency of 10 ms, the timer drift will be 10 seconds after 1000 iterations.
Exercise 9.20
Consider an extreme case of a repeating timer with period of 22 ms when the timer has a resolution of 10 ms. Estimate the timer drift for 10 expirations of the timer.
Answer:
If you set the time until expiration to be 22 ms, this value will be rounded up to the clock resolution to give 30 ms, giving a drift of 8 ms every 30 ms. These results are summarized in the following table. The drift grows by 8 ms on each expiration.
|
time | 0 | 30 | 60 | 90 | 120 | 150 | 180 | 210 | 240 | 270 | 300 | drift | 0 | 8 | 16 | 24 | 32 | 40 | 48 | 56 | 64 | 72 | 80 | desired expiration | 22 | 44 | 66 | 88 | 110 | 132 | 154 | 176 | 198 | 220 | 242 | timer set for | 22 | 22 | 22 | 22 | 22 | 22 | 22 | 22 | 22 | 22 | 22 | rounded to resolution | 30 | 30 | 30 | 30 | 30 | 30 | 30 | 30 | 30 | 30 | 30 |
One way to handle the drift problem is keep track of when the timer should actually expire and adjust the value for setting the timer each time. This method uses absolute time for setting the timer rather than relative time.
Exercise 9.21
For the specific case described by Exercise 9.20, devise a procedure for setting the timers according to absolute time. What is the timer drift for 10 iterations? Work out a chart similar to the one of Exercise 9.20.
Answer:
Before starting the timer for the first time, determine the current time, add 22 ms to this and save the value as T. This is the desired expiration time. Set the timer to expire in 22 ms. In the signal handler, determine the current time, t. Set the timer to expire in time (T - t + 22 ms). Add 22 ms to T so that T represents the next desired expiration time.
If the timer resolution is 30 ms, then the time at the beginning of step 3 is approximately t = T + 30 ms, and the timer is set to expire in 12 ms. No matter how long the program runs, the total timer drift will be less than 10 ms.
|
time | 0 | 30 | 50 | 70 | 90 | 110 | 140 | 160 | 180 | 200 | 220 | drift | 0 | 8 | 6 | 4 | 2 | 0 | 8 | 6 | 4 | 2 | 0 | desired expiration | 22 | 44 | 66 | 88 | 10 | 132 | 154 | 176 | 198 | 220 | 242 | timer set for | 22 | 14 | 16 | 18 | 20 | 22 | 14 | 16 | 18 | 20 | 22 | rounded to resolution | 30 | 20 | 20 | 20 | 20 | 30 | 20 | 20 | 20 | 20 | 30 |
The procedure of Exercise 9.21 assumes that the value (T - t + 22 ms) is never negative. You cannot set a timer to expire in the past. A negative value means that a timer expiration has been missed completely. This is called a timer overrun. A timer overrun also occurs when the timer is set to automatically restart and a new signal is generated before the previous one has been handled by the process.
The POSIX:TMR timers can make it easier to use absolute time, and they can keep track of timer overruns. POSIX:TMR does not queue signals generated by the same timer. The timer_getoverrun function can be called from within the timer signal handler to obtain the number of missed signals. The flags parameter of timer_settime can be set to TIMER_ABSOLUTE to signify that the time given in the it_value member of the *value parameter represents the real time rather than a time interval. The time is related to the clock from which the timer was generated.
Exercise 9.22
Outline the procedure for using POSIX:TMR timers with absolute time to solve the problem of Exercise 9.21.
Answer:
The procedure for using absolute time with POSIX:TMR timers is as follows.
Before starting the first timer for the first time, determine the current time by using clock_gettime and add 22 ms to this. Save this value as T. Set the timer to expire at time T. Use the TIMER_ABSOLUTE flag. In the timer signal handler, add 22 ms to T and set the timer to expire at time T.
The abstime program of Program 9.13 demonstrates various scenarios for using the POSIX:TMR timer facility. Program 9.13 has three modes of operation: absolute time, relative time and automatic periodic reset. Use the abstime program as follows.
abstime -a | -r | -p [inctime [numtimes [spintime]]]
The first command-line argument must be -a, -r or -p specifying absolute time, relative time or automatic periodic reset. The optional additional arguments (inctime, numtimes and spintime) control the sequence in which timer expirations occur. The program generates numtimes SIGALARM signals that are inctime seconds apart. The signal handler wastes spintime seconds before handling the timer expiration.
The abstime program uses a POSIX:TMR timer that is created with timer_create and started with timer_settime. For absolute times, the abstime program sets the TIMER_ABSTIME flag in timer_settime and sets the it_value member of value field to the current absolute time (time since January 1, 1970) plus the inctime value. When the timer expires, abstime calculates a new absolute expiration time by adding inctime to the previous expiration time. If relative time is set, the program sets it_value to the value specified by inctime. When the timer expires, the handler uses inctime to restart the timer. For periodic time, abstime sets relative time and automatically restarts the timer so that the handler does not have to restart it. The program calculates the time it should take to finish numtimes timer expirations and compares the calculated value with the actual time taken.
Program 9.14 is a header file that defines a data type and the prototypes of the functions in Program 9.15 that are used in the main program of Program 9.13. You must link these files with Program 9.13 to run the abstime program.
Example 9.23
The following command uses abstime with absolute time. It simulates a signal handler that takes 5 milliseconds to execute and does 1000 iterations with a time interval of 22 milliseconds. If the timing were exact, the 5 milliseconds of spin time would not affect the total running time, which should be 22 seconds.
abstime -a 0.022 1000 0.005
Exercise 9.24
The command of Example 9.23 uses absolute time. Are there differences in output when it is run with relative time instead?
Answer:
For an execution of
abstime -a 0.022 1000 0.005
the output might be the following.
pid = 12374
Clock resolution is 10000.000 microseconds or 0.010000 sec.
Using absolute time
Interrupts: 1000 at 0.022000 seconds, spinning 0.005000
Total time: 22.0090370, calculated: 22.0000000, error = 0.0090370
For an execution of
abstime -r 0.022 1000 0.005
the output might be the following.
pid = 12376
Clock resolution is 10000.000 microseconds or 0.010000 sec.
Using relative time
Interrupts: 1000 at 0.022000 seconds, spinning 0.005000
Total time: 30.6357934, calculated: 22.0000000, error = 8.6357934
When absolute timers are used, the error is much less than 1 percent, while relative timers show the expected drift corresponding to the amount of processing time and timer resolution.
The resolution of the clock is displayed by means of a call to clock_getres. A typical value for this might be anywhere from 1000 nanoseconds to 20 milliseconds. The 20 milliseconds (20,000,000 nanoseconds or 50 Hertz) is the lowest resolution allowed by the POSIX:TMR Extension. One microsecond (1000 nanoseconds) is the time it takes to execute a few hundred instructions on most fast machines. Just because a system has a clock resolution of 1 microsecond does not imply that a program can use timers with anything near this resolution. A context switch is often needed before the signal handler can be entered and, as Table 1.1 on page 5 points out, a context switch can take considerably longer than this.
Example 9.25
The following command uses Program 9.13 to estimate the effective resolution of the hardware timer on a machine by calling abstime with an inctime of 0, default numtimes of 1 and default spintime of 0. The abstime program displays the clock resolution and starts one absolute time clock interrupt to expire at the current time. The timer expires immediately.
abstime -a 0
Example 9.26
The following command uses Program 9.13 to determine the maximum number of timer signals that can be handled per second by starting 1000 timer interrupts with an inctime of 0. These should all expire immediately. The abstime program then displays the minimum time for 1000 interrupts.
abstime -a 0.0 1000 0.0
Program 9.13 illustrates some other useful tips in using POSIX:TMR timers. Information about the timer that generated the signal is available in the signal handler. When a timer is created, an integer or a pointer can be stored in the sigev_value member of the struct sigevent structure. If the signal handler is to restart that timer or if multiple timers are to share a signal handler, the signal handler must have access to the timer ID of the timer that generated the signal. If the signal handler was set up with the SA_SIGINFO flag, it can access the value that timer_create stored in sigev_value through its second parameter. The timer_create cannot directly store the timer ID in its sigev_value because the ID is not known until after the timer has been created. It therefore stores a pointer to the timer ID in the sival_ptr member of union sigval.
Program 9.13 abstime.c
The abstime program illustrates POSIX:TMR timers with absolute time. Program 9.14 and Program 9.15 are called.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "abstime.h"
#define INCTIME 0.01
#define NUMTIMES 1
#define SPINTIME 0.0
int main(int argc, char *argv[]) {
struct sigaction act;
struct timespec clockres, currenttime;
timer_data data;
struct sigevent evp;
sigset_t sigset;
double tcalc, tend, tstart, ttotal;
data.exitflag = 0;
data.inctime = INCTIME;
data.numtimes = NUMTIMES;
data.spintime = SPINTIME;
data.type = -1;
if (argc > 1) {
if (!strcmp(argv[1], "-r"))
data.type = TYPE_RELATIVE;
else if (!strcmp(argv[1], "-a"))
data.type = TYPE_ABSOLUTE;
else if (!strcmp(argv[1], "-p"))
data.type = TYPE_PERIODIC;
}
if ( (argc < 2) || (argc > 5) || (data.type < 0) ){
fprintf(stderr,
"Usage: %s -r | -a | -p [inctime [numtimes [spintime]]]\n",
argv[0]);
return 1;
}
if (argc > 2)
data.inctime = atof(argv[2]);
if (argc > 3)
data.numtimes = atoi(argv[3]);
if (argc > 4)
data.spintime = atof(argv[4]);
fprintf(stderr, "pid = %ld\n", (long)getpid());
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = timehandler;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGALRM, &act, NULL)) == -1) {
perror("Failed to set handler for SIGALRM");
return 1;
}
evp.sigev_notify = SIGEV_SIGNAL;
evp.sigev_signo = SIGALRM;
evp.sigev_value.sival_ptr = &data;
if (timer_create(CLOCK_REALTIME, &evp, &data.timid) < 0) {
perror("Failed to create a timer");
return 1;
}
if (clock_getres(CLOCK_REALTIME, &clockres) == -1)
perror("Failed to get clock resolution");
else
fprintf(stderr, "Clock resolution is %0.3f microseconds or %0.6f sec.\n",
D_MILLION*time_to_double(clockres), time_to_double(clockres));
data.tvalue.it_interval.tv_sec = 0;
data.tvalue.it_interval.tv_nsec = 0;
data.tvalue.it_value = double_to_time(data.inctime);
data.flags = 0;
if (clock_gettime(CLOCK_REALTIME, ¤ttime) == -1) {
perror("Failed to get current time");
return 1;
}
tstart = time_to_double(currenttime);
if (data.type == TYPE_ABSOLUTE) {
data.tvalue.it_value.tv_nsec += currenttime.tv_nsec;
data.tvalue.it_value.tv_sec += currenttime.tv_sec;
if (data.tvalue.it_value.tv_nsec >= BILLION) {
data.tvalue.it_value.tv_nsec -= BILLION;
data.tvalue.it_value.tv_sec++;
}
data.flags = TIMER_ABSTIME;
fprintf(stderr,"Using absolute time\n");
}
else if (data.type == TYPE_RELATIVE)
fprintf(stderr,"Using relative time\n");
else if (data.type == TYPE_PERIODIC) {
data.tvalue.it_interval = data.tvalue.it_value;
fprintf(stderr,"Using periodic time\n");
}
fprintf(stderr, "Interrupts: %d at %.6f seconds, spinning %.6f\n",
data.numtimes, data.inctime, data.spintime);
if (timer_settime(data.timid, data.flags, &data.tvalue, NULL) == -1){
perror("Failed to start timer");
return 1;
}
if (sigemptyset(&sigset) == -1) {
perror("Failed to set up suspend mask");
return 1;
}
while (!data.exitflag)
sigsuspend(&sigset);
if (clock_gettime(CLOCK_REALTIME, ¤ttime) == -1) {
perror("Failed to get expiration time");
return 1;
}
tend = time_to_double(currenttime);
ttotal=tend - tstart;
tcalc = data.numtimes*data.inctime;
fprintf(stderr, "Total time: %1.7f, calculated: %1.7f, error = %1.7f\n",
ttotal, tcalc, ttotal - tcalc);
return 0;
}
Program 9.14 abstime.h
The abstime.h include file contains constants, type definitions, and prototypes used by abstime and abstimelib.
#define BILLION 1000000000L
#define D_BILLION 1000000000.0
#define D_MILLION 1000000.0
#define TYPE_ABSOLUTE 0
#define TYPE_RELATIVE 1
#define TYPE_PERIODIC 2
typedef struct {
timer_t timid;
int type;
int flags;
int numtimes;
int exitflag;
double inctime;
double spintime;
struct itimerspec tvalue;
} timer_data;
struct timespec double_to_time(double tm);
double time_to_double(struct timespec t);
void timehandler(int signo, siginfo_t* info, void *context);
Program 9.15 abstimelib.c
The abstimelib module contains the signal handler and utility routines used by abstime.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "abstime.h"
static struct timespec add_to_time(struct timespec t, double tm) {
struct timespec t1;
t1 = double_to_time(tm);
t1.tv_sec = t1.tv_sec + t.tv_sec;
t1.tv_nsec = t1.tv_nsec + t.tv_nsec;
while (t1.tv_nsec >= BILLION) {
t1.tv_nsec = t1.tv_nsec - BILLION;
t1.tv_sec++;
}
return t1;
}
static int spinit (double stime) { /* loops for stime seconds and returns */
struct timespec tcurrent;
double tend, tnow;
if (stime == 0.0)
return 0;
if (clock_gettime(CLOCK_REALTIME, &tcurrent) == -1)
return -1;
tnow = time_to_double(tcurrent);
tend = tnow + stime;
while (tnow < tend) {
if (clock_gettime(CLOCK_REALTIME, &tcurrent) == -1)
return -1;
tnow = time_to_double(tcurrent);
}
return 0;
}
/* ------------------------- Public functions -------------------------- */
double time_to_double(struct timespec t) {
return t.tv_sec + t.tv_nsec/D_BILLION;
}
struct timespec double_to_time(double tm) {
struct timespec t;
t.tv_sec = (long)tm;
t.tv_nsec = (tm - t.tv_sec)*BILLION;
if (t.tv_nsec == BILLION) {
t.tv_sec++;
t.tv_nsec = 0;
}
return t;
}
void timehandler(int signo, siginfo_t* info, void *context) {
timer_data *datap;
static int timesentered = 0;
timesentered++;
datap = (timer_data *)(info->si_value.sival_ptr);
if (timesentered >= datap->numtimes) {
datap->exitflag = 1;
return;
}
if (spinit(datap->spintime) == -1) {
write(STDERR_FILENO, "Spin failed in handler\n", 23);
datap->exitflag = 1;
}
if (datap->type == TYPE_PERIODIC)
return;
if (datap->type == TYPE_ABSOLUTE)
datap->tvalue.it_value =
add_to_time(datap->tvalue.it_value, datap->inctime);
if (timer_settime(datap->timid, datap->flags, &datap->tvalue, NULL) == -1) {
write(STDERR_FILENO, "Could not start timer in handler\n",33);
datap->exitflag = 1;
}
}
Program 9.16 timesignals.c
A program that calculates the time to receive 1000 SIGALRM signals.
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#define COUNT 1000
#define MILLION 1000000L
static int count = 0;
/* ARGSUSED */
static void handler(int signo, siginfo_t *info, void *context) {
count++;
}
int main(void) {
struct sigaction act;
sigset_t sigblocked, sigunblocked;
long tdif;
struct timeval tend, tstart;
act.sa_flags = SA_SIGINFO;
act.sa_sigaction = handler;
if ((sigemptyset(&act.sa_mask) == -1) ||
(sigaction(SIGALRM, &act, NULL) == -1)) {
perror("Failed to set up handler for SIGALRM");
return 1;
}
if ((sigemptyset(&sigblocked) == -1) ||
(sigemptyset(&sigunblocked) == -1) ||
(sigaddset(&sigblocked, SIGALRM) == -1) ||
(sigprocmask(SIG_BLOCK, &sigblocked, NULL) == -1)) {
perror("Failed to block signal");
return 1;
}
printf("Process %ld waiting for first SIGALRM (%d) signal\n",
(long)getpid(), SIGALRM);
sigsuspend(&sigunblocked);
if (gettimeofday(&tstart, NULL) == -1) {
perror("Failed to get start time");
return 1;
}
while (count <= COUNT)
sigsuspend(&sigunblocked);
if (gettimeofday(&tend, NULL) == -1) {
perror("Failed to get end time");
return 1;
}
tdif = MILLION*(tend.tv_sec - tstart.tv_sec) +
tend.tv_usec - tstart.tv_usec;
printf("Got %d signals in %ld microseconds\n", count-1, tdif);
return 0;
}
Although the timer resolution might be as large as 10 ms, signals may be processed at a much higher rate than timer signals can be generated. Program 9.16 waits for SIGALRM signals and calculates the time to receive 1000 signals after the first one arrives. You can use Program 9.17 to send signals to a process. It takes two command-line arguments: a process ID and a signal number. It sends the signals as fast as it can until the process dies. A reasonably fast machine should be able to handle several thousand signals per second.
Program 9.17 multikill.c
The multikill program continually sends signals to another process until the process dies.
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
int pid;
int sig;
if (argc != 3) {
fprintf(stderr, "Usage: %s pid signal\n", argv[0]);
return 1;
}
pid = atoi(argv[1]);
sig = atoi(argv[2]);
while (kill(pid, sig) == 0) ;
return 0;
}
|
siglongjmp | The use of siglongjmp in a handler is unsafe because the handler might have interrupted
the execution of a function that is not asynch-signal safe. The code that is executed after
the longjmp transfer of control has then the same restrictions that hold in handlers.
Howver, that code is normally not in handlers (e.g. it is in the main function, as the example
in section 8.7 shows).
The use of siglongjmp is also prohibited by the CERT security recommendations, see: CERT SIG32-C.
I would suggest to warn the programmers not to use them. 2009-05-06 22:02:55 | |
|
|
|
|
|
|