When a child terminates either normally or not in Linux, a system
routine executes to handle the process termination.
This system routine arranges for a signal to be sent to the parent process.
This child termination signal is asynchronous to the parent's execution.
- There are a fixed set of signals that can be sent to a
process.
- Signals are identified by integers.
- Signal numbers have symbolic names. For example, SIGCHLD is
the number of the signal to a parent when a child
terminates.
Symbolic constants for signal numbers are typically defined in
a header file: signal.h
Operating systems that are compliant with the POSIX standard
must include the following names (associated numbers might be
different on different systems.)
Here are the integers for one Linux system (Ubuntu):
#define SIGHUP 1 /* Hangup (POSIX). */
#define SIGINT 2 /* Interrupt (ANSI). */
#define SIGQUIT 3 /* Quit (POSIX). */
#define SIGILL 4 /* Illegal instruction (ANSI). */
#define SIGTRAP 5 /* Trace trap (POSIX). */
#define SIGABRT 6 /* Abort (ANSI). */
#define SIGIOT 6 /* IOT trap (4.2 BSDES). */
#define SIGBUS 7 /* BUS error (4.2 BSD). */
#define SIGFPE 8 /* Floating-point exception (ANSI).
#*/
#define SIGKILL 9 /* Kill, unblockable (POSIX). */
#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */
#define SIGSEGV 11 /* Segmentation violation (ANSI). */
#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */
#define SIGPIPE 13 /* Broken pipe (POSIX). */
#define SIGALRM 14 /* Alarm clock (POSIX). */
#define SIGTERM 15 /* Termination (ANSI). */
#define SIGSTKFLT 16 /* Stack fault. */
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX).
#*/
#define SIGCONT 18 /* Continue (POSIX). */
#define SIGSTOP 19 /* Stop, unblockable (POSIX). */
#define SIGTSTP 20 /* Keyboard stop (POSIX). */
#define SIGTTIN 21 /* Background read from tty (POSIX).
#*/
#define SIGTTOU 22 /* Background write to tty (POSIX).
#*/
#define SIGURG 23 /* Urgent condition on socket (4.2
#BSD). */
#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */
#define SIGXFSZ 25 /* File size limit exceeded (4.2
#BSD). */
#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD).
#*/
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun).
#*/
#define SIGPOLL SIGIO /* Pollable event occurred (System
#V). */
#define SIGIO 29 /* I/O now possible (4.2 BSD). */
#define SIGPWR 30 /* Power failure restart (System V).
#*/
#define SIGSYS 31 /* Bad system call. */
#define SIGUNUSED 31
For each process, the operating system maintains 2 integers
with the bits corresponding to a signal numbers.
The two integers keep track of:
- pending signals
- blocked signals
With 32 bit integers, up to 32 different signals
can be represented.
In the example below, the SIGINT ( = 2) signal is blocked and
no signals are pending.
Pending Signals |
31 |
30 |
29 |
28 |
... |
3 |
2 |
1 |
0 |
0 |
0 |
0 |
0 |
... |
0 |
0 |
0 |
0 |
Blocked Signals |
31 |
30 |
29 |
28 |
... |
3 |
2 |
1 |
0 |
0 |
0 |
0 |
0 |
... |
0 |
1 |
0 |
0 |
A signal is sent to a process
setting the corresponding bit in the pending signals integer for
the process.
Each time the operating system selects a process to be run on a
processor, the pending and blocked integers are
checked.
If no signals are pending, the process is restarted normally
and continues executing at its next instruction.
If 1 or more signals are pending, but each one is blocked, the
process is also restarted normally but with the signals still
marked as pending.
If 1 or more signals are pending and NOT blocked, the operating
system executes the routine in the process's code to handle the
signal. If that handler routine returns, the process continues
where it would have normally executed its next instruction.
There are several default signal handler routines.
Each signal is associated with one of these default handler
routine.
The different default handler routines typically have one of
the following actions:
- ignore the signal; i.e., do nothing, just return - Ign
- terminate the process - Term
- unblock a stopped process - Cont
- block the process - Stop
Note
A ready process is one that
has all the resources it needs to execute except for a
processor.
A blocked process is one that needs
some resource or some event to occur before it is elligible to execute.
A process can replace the default signal handler for almost all
signals (but not SIGKILL) by its own handler function.
A signal handler function can have any name, but must have
return type void and have one int parameter.
Example, you might choose the name sigchld_handler for a signal handler for the SIGCHLD
signal (termination of a child process). Then the declaration
would be:
void sigchld_handler(int sig);
When a signal handler executes, the parameter passed to it is
the number of the signal.
A programmer can use the same
signal handler function to handle several signals. In this case
the handler would need to check the parameter to see which signal
was sent.
On the other hand, if a signal handler function only handles
one signal, it isn't necessary to bother examining the parameter
since it will always be that signal number.
Here is an example of registering a user defined signal handler
for the SIGINT signal. Note the signal is sent to a process by
typing ctrl-c in the console/window where the process is
running.
(
source)
1 /*------------------------------------------------------------------------
2 |
3 | File: signal03.c
4 |
5 | Description: This program repeatedly increments a counter. When
6 | overflow occurs, it increments an separate overflow counter.
7 | It has an infinite loop. Typing ctrl-c at the keyboard sends
8 | this process a SIGINT signal. A signal handler, sigint_handler
9 | catches the signal increments a signal counter and prints the
10 | current counts. The signal handler terminates the program when
11 | a maximum number of allowed SIGINT signals have been received.
12 | The user defined sigint_handler function executes instead of
13 | the default handler since sigint_handler is registered as
14 | the handler (the signal function does this).
15 |
16 |
17 | Created: 12 Jan 15
18 | Author: glancast
19 |
20 +-----------------------------------------------------------------------*/
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <signal.h>
24
25 const int MAXSIGNALS = 5;
26
27 void sigint_handler(int sig);
28
29 /**
30 * Counters are global so that
31 * the signal handler can access them.
32 */
33 int cnt = 0;
34 int overflow = 0;
35 int signals = 0;
36
37
38 int main(int argc, char *argv[])
39 {
40 printf("This program increments a counter. Type ctrl-c to send a SIGINT signal.\n");
41 printf("%d maximum number of SIGINT signals allowed.\n", MAXSIGNALS);
42
43 signal(SIGINT, sigint_handler);
44
45 while(1) {
46 if (cnt + 1 < cnt) {
47 cnt = 0;
48 overflow++;
49 } else {
50 cnt++;
51 }
52 }
53
54 return 0;
55 }
56
57
58 void sigint_handler(int sig)
59 {
60 signals++;
61 printf("\nsigint %d: overflow = %d, count = %d\n", signals, overflow, cnt);
62 if (signals == MAXSIGNALS) {
63 printf("\nMaximum number of SIGINT signals allowed is %d. Exiting\n", MAXSIGNALS);
64 printf("Edit MAXSIGNALS if you want more!\n\n");
65 exit(0);
66 }
67
68 }
A shell program reads a command with 0 or more arguments and
creates a child process to execute the command.
The default is for the shell to wait for the command execution
to finish; that is, to wait for the child process to terminate.
However, in Linux if the command line ends with &, the shell doesn't wait. The shell prints a
prompt and handles another command while the first command
executes in the background.
In this latter case, the shell has the responsibility to let
the operating system know that all the information about the child
can finally be discarded. Otherwise, the operating system keeps
the following information:
- How the child terminated - normally or otherwise
- The child's exit or return value if it terminated
normally
- The signal that caused the child to terminate if it
terminated abnormally.
This information is retained in the operating system's process
table entry for the child.
Freeing this entry and discarding the child information is
called reaping the process.
The shell can wait for the child to terminate by calling
waitpid:
int n;
int status;
...
n = waitpid(child_pid, &status, 0);
This would cause the parent (e.g., the shell) to wait until the
child process whose pid is child_pid to terminate.
- The return value would be equal to child_pid or -1 if no
such process existed or has already terminated and been waited
for.
- The status integer has some bits indicating the way the
child terminated (e.g. normally or killed by a signal) and if
terminated normally, its exit or return value.
The last parameter specifies options for waiting; 0, means wait
for the child to terminate, but other
options can specify wait until the child's state changes, not just
termination.
A successful call to waitpid will reap the child process; free its process table entry and
discard all information about the process.
If the shell creates a child process, but doesn't wait for it
(doesn't immediately call waitpid), it should call waitpid to reap the process when it terminates.
How will the shell know when the child terminates in this case?
The parent process (the shell) will receive a SIGCHLD signal when
the child terminates.
The shell needs to write a signal handler for the SIGCHLD
signal and can call waitpid in that handler to reap the child
process.
Note: The default handler for SIGCHLD is to just ignore it.
- p.
721 Output?
- 8.2
Output of child? Output of parent?
- 8.3
All possible output sequences?
- 8.4
All possible output sequences?
A program printCh is to be executed like
this if no command line arguments are given:
$ printCh
argc = 1
argv[0] = printCh
AAAAA
AAAAA
But if a command line argument is typed:
$ printCh B
argc = 2
argv[0] = printCh
argv[1] = B
BBBBB
BBBBB
Here is the code for printCh:
int main(int argc, char * argv[])
{
char ch;
if ( argc < 2 ) {
ch = 'A';
} else {
ch = argv[0][0];
}
printf("argc = %d\n", argc);
for(i = 0; i < argc; i++) {
printf("argv[%d] = %c\n", i, ch);
}
return 0;
}
How does this program get values for argc and argv if executed
from a shell?
One version of the exec system calls is:
#include <unistd.h>
int execvp(char *file, char* args[])
file - the name of the executable file. The 'p' means
that all directories in the PATH evironment variable will be
searched for the first occurrence of file.
args - this will be passed to the main function of
file as argv. But args should have one extra entry which
is NULL, so that execvp can count the number of elements in the
array and pass this count to main as argc
This example is similar to shell as it (1) creates a child
proces, (2) the child process executes the printCh program passing
arguments to it, and (3) the parent waits for the child to
finish.
Unlike a shell, this program only executes one command
(printCh) and instead of printing a prompt after the command
finishes, it prints how the child terminated and its exit value if
it terminated normally.
1
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <string.h> // for strerror
6 #include <errno.h> // for errno!
7
8 int main()
9 {
10 // Create a child process to run printCh with command line argument
11 // of C. Should work just like $ printCh C
12 pid_t spid, fpid;
13 char *args[3];
14 int status;
15
16 args[0] = "printCh";
17 args[1] = "C";
18 args[2] = (char *) NULL;
19
20 if ( (spid = fork()) == 0 ) {
21 execvp("printCh", args);
22 printf("Error executing printCh\n");
23 exit(1);
24 }
25 fpid = waitpid(spid, &status, 0);
26
27 if ( fpid < 0 ) {
28 printf("wait error: %s\n", strerror(errno));
29 }
30 if ( WIFEXITED(status) ) {
31 printf("child %d terminated normally with exit value %d\n",
32 fpid, WEXITSTATUS(status));
33 } else {
34 printf("child %d terminated abnormally\n", spid);
35 }
36 return 0;
37 }