Linux for Programmers and Users
12.5. Signals
Programs must sometimes deal with unexpected or unpredictable events, such as:
These kind of events are sometimes called interrupts, as they must interrupt the regular flow of a program in order to be processed. When Linux recognizes that such an event has occurred, it sends the corresponding process a signal. There is a unique, numbered signal for each possible event. For example, if a process causes a floating point error, the kernel sends the offending process signal number 8 (Figure 12-43). Figure 12-43. Floating-point error signal.
The kernel isn't the only one that can send a signal; any process can send any other process a signal, as long as it has permission. The rules regarding permissions are discussed shortly. A programmer may arrange for a particular signal to be ignored or to be processed by a special piece of code called a signal handler. In the latter case, the process that receives the signal suspends its current flow of control, executes the signal handler, and then resumes the original flow of control when the signal handler finishes. By learning about signals, you can "protect" your programs from Control-C, arrange for an alarm clock signal to terminate your program if it takes too long to perform a task, and learn how Linux uses signals during everyday operations. 12.5.1. Signal Types
Linux supports two types of signals: standard signalsthe traditional UNIX signalsand real-timeor queuedsignals. Traditional signals are delivered to a process by setting a bit in a bitmap, one for each signal. Therefore, multiple instances of the same signal cannot be represented, because the bitmap can only be one (signal) or zero (no signal). POSIX 1003.1b also defines queued signals for real-time processes where successive instances of the same signal are significant and need to be properly delivered. In order to use queued signals, you must use the sigaction () system call, rather than signal (), which is described in the rest of this section. 12.5.2. Defined Signals
Signals are defined in "/usr/include/signal.h" and the other platform-specific header files it includes (the actual signal definitions are in "/usr/include/asm/signal.h" on my system). A programmer may choose that a particular signal triggers a user-supplied signal handler, triggers the default kernel-supplied handler, or is ignored. The default handler usually performs one of the following actions:
12.5.3. POSIX Signals
Figure 12-44 is a list of the standard POSIX signals defined in Linux along with their macro definition, numeric value, process's default action, and a brief description.
Other signals are supported by Linux. For information on other signals, see the man page for signal in section 7 ("man 7 signal"). 12.5.4. Terminal Signals
The easiest way to send a signal to a foreground process is by pressing Control-C or Control-Z from the keyboard. When the terminal driver (the piece of software that supports the terminal) recognizes a Control-C, it sends a SIGINT signal to all of the processes in the current foreground job. Similarly, Control-Z causes it to send a SIGTSTP signal to all of the processes in the current foreground job. By default, SIGINT terminates a process and SIGTSTP suspends a process. Later in this section, I'll show you how to perform similar actions from a C program. 12.5.5. Requesting an Alarm Signal: alarm ()
One of the simplest ways to see a signal in action is to arrange for a process to receive an alarm clock signal, SIGALRM, by using alarm (). The default handler for this signal displays the message "Alarm clock" and terminates the process. Figure 12-45 describes how alarm () works.
Here's a small program that uses alarm (), together with its output: $ cat alarm.c ...list the program. #include <stdio.h> main () { alarm (3); /* Schedule an alarm signal in three seconds */ printf ("Looping forever...\n"); while (1); printf ("This line should never be executed\n"); } $ ./alarm ...run the program. Looping forever... Alarm clock ...occurs three seconds later. $ _
The next section shows you how you override a default signal handler and make your program respond specially to a particular signal. 12.5.6. Handling Signals: signal ()
The last example program reacted to the alarm signal SIGALRM in the default manner. The signal () system call may be used to override the default action (Figure 12-46).
I made a couple of changes to the previous program so that it caught and processed the SIGALRM signal efficiently:
Before I show you the updated program, let's look at a description of pause () (Figure 12-47).
Here's the updated version: $ cat handler.c ...list the program. #include <stdio.h> #include <signal.h> int alarmFlag = 0; /* Global alarm flag */ void alarmHandler (); /* Forward declaration of alarm handler */ /***************************************************************/ main () { signal (SIGALRM, alarmHandler); /* Install signal handler */ alarm (3); /* Schedule an alarm signal in three seconds */ printf ("Looping...\n"); while (!alarmFlag) /* Loop until flag set */ { pause (); /* Wait for a signal */ } printf ("Loop ends due to alarm signal\n"); } /***************************************************************/ void alarmHandler () { printf ("An alarm clock signal was received\n"); alarmFlag = 1; } $ ./handler ...run the program. Looping... An alarm clock signal was received ...occurs three seconds later. Loop ends due to alarm signal $ _ 12.5.7. Protecting Critical Code and Chaining Interrupt Handlers
The same techniques that I just described may be used to protect critical pieces of code against Control-C attacks and other such signals. In these cases, it's common to save the previous value of the handler so that it can be restored after the critical code has executed. Here's the source code of a program that protects itself against SIGINT signals: $ cat critical.c ...list the program. #include <stdio.h> #include <signal.h> main () { void (*oldHandler) (); /* To hold old handler value */ printf ("I can be Control-C'ed\n"); sleep (3); oldHandler = signal (SIGINT, SIG_IGN); /* Ignore Control-C */ printf ("I'm protected from Control-C now\n"); sleep (3);
12.5.8. Sending Signals: kill ()
A process may send a signal to another process by using the kill () system call (Figure 12-48). kill () is a misnomer, since many of the signals that it can send do not terminate a process. It's called kill () for historical reasons; back when UNIX was first designed, the main use of signals was to terminate processes.
12.5.9. Death of Children
When a parent's child terminates, the child process sends its parent a SIGCHLD signal. A parent process often installs a handler to deal with this signal, which typically executes a wait () to accept the child's termination code and let the child de-zombify.[1] [1] This means that the child is completely laid to rest and is no longer a zombie. Alternatively, the parent can choose to ignore SIGCHLD signals, in which case the child de-zombifies automatically. One of the socket programs that follows later in this chapter makes use of this feature. The next example illustrates a SIGCHLD handler, and allows a user to limit the amount of time that a command takes to execute. The first parameter of "limit" is the maximum number of seconds allowed for execution, and the remaining parameters are the command itself. The program works by performing the following steps:
Here are the source code and sample output from the program: $ cat limit.c ...list the program. #include <stdio.h> #include <signal.h> int delay; void childHandler (); /********************************************************************/ main (argc, argv) int argc; char* argv[]; { int pid; signal (SIGCHLD, childHandler); /* Install death-of-child handler */ pid = fork (); /* Duplicate */ if (pid == 0) /* Child */ { execvp (argv[2], &argv[2]); /* Execute command */ perror ("limit"); /* Should never execute */ } else /* Parent */
12.5.10. Suspending and Resuming Processes
The SIGSTOP and SIGCONT signals suspend and resume a process, respectively. They are used by the Linux shells to support job control to implement built-in commands like stop, fg, and bg. In the following example, the main program created two children that both entered an infinite loop and displayed a message every second. The main program waited for three seconds and then suspended the first child. The second child continued to execute as usual. After another three seconds, the parent restarted the first child, waited a little while longer, and then terminated both children. $ cat pulse.c ...list the program. #include <signal.h> #include <stdio.h> main () { int pid1; int pid2; pid1 = fork(); if (pid1 == 0) /* First child */ { 12.5.11. Process Groups and Control Terminals
When you're in a shell and you execute a program that creates several children, a single Control-C from the keyboard will normally terminate the program and its children and then return you to the shell. There are several features that produce this behavior:
Here's how a shell uses these features:
Figure 12-49 illustrates a typical setup. Assume that process 145 and process 230 are the process leaders of background jobs, and that process 171 is the process leader of the foreground job. Figure 12-49. Control terminals and process groups.
setpgid () changes a process's group (Figure 12-50).
A process may find out its current process group ID by using getpgid () (Figure 12-51).
The following example illustrates the fact that a terminal distributes signals to all of the processes in its control process's process group. Since the child inherited its process group from its parent, both the parent and child caught the SIGINT signal: $ cat pgrp1.c ...list program. #include <signal.h> #include<stdio.h> void sigintHandler(); main () { signal (SIGINT, sigintHandler); /* Handle Control-C */ if (fork () == 0) printf ("Child PID %d PGRP %d waits\n", getpid (),getpgid (0)); else printf ("Parent PID %d PGRP %dwaits\n", getpid (), getpgid (0)); pause (); /* Wait for asignal */ } void sigintHandler () { printf ("Process %d got a SIGINT\n",getpid ()); } $ ./pgrp1 ...run the program. Parent PID 24583 PGRP 24583 waits Child PID 24584 PGRP 24583 waits ^C ...press Control-C. Process 24584 got a SIGINT Process 24583 got a SIGINT $ _ If a process places itself into a different process group, it is no longer associated with the terminal's control process, and does not receive signals from the terminal. In the following example, the child process was not affected by a Control-C: $ cat pgrp2.c ...list the program. #include <signal.h> #include <stdio.h> void sigintHandler (); main () { int i; signal (SIGINT, sigintHandler); /* Install signal handler */ if (fork () == 0) setpgid (0, getpid ()); /* Place child in its own process group */ printf ("Process PID %d PGRP %d waits\n", getpid (), getpgid (0)); for (i = 1; i <= 3; i++) /* Loop three times */ { printf ("Process %d is alive\n", getpid ()); sleep(1); } } void sigintHandler () { printf ("Process %d got a SIGINT\n", getpid ()); exit (1); } $ ./pgrp2 ...run the program. Process PID 24591 PGRP 24591 waits Process PID 24592 PGRP 24592 waits ^C ...Control-C Process 24591 got a SIGINT ...parent receives signal. Process 24592 is alive ...child carries on. Process 24592 is alive Process 24592 is alive $ _
If a process attempts to read from its control terminal after it disassociates itself from the terminal's control process, it is sent a SIGTTIN signal, which suspends the receiver by default. In the following example, I trapped SIGTTIN with my own handler to make the effect a little clearer: $ cat pgrp3.c ...list the program. #include <signal.h> #include <stdio.h> #include <sys/termio.h> #include <fcntl.h> void sigttinHandler (); main () { int status; char str [100]; if (fork () == 0) /* Child */ { signal (SIGTTIN, sigttinHandler); /* Install handler */ setpgid (0, getpid ()); /* Place myself in a new process group */ printf ("Enter a string: "); scanf ("%s", str); /* Try to read from control terminal */ printf ("You entered %s\n", str); } else /* Parent */ { wait (&status); /* Wait for child to terminate */ } } void sigttinHandler () { printf ("Attempted inappropriate read from control terminal\n"); exit (1); } $ ./pgrp3 ...run the program. Enter a string: Attempted inappropriate read from control terminal $ _
|
Категории