CSC374 Jan13

slide version

single file version

Contents

  1. Child Termination
  2. Signals
  3. Some Signal Numbers
  4. Operating System Data for Signals
  5. Sending, Receiving Signals
  6. Default Signal Handlers
  7. User Defined Signal Handlers
  8. Registering a User Defined Signal Handler
  9. Waiting for a Child Process
  10. Wait for the Child to Finish
  11. Reaping Background Processes
  12. Practice Problems
  13. Summary Example: Executing a Program with Command Line Arguments
  14. Summary Example: The Program
  15. Summary Example: Which exec Version?
  16. Summary Example: Execution

Child Termination[1] [top]

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.

Signals[2] [top]

Some Signal Numbers[3] [top]

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

Operating System Data for Signals[4] [top]

For each process, the operating system maintains 2 integers with the bits corresponding to a signal numbers.

The two integers keep track of:

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

Sending, Receiving Signals[5] [top]

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.

Default Signal Handlers[6] [top]

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:

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.

User Defined Signal Handlers[7] [top]

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.

Registering a User Defined Signal Handler[8] [top]

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	}

Waiting for a Child Process[9] [top]

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:

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.

Wait for the Child to Finish[10] [top]

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

Reaping Background Processes[11] [top]

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.

Practice Problems[12] [top]

Summary Example: Executing a Program with Command Line Arguments[13] [top]

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
      

Summary Example: The Program[14] [top]

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?

Summary Example: Which exec Version?[15] [top]

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

Summary Example: Execution[16] [top]

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	}