Section 12.5. Signals


[Page 486 (continued)]

12.5. Signals

Programs must sometimes deal with unexpected or unpredictable events, such as:

  • a floating-point error

  • a power failure

  • an alarm clock "ring" (discussed soon)

  • the death of a child process

  • a termination request from a user (i.e., a Control-C)

  • a suspend request from a user (i.e., a Control-Z)


[Page 487]

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:

  • terminates the process and generates a dump of memory in a core file (core)

  • terminates the process without generating a core image file (quit)


  • [Page 488]
  • ignores and discards the signal (ignore)

  • suspends the process (stop)

  • resumes the process

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.

Figure 12-44. POSIX signals.

Macro

#

Default action

Description

SIGHUP

1

quit

Hangup or death of controlling process.

SIGINT

2

quit

Keyboard interrupt.

SIGQUIT

3

core

Quit.

SIGILL

4

core

Illegal instruction.

SIGABRT

6

core

Abort.

SIGFPE

8

core

Arithmetic exception.

SIGKILL

9

quit

Kill (cannot be caught, blocked, or ignored).

SIGUSR1

10

quit

User-defined signal.

SIGSEGV

11

core

Segmentation violation (out of range address).

SIGUSR2

12

quit

User-defined signal.

SIGPIPE

13

quit

Write on a pipe or other socket with no one to read it.

SIGALRM

14

quit

Alarm clock.

SIGTERM

15

quit

Software termination signal (default signal sent by kill).

SIGCHLD

17

ignore

Status of child process has changed.

SIGCONT

18

none

Continue if stopped.

SIGSTOP

19

stop

Stop (suspend) the process.

SIGTSTP

20

stop

Stop from the keyboard.

SIGTTIN

21

stop

Background read from tty device.

SIGTTOU

22

stop

Background write to tty device.



[Page 489]

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.

Figure 12-45. Description of the alarm () system call.

System Call: unsigned int alarm (unsigned int count)

alarm () instructs the kernel to send the SIGALRM signal to the calling process after count seconds. If an alarm had already been scheduled, it is overwritten. If count is 0, any pending alarm requests are cancelled.

alarm () returns the number of seconds that remain until the alarm signal is sent.


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.


[Page 490]

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).

Figure 12-46. Description of the signal () system call.

System Call: void (*signal (int sigCode, void (*func)(int))) (int)

signal () allows a process to specify the action that it will take when a particular signal is received. The parameter sigCode specifies the number of the signal that is to be reprogrammed, and func may be one of several values:

  • SIG_IGN, which indicates that the specified signal should be ignored and discarded.

  • SIG_DFL, which indicates that the kernel's default handler should be used.

  • an address of a user-defined function, which indicates that the function should be executed when the specified signal arrives.

The valid signal numbers are included from "/usr/include/signal.h" (and the other header files that includes, the actual signal definitions are in "/usr/include/asm/signal.h" on my Linux machine). The signals SIGKILL and SIGSTP may not be reprogrammed. A child process inherits the signal settings from its parent during a fork (). When a process performs an exec (), previously ignored signals remain ignored but installed handlers are set back to the default handler.

With the exception of SIGCHLD, signals are not stacked. This means that if a process is sleeping and three identical signals are sent to it, only one of the signals is actually processed.

signal () returns the previous func value associated with sigCode if successful; otherwise it returns -1.


I made a couple of changes to the previous program so that it caught and processed the SIGALRM signal efficiently:

  • I installed my own signal handler, alarmHandler (), by using signal ().

  • I made the while loop less draining on the timesharing system by making use of a system call called pause (). The old version of the while loop had an empty code body which caused it to loop very fast and soak up CPU resources. The new version of the while loop suspends each time through the loop until a signal is received.

Before I show you the updated program, let's look at a description of pause () (Figure 12-47).

Figure 12-47. Description of the pause () system call.

System Call: int pause (void)

pause () suspends the calling process and returns when the calling process receives a signal. It is most often used to wait efficiently for an alarm signal. pause () doesn't return anything useful.



[Page 491]

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); 
[Page 492]
signal (SIGINT, oldHandler); /* Restore old handler */ printf ("I can be Control-C'ed again\n"); sleep (3); printf ("Bye!\n"); } $ ./critical ...run the program. I can be Control-C'ed ^C ...Control-C works here. $ ./critical ...run the program again. I can be Control-C'ed I'm protected from Control-C now ^C ...Control-C is ignored. I can be Control-C'ed again Bye! $ _


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.

Figure 12-48. Description of the kill () system call.

System Call: int kill (pid_t pid, int sigCode)

kill () sends the signal with value sigCode to the process with PID pid. kill () succeeds and the signal is sent as long as at least one of the following conditions is satisfied:

  • The sending process and the receiving process have the same owner.

  • The sending process is owned by a super-user.

There are a few variations on the way that kill () works:

  • If pid is 0, the signal is sent to all of the processes in the sender's process group.

  • If pid is -1 and the sender is owned by a super-user, the signal is sent to all processes, including the sender.

  • If pid is -1 and the sender is not a super-user, the signal is sent to all of the processes owned by the same owner as the sender, excluding the sending process.

  • If the pid is negative and not -1, the signal is sent to all of the processes in the process group.

Process groups are discussed later in this chapter. If kill () manages to send at least one signal successfully, it returns 0; otherwise, it returns -1.



[Page 493]

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:

1.

The parent process installs a SIGCHLD handler that is executed when its child process terminates.

2.

The parent process forks a child process to execute the command.

3.

The parent process sleeps for the specified number of seconds. When it wakes up, it sends its child process a SIGINT signal to kill it.

4.

If the child terminates before its parent finishes sleeping, the parent's SIGCHLD handler is executed, causing the parent to terminate immediately.

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 */ 
[Page 494]
{ sscanf (argv[1], "%d", &delay); /* Read delay from command line */ sleep (delay); /* Sleep for the specified number of seconds */ printf ("Child %d exceeded limit and is being killed\n", pid); kill (pid, SIGINT); /* Kill the child */ } } /********************************************************************/ void childHandler () /* Executed if the child dies before the parent */ { int childPid, childStatus; childPid = wait (&childStatus); /* Accept child's termination code */ printf ("Child %d terminated within %d seconds\n", childPid, delay); exit (/* EXITSUCCESS */ 0); } $ ./limit 5 ls ...run the program; command finishes OK. a.out alarm critical handler limit alarm.c critical.c handler.c limit.c Child 4030 terminated within 5 seconds $ ./limit 4 sleep 100 ...run it again; command takes too long. Child 4032 exceeded limit and is being killed $ _


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 */    { 
[Page 495]
while (1) /* Infinite loop */ { printf ("pid1 is alive\n"); sleep (1); } } pid2 = fork (); /* Second child */ if (pid2 == 0) { while (1) /* Infinite loop */ { printf ("pid2 is alive\n"); sleep (1); } } sleep (3); kill (pid1, SIGSTOP); /* Suspend first child */ sleep (3); kill (pid1, SIGCONT); /* Resume first child */ sleep (3); kill (pid1, SIGINT); /* Kill first child */ kill (pid2, SIGINT); /* Kill second child */ } $ ./pulse ...run the program. pid1 is alive ...both run in first three seconds. pid2 is alive pid1 is alive pid2 is alive pid1 is alive pid2 is alive pid2 is alive ...just the second child runs now. pid2 is alive pid2 is alive pid1 is alive ...the first child is resumed. pid2 is alive pid1 is alive pid2 is alive pid1 is alive pid2 is alive $ _



[Page 496]

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:

  • In addition to having a unique process ID number, every process is also a member of a process group. Several processes can be members of the same process group. When a process forks, the child inherits its process group from its parent. A process may change its process group to a new value by using setpgid (). When a process execs, its process group remains the same.

  • Every process can have an associated control terminal. This is typically the terminal where the process was started. When a process forks, the child inherits its control terminal from its parent. When a process execs, its control terminal stays the same.

  • Every terminal can be associated with a single control process. When a metacharacter such as a Control-C is detected, the terminal sends the appropriate signal to all of the processes in the process group of its control process.

  • If a process attempts to read from its control terminal and is not a member of the same process group as the terminal's control process, the process is sent a SIGTTIN signal, which normally suspends the process.

Here's how a shell uses these features:

  • When an interactive shell begins, it is the control process of a terminal and has that terminal as its control terminal. How this occurs is beyond the scope of this book.

  • When a shell executes a foreground process, the child shell places itself in a different process group before exec'ing the command, and takes control of the terminal. Any signals generated from the terminal thus go to the foreground command rather than the original parent shell. When the foreground command terminates, the original parent shell takes back control of the terminal.

  • When a shell executes a background process, the child shell places itself in a different process group before exec'ing, but does not take control of the terminal. Any signals generated from the terminal continue to go to the shell. If the background process tries to read from its control terminal, it is suspended by a SIGTTIN signal.

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.


[Page 497]

Figure 12-49. Control terminals and process groups.


setpgid () changes a process's group (Figure 12-50).

Figure 12-50. Description of the setpgid () system call.

System Call: pid_t setpgid (pid_t pid, pid_t pgrpId)

setpgid () sets the process group ID of the process with PID pid to pgrpId. If pid is zero, the caller's process group ID is set to pgrpId. In order for setpgid () to succeed and set the process group ID, at least one of the following conditions must be met:

  • The caller and the specified process must have the same owner.

  • The caller must be owned by a super-user.

When a process wants to start its own unique process group, it typically passes its own process ID number as the second parameter to setpgid ().

If setpgid () fails, it returns -1.


A process may find out its current process group ID by using getpgid () (Figure 12-51).


[Page 498]

Figure 12-51. Description of the getpgid () system call.

System Call: pid_t getpgid (pid_t pid)

getpgid () returns the process group ID of the process with PID pid. If pid is zero, the process group ID of the caller is returned.


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 $ _ 



[Page 499]

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 $ _ 



[Page 500]

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 $ _ 





Linux for Programmers and Users
Linux for Programmers and Users
ISBN: 0131857487
EAN: 2147483647
Year: 2007
Pages: 339

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net