Free Web Hosting Provider - Web Hosting - E-commerce - High Speed Internet - Free Web Page
Search the Web

 

 

 

 

 

Processes and Signals

 Back to Index
 

Introduction:

A good understanding of processes & signals is vital to anyone who wishes to develop programs within the Unix environment. In this document I will attempt to show how processes are handled within the Linux / Unix operating system(s). More precisly, this document will focus upon:

What is a Process


  One straightforward definition of  a  process is , "an instance of a program in executuion 1" . Furthermore, if we look at a program as an addressable space (an area of memory that can be addressed)  along with a thread of control which runs within that spacethen, where is the distinction between a program and a process? Why not use both terms interchangeably? As it turns out what generally constitutes a process in Unix is, a running program which has a process ID associated  with it. This process id (PID) enables the operating system to distinguish among the various processes which may be running on the system at any given time. In addition the operating system also keeps track of the process state (i.e. the execution status of the process at any given time), as well as the amount of memory utilized by the process. This information is vital to the operating system so that it can succesfully and efficiently manage and allocate system resources.Once the operating system has registered the vital information needed to manage a process and subsequently has allocated the needed system resources the program is referred to as a process.
Some people make further distinguisment between kinds of processes, referring to lightweight processes versus heavyweight processes. For instance, the type of process discussed here is referred to as a heavyweight process because of the required effort that the operating system performs to track the process and allocate needed resopurces. Lightweight processes on the other hand do not require as much overhead on the part of the operating system.  Lightweight processes are commonly referred to as threads. Both types of process have their advantages and disadvantages depending upon the programming task at hand.
 

   Back to Index

Process Structure

  •  

  • If we were able to look within the operating system and examine the layout of processes within the system we might see something which looks as follows. Note, the following diagram depicts 2 processes running the same program:

    process1.new.png  Image
    (diagram 1)

     First and foremost, each process is given a unique process id (PID) in order to identify the process. PID's are usually issues sequentially, each process is given the next available PID number.

    The actual program code  and shared libraries is shared between the two processes. There are two major advantages to this:
     

  • First, a separate copy of the program code does not have to be loaded into memory for each separate instance of a process running a particular  program. Therefore, only 1 instance of the code needs to be in memory, regardless of how many processes are running that particular program.
  • Second, the program itself does not need to contain the shared library code. Thus, the overall amount of disc space that the program occupies is smaller and, since the library code is shared  valuable memory resources are kept free.

  •  

     
     
     
     
     
     
     

    Note: In an MS Windows environment shared libraries are commonly referred to as dynamically linked libraries (dll's).

    Note that the program code and the shared libraries are shared between various processes. However, not all resources are shared. Certain process resources are unique to the individual process. Among these are:
     

  • Stack Space: which is where local variables, function calls, etc. are stored.
  • Environment Space: which is used for storage of specific environment variables.
  • Program Pointer (counter):
  • File Descriptors
  • Variables

  •  

     
     
     

    This discussion is summarized in Diagram 1.

     Back to Index
     

    The Process Table


     

    Viewing Processes

    Much information about processes can be derived via the /proc filesystem. According to the man page(s) descriptor /proc is described as a pseudo-filesystem which is used to interface kernel data structures. The following diagram describes the layout of the /proc filesystem:

    proc_fs.png  PNG Image

    Each sub directory under /proc corresponds to a running process (PID #). Thus for example /proc/205 corresponds to the process with PID 205. Much info is found within /proc/205 , and the information is held within the various files, links, and sub directories within each process sub directory. Some of this info is represented in the diagram above.  Further details regarding /proc can be found by viewing the proc man pages.

    A simple way to view the processes running on the system at any given time is to use the ps command:

    $ ps
    (See the man or info pages for ps for a description of the command, options, etc.)

    By default ps specifically shows processes which communicate with certain devices, i.e. terminals, consoles, etc. In addition it's default behavior it shows processes belonging to the user running the command. If we use ps with the a option:

    $ ps a
    will show processes belonging to all users. If we apply the x option ps will show all processes running, not just those which communicate with specific devices. Thus we can view all processes running by using both the a and the x option.

    Typical output of ps looks as follows:
     
              PID            TTY            STAT           TIME       COMMAND
             3823              3              SW             0:00         (login)
             3866              3              SW             0:00         (bash)
             3877              3              SW             0:00         (startx)
                .              .                .               .             .
              6912              p7               R             0:00            ps

    The columns refer to the following:
     

  • PID - the process id's (PID)
  • TTY - the terminal the process was started from
  • STAT - the current status of all the processes. Info about the process status can be broken into more than 1 field. The first of these fields can contain the following entries:
  • R - Runnable
  • S - Sleeping
  • D - Un-interuptable sleep
  • T - Stopped or Traced
  • Z - Zombie Process
  • The second field can contain the following entry:
  • W - if the process has no residual pages
  • And the third field:
  • N - if the process has a positive nice value
  • TIME - The CPU time used by the process so far
  • COMMAND - The actual command

  •  

     

    Therefore a line in the process table such as:

    3823   3   SW   0:00   (login):  implies that the login process, which is used to log in to the system was started from TTY 3. It has a PID of 3823, it has thus far used 0:00 of CPU time and it is currently sleeping.

    6912   p7   R   0:00   ps: implies that PID 6912 refers to the ps  command which was started from p7 (probably an xterm), It's current status is runnable and it has thus far used 0:00 of CPU time.

    Extended process info can be gotten by running ps  with the l (long format) option:

    $ ps l  ==> process table in long format
    See the man pages for the ps command to learn about all the various options and the information displayed.
     
     

     Back to Index

    System Processes


    On a Unix/Linux system each existing process is spawned from some other pre-existing process. The process which does the spawning is referred to as the Parent Process. The process which is spawned by the parent process is referred to as the Child Process.
    Now, if it is so that every process is the child of some other process this creates a sort od process hierarchy and if we follow this hierarchy up to it's top level it would imply that there must be some initial process(s) from which all other processes are started. As it turns out such is the case. If we examine the process table for all existing process on the system via:
    $ ps ax
    and examine the resulting process table we will find an entry which looks something  like this:
     
              PID            TTY            STAT           TIME      COMMAND
                1               ?                S            0:03         init
    This entry refers to the init process. The init process is essentially the grand daddy of all other processes. It is the very first process run upon startup. It's role is to start additional processes which are vital to normal system operation at the various runlevels. When init runs it reads a file called /etc/inittab which tells init how to set up the system, what processes it should start with respect to specific runlevels. One very important process which init usually starts is the getty  program. A getty process is usually started for each terminal upon which a user can log into the system. The getty  program produces the login: prompt on each terminal and then waits for activity. Once a getty process detects activity (at a user attempts to log in to the system), the getty program passes control over to the login program.
    Obviously init and the content of /etc/inittab are extremely important because it determines what processes are initially started for each runlevel, it basically manages processes on the system.

    For more information regarding init , /etc/inittab, and runlevels  check out the init man page(s).

     Back to Index

    Process Scheduling


    On a single processor computer only 1 process can run at a time while others await their turn. These turns are referred to as time slices. They are quite short and give the impression that many processes are running simultaneously.  The R shown in the STAT column of the command ps -l implies that the process is ready to run, not that it is nessesarily running at that very instant.

    Unix uses a process scheduler to decide what process to allocate the next time slice to. It is based on process priority. Higher priority processes get to run more often than lower priority ones. In Unix, processes cannot overrun their allocated time slice. They are preemptively multitasked , that is, they are started and stopped without the cooperation of the process.

    2 commands we can use to set process priority are the commands nice and renice. Priorities follow a scale which looks like:

            highest (-20) ------------- 0 ---------------- (+20) lowest
                                                           default

    By default, the nice command starts a program with a priority of +10

    We can view the priorities of active processes by using the -l option for ps., i.e. $ ps

    We use the nice command as:
    $ nice oclock &
    And be default this will start the oclock program at a priority level of +10.
    Let's say we want to change the priority to +15. We can use the renice command as follows:

    $ renice 15 255

    In which 15 is the desired priority and 255 is the PID for the process

     Back to Index

    Starting New Processes


    We can cause a program to be run from within another program, thereby creating a new process via the system() library function. It's syntax is:



    #include <stdlib.h>

    int system(const char *string);



    This function runs the command passed to it as string and waits for it to complete. system returns a value of 127 if a shell can't be started to run the command and it returns -1 if any other error occurrs. Otherwise it returns the exit code of the command.

    ex: we can use system() to run the ps command as follows:
     

    /* system.c */

    #include <stdio.h>
    #include <stdlib.h>

    int main()
    {

      printf("Running ps with system\n");
      system("ps -ax");
      printf("Done \n");

     exit(0);
    }

    Since the system( )  function uses a shell to start the program we could then consider putting it into the background. Let's go back to the listing for system.c and change the function call to system("ps -ax &");

    When we use the command system("ps -ax");  the ps program is executed. The program returns from the call to system once the ps  command has finished. The system call is somewhat limited because the program has to wait untill the process started by the call to system finishes before we get on with other tasks.

    Using system("ps -ax &"); the call to system() returns as sooon as the shell command finishes. Since it is a request to run a program in the background the shell returns as soon as the ps program is started. This is the same thing that would happen as if we types ps -ax & on the command line.  The output from ps follows the shell prompt put out after system exits.
    Let'snow look at another type of interface which provides us finer control over the processes.
     Back to Index


     

    Replacing a Process Image

    Let's look at the "exec" functtions. These functions replace a current process with another created according to the arguments given. The syntax of these functions is as follows:

    #include <unistd.h>

    char **environ;

    int execl(const char *path, const char *arg0, ..., (char *)0 );
    int execlp(const char *path, const char *arg0, ..., (char *)0 );
    int execle(const char *path, const char *arg0, ... , (char *)0, const char *envp[] );
    int execv( const char *path, const char *argv[] );
    int execvp( const char *path, const char *argv[] );
    int execve( const char *path, const char *argv[], const char *envp[] );

    The program given by the path argument is used as the program code to execute in place of what is currently running.  In the case of the execl the new program is passed arguments arg0, arg1, arg2,... up to a null pointer. By convention, the first argument supplied (i.e. arg0) should point to the file name of the file being executed. In the case of the execv  programs the arguments can be given in the form of a pointer to an array of strings, i.e. the  argv  array. The new program starts with the given arguments appearing in the argv array passed to main. Again, by convention, the first argument listed should point to the file name of the file being executed..

    The function name suffixed with a p differ in that they will search the PATH environment variable to find the new program executeable file. If the executeable isn;t on the path, and absolute file name, including directories, will need to be passed to the function as a parameter.

    The global variable environ is available to pass a value for the new program environment. In addition, an additional argument to the exec functions execle and execve is available for passing an array of strings to be used as the new program environment.

    We can use the exec functions to run the ps program as follows:

    #include <unistd.h>

    /* example of an argument list */

    /* note that we need a program name for argv[0] */

    const char *ps_argv[] = ("ps", "-ax", 0);

    /* Example environment, not terribly useful */

    const char *ps_envp[] = {"PATH=/bin:/usr/bin", "TERM=console", 0};

    /* Possible calls to exec functions */

    execl("/bin/ps", "ps", "-ax", 0);   /* assumes ps is in bin */

    execlp("ps", "ps", "-ax", 0);   /* assumes "/bin" is in PATH */
    execle("/bin/ps", "ps", "-ax", 0, ps_envp);  /* passes own environment */

    execv("/bin/ps", ps_argv);
    execvp("ps", ps_argv);
    execve("/bin/ps", ps_argv, ps_envp);

    Examples:

    1) Let's use execl to run the command ls -l. We can accomplish this as follows:

    #include <unistd.h>
    #include <stdio.h>

    int main()
    {
       printf("Using execl to run ls -l.\n");
       execl("/bin/ps", "ls" , "-l", 0);
       exit(0);
    }
     

    Note that for the path  argument we supplied the full path "/bin/ls". Also, for our first argument arg0 we supply the file name ls which is the command being executed. When we compile and run this program the output will be the listing for the directory from which the command is run.

    2) Lets do the same thing only using the execlp  function:
     

    #include <unistd.h>
    #include <stdio.h>

    int main()
    {
      printf("Using execlp to run ls -l \n");
      execlp("ls", "ls", "-l", 0);
      exit(0);
    }
     

    The only difference here is that for the path argument it suffices to use the name of the function being executed without supplying the entire path to that file. This is because the execlp function will search our entire PATH environment variable to find ls assuming that it is in our path. If ihowever ls were not in out path then we would have had to supply the entire absolute file name /bin/ls.

    3) Let's now do the same using execle:
     

    #include <unistd.h>
    #include <stdio.h>

    int main()
    {
       const char *ls_envp[]= {"PATH=/bin:/usr/bin", "TERM=console");
       printf("Using execle to run ls -l \n");
       execle("/bin/ls", "ls", "-l", 0, le_envp);
       exit(0);
    }
     

    In this case we are passing a new program environment to the program.

    4) Now using execv is basically the same as for the first example suing  execl except that we suppy our arguments in the form of an arrtay. So, the program would look as follows:
     

    #include <unistd.h>
    #include <stdio.h>

    int main()
    {
       char *ls_argv[] = { "ls", "-l", 0};

       printf("Running ls -l with execv\n");
       execv("/bin/ls", ls_argv);
       exit(0);
     }
     

    The array ls_argv[] = { "ls", "-l", 0}; is an array of null terminated strings. The first array element, (i.e. the first argument) should point to the file name associated with the file being executed, in this case ls . The list of arguments in the array must be terminated with a null pointer.

    5) Using execvp is exactly the same as for execv except, that execvp will automatically search the PATH environment variable. Since ls is in the path defined by this environment variable we don't have to specify the full pathname fot the file being executed in the *path argument. Thus to use execvp we can use the same program as in example 4, only changing the command line
    execv("/bin/ls", ls_argv);  with:
     

    execvp( "ls", ls_argv );
    6) Using execve() function we pass an additional array of null terminated strings which act as the new program environment:
     
    #include <unistd.h>
    #include <stdio.h>

    int main()
    {
      char *ls_argv[] = { "ls", "-l", 0};
      char *ls_envp[] = { "PATH=/bin:/usr/bin", "TERM=xterm", 0 };

      printf("Using execve to run ls -l \n");
      execve("/bin/ls", ls_argv, ls_envp);
      exit(0);
    }
     

    Note that similar to the argument array ls_argv, the environment array ls_envp is again an array of null terminated strings and, the final element of the list must be a null pointer.

    Let's look at a similar program which uses an execlp call to run ps:
     

    /* pexec.c */
    #include <unistd.h>
    #include <stdio.h>

    int main()
    {
       printf("Running ps with execlp\n");
       execlp("ps", "ps", "-ax", 0);
       exit(0);
    }
     

    This abbreviated output looks as follows:
     
    [holotko@landreau UNIX-Programming]$ ./pexec
    Running ps with execlp
    warning: `-' deprecated; use `ps ax', not `ps -ax'
      PID TTY STAT TIME COMMAND
        1  ?  S    0:03 (init)
        2  ?  SW   0:00 (kflushd)
        3  ?  SW<  0:00 (kswapd)

     1012  p1 S    0:00 script photo.log
     1013  p6 S    0:00 bash -i
     1014  p6 R    0:00 ps -ax
     

    Note that the program prints the first message (first printf() statement) then calls execlp which then searches the directories given in the PATH environment variable for a new program called ps. It then executes ps in place of our pexec program starting it just as if we had entered the command
    $ ps -ax from the shell prompt.

    Note thar once ps finishes we arrive at a new shell prompt, pexec does not resume and thus the second printf() statement does not get executed. The PID of the new process has the same PID as the origional, and similarly are the parent PID and nice values. Essentially, what has occurred is that the running program has started to execute mew code from a new executeable file specified in the call to exec.

    There is a limit on the combined size of the argument list and environment for a process started by the exec functions. This is given by ARG_MAX and on most Linux systems is 128k bytes. POSIXspecs require that ARG_MAX be at least 4096 bytes.

    The exec funcions generally don't return values except when an error occurrs in which case the error variable errno is set and the exec function returns -1.

    New processes started by exec inherit many features from the origional. Particularly, open file descriptors remain open in the new process unless their "close on exec" flag has been set. Any open directory streams in the origional process are closed.

     Back to Index

    Duplicating a Process Image (fork):

    In order to get a process to perform more than one function at a time we must create an entirely separate process from within a program, rather than just replacing the current thread of execution, as with exec.

    We can use the function fork() to actually create a new process. This system call duplicates the current process, creating a new entry in the process table with many of the same attributes as the origiional. The new process will be almost identical to the origional, it will execute the same code, but it will have it''s own data space, environment, and file descriptors. When we combine exec witth fork we can create new processes. The systax of fork is:
     

    #include <sys/types.h>
    #include <unistd.h>

    pid_t fork( void );
     

    The fork() system call creates a new child process. It is identical to the calling process except that the new process has a unique process ID, and has the calling process as it's parent PID. The new child process inherits a copy of the parents data space (variables), open file descriptors, and directory streams.

    The call to fork() in the parent returns the PIDof the new child process. The new process continues to execute just as the origional however, in the child process the call to fork() returns 0. This allows both parent and child process to determine which is which.

    fork() returns -1 on failure. Offten this occurrs when we try to exceed a limit (CHILD_MAX) on the number of child processes which a parent may have, in which case errno will be set to EAGAIN.

    Typical code segment using fork looks as follows:
     

    pid_t = new_pid;

    new_pid = fork();

    switch(new_pid)  {
      case -1 : /* error */
        break;
      case 0 :  /* child process */
        break;
      default:  /* parent process */
        break;
     }
     

    The following example demonstrates a simple useage of fork():
    /* fork1.c */

    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>

    int main()
    {

      pid_t pid;
      char *message;
      int n;

      printf("Fork program starts here...\n");
      pid = fork();
      switch(pid) {

      case -1:
        exit(1);

      case 0:
        message = "This is the child process";
        n = 5;
        break;

      default:

        message = "This is the parent process";
        n = 3;
        break;
      }

      for (; n > 0; n--) {
        puts(message);
        sleep(5);
      }

      exit(0);

    }
     

    This program actually runs as 2 processes. A child process is created and pronts a message 5 times. The origional process prints a message only 3x. The output looks as follows:
     
    [holotko@landreau UNIX-Programming]$ ./fork1
    Fork program starts here...
    This is the parent process
    This is the child process
    This is the parent process
    This is the child process
    This is the parent process
    This is the child process
    [holotko@landreau UNIX-Programming]$ This is the child process
    This is the child process
    Note that the parent process has finished before the child process. Thus  output from the child process appears after the shell prompt.

     Back to Index

    Waiting for a Process

    A child process started with fork()  basically runs independently. Sonmetimes, within a program, we'd like to determine when a child process has completed it's job  and finished. We can arrange for the parent process to wait untill the child finishes before continuing by calling wait(). The syntax for wait() is as follows:
     
    #include <sys/types.h>
    #include <sys/wait.h>

    pid_t wait( int *stat_loc );
     

    wait() causes a parent process to pause untill one of the child processes dies or is stopped. The call returns the PID of the child process for which status information is available. This will usually be a child process which has terminated. The status information allows the parent process to determine the exit status of the child process, the value returned from main or passed to exit. If it is not a null pointer the status information will be written to the location pointed to by stat_loc.

    We can interrogate the status information using macros defined in sys/wait.h. These are listed in the following table:
     

     Back to Index
     

               Macro            Definition
    WIFEXITED(stat_val); Nonzero if the child is terminated normally
    WEXITSTATUS(stat_val); If WIFEXITED is nonzero, this returns child exit code.
    WIFSIGNALLED(stat_val); Nonzero if the child is terminated on an uncaught signal.
    WTERMSIG(stat_val); If WIFSIGNALLED is nonzero, this returns a signal number.
    WIFSTOPPED(stat_val); Nonzero if the child stopped on a signal.
    WSTOPSIG(stat_val); If WIFSTOPPED is nonzero, this returns a signal number.
    Let'smodify the previos sample program so that we can wait for and examine the child processes exit status.
     
    /* wait.c */

    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <stdio.h>

    int main()
    {

      pid_t pid;
      char *message;
      int n;
      int exit_code;

      printf("Fork program starting...\n");
      pid = fork();
      switch(pid) {

      case -1:
       exit(1);
      case 0:
       message = "This is the child";
       n = 5;
       exit_code = 37;
       break;
      default:
       message = "this is the parent";
       n = 3;
       exit_code = 0;
       break;
      }

      for (; n > 0; n--) {
        puts(message);
        sleep(1);
      }

      /* This section of the program waits for the child process to finish */

      if (pid) {
        int stat_val;
        pid_t child_pid;

        child_pid = wait(&stat_val);

        printf("Child has finished: PID = %d\n", child_pid);
        if (WIFEXITED(stat_val))
           printf("Child exited with code %d\n", WEXITSTATUS(stat_val));
        else
           printf("Child terminated abnormally.\n");
       }
      exit(exit_code);

      }
     

    The output of this program looks like this:
     
    [holotko@landreau UNIX-Programming]$ ./wait
    Fork program starting...
    this is the parent
    This is the child
    This is the child
    this is the parent
    this is the parent
    This is the child
    This is the child
    This is the child
    Child has finished: PID = 825
    Child exited with code 37
    The parent process, which gets a nonzero return from fork(), uses wait() to suspend it's own execution untill status information becomes available for the child process. This occurrs when the child process calls exit(exit_code), The parent process then resumes and extracts and displays the exit code and information.

    In the next program start a parent process which  runs through a loop several  times. It then starts a child process. The parent process is then suspended via wait untill status information becomes available for the child process. While the parent process is waiting, the child process is running through several repetitve counts of a loop. Once the status information for the child process is received the parent  process again resumes running, it runs through several repititions of a for loop and then exits extracting and displaying information about the child process. The program looks as follows:
     Back to Index

    /* fork4.c */

    #include <sys/types.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <sys/wait.h>

    int main()
    {

      pid_t pid, child_pid;
      int x;
      int stat;

      for (x = 0; x < 5; x++) {
        printf("%d Parent process running...\n", x);
        sleep(1);
      }

      printf("Starting a new child process...\n");
      pid = fork();

      if (pid) {
        child_pid = wait(&stat);
        for (x = 0; x < 5; x++) {
          printf("%d Parent process running...\n", x);
          sleep(1);
        }
      if (WIFEXITED(stat)) {
        printf("The child process exited normally...\n");
        printf("Exit status was...%d\n", WEXITSTATUS(stat));
        printf("The PID of the child process was %d\n", child_pid);
      }
      else
       printf("Child process terminated abnormally...\n");
      }

      else if (!pid) {
        for (x = 0; x <= 5; x++) {
          printf("%d Child process running...\n", x);
          sleep(1);
        }
      }
     
     

     exit(0);

    }
     

      And the output from this program  looks as follows:
     
    [holotko@landreau UNIX-Programming]$ ./fork4
    0 Parent process running...
    1 Parent process running...
    2 Parent process running...
    3 Parent process running...
    4 Parent process running...
    Starting a new child process...
    0 Child process running...
    1 Child process running...
    2 Child process running...
    3 Child process running...
    4 Child process running...
    5 Child process running...
    0 Parent process running...
    1 Parent process running...
    2 Parent process running...
    3 Parent process running...
    4 Parent process running...
    The child process exited normally...
    Exit status was...0
    The PID of the child process was 853
     Back to Index

    Zombie Processes: 

    When using fork() to create child processes it is important to keep track of these processes. For instance, when a child process terminates, an association with the parent survives untill the parent either terminates normally or calls wait(). The child process entry in the process table is not freed up immediately. Although it is no longer active, the child process is still in the system because it's exit code needs to be stored in the even the parent process calls wait().  The child process is at that point referred to as a zombie process.

    Consider the  fork1.c program . If we change the program so that the child process prints fewer messages than the parent process, the child process will therefore finish before the parent, and will exist as a zombie untill the parent finishes and exits  normally.

    As an alternate example consider the following program:

     Back to Index

     
    /* zombie1.c */

    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/wait.h>
    #include <stdio.h>

    int main()
    {

      pid_t pid, child_pid;
      int i;
      int stat_loc;

      printf("Starting child process...\n");
      pid = fork();

      if (pid) {
        for (i = 0; i < 10; i++) {
          printf("Parent process running...\n");
          sleep(2);
        }
        child_pid = wait( &stat_loc);
        if (WIFEXITED(stat_loc)) {
          printf("Child process exited normally...\n");
          printf("Terminated with exit code %d\n", WEXITSTATUS(stat_loc));
          printf("PID == %d\n", child_pid);
        }
       }
       else if (!pid)  {
         for (i = 0; i < 5; i++)  {
           printf("Child process running...\n");
           sleep(1);
         }
       }

     exit(0);
    }
     

    If we compile and run the above program, and then examine the process table at the point where the child process finishes printing, we will see an entry in the table for a zombie process. However, once the parent calls wait() the zombie process will dissapear from the table. A zombie exists when a child process dies and the  parent process is still running. It dissapears when the parent program either calls wait or terminates normally.

    Note, if the parent terminates abnormally then the child process gets the  process with PID 1, (init) as parent. (such a child process is often referred to as an orphan). The child process is now a zombie. It is no longer running, it's origional parent process is gone, and it has been inherited by init. It will remain in the process table as a zombie untill the next time the table is processed. If the process table is long this may take a while. till init cleans them up.

    As a general rule, program wisely and try to avoid zombie processes. When zobbies accumulate they eat up valuable resources.

     Back to Index

     The waitpid() System Call:

    The waitpid() system call is another call that can be used to wait for child processes. This system call however can be used to wait for a specific process to terminate.
    It's syntax is as follows:
     
    #include <sys/types.h>
    #include <sys/wait.h>

    pit_t waitpid(pid_t pid, int *stat_loc, int options);
     

    The pid argument specifies the PID of the particular child process to wait for. If it is a -1 then waitpid() will return information to the child process. Status information will be written to the location pointed to by stat_loc. The options  argument enables us to change the behavior of waitpid() . A very usefull oprion is WNOHANG which prevents the call to waitpid() from suspending the execution of the caller.  we can therby use it to find out whether any child process has terminated and, if not, to continue. See the man pages for other options.

    Lets say we want to have a parent process regularly check whether or not a specific child process has terminated. We could use  a call such as:
     

    waitpid( child_pid, (int *) 0, WNOHANG);
    will return 0 if the child has not terminated nor stopped. or child_pid if it has. waitpid() will return -1 on error and ser errno.
    The following program demonstrates the useage of waitpid(). It allows a parent process to continue while repeatedly monitoring fornthe termintation of a child process:
     
    /* waitpid1.c */

    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    #include <stdio.h>

    int main()
    {

      pid_t pid, child_pid;
      int i, status;

      printf("Starting child process...\n");
      if ( (pid = fork()) == -1 ) {
        fprintf(stderr, "Fork process did not work, no child created, aborting...\n");
        exit(1);
      }

      if (pid)  {
       while( (child_pid = waitpid(-1, &status, WNOHANG)) == 0)  {
         printf("This is the parent process, pid = %ld \n", (long)getpid() );
         sleep(1);
       }
       if (WIFEXITED(status))  {
          printf("The child exited normally...\n");
          printf("Exit status = %d\n", WEXITSTATUS(status));
          printf("Exit code = %ld \n", (long)child_pid);
       }
       else
         printf("The child exited abnormally...\n");
      }
      else if (!pid)  {
        for (i = 0; i < 10; i++)  {
           printf("This is the child process...\n");
           sleep(1);
        }
      }

     exit(0);

    }
     

     Back to Index

    Input and Output Redirection:

    We can apply our knowledge of processes to alter the behavior of programs be exploiting  the fact that open file descriptors are preserved across calls to fork() and exec(). For example, lets say we have a program  which acts as a filter, it reads from stdin and writes to stdout after performing some form of transformation. For instance, the following filter program reads data from stdin and converts all charachters to uppercase, before displaying them to stdout.
     
    /* upper.c */

    #include <stdio.h>
    #include <ctype.h>

    int main()
    {

      int ch;
      while ((ch = getchar()) != EOF) {
        putchar(toupper(ch));
      }

      exit(0);

    }
     

    When this program is run it reads our input and converts it to uppercase. for example:
     
    $ ./upper
    hello
    HELLO
    Nice Day TODAY
    NICE DAY TODAY
    ^C
    We can convert an entire file to uppercase using shell redirection. For example:
     
    $ upper < file.txt
    Let's say that we would like to use this filter from within another program. The following program accepts a  filename as an argument and runs the program upper  on that file to produce uppercase output:
     
    /* useupper1.c */

    #include <unistd.h>
    #include <stdio.h>

    int main( int argc, char *argv[])
    {

      char *filename;

      if (argc != 2) {
        fprintf(stderr, "useage: useupper1 file \n");
        exit(1);
      }

      filename = argv[1];

      if (!freopen(filename, "r", stdin)) {
        fprintf(stderr, "could not redirect stdin to file %s \n", filename);
        exit(2);
      }

      execl("/home/holtko/UNIX-Programming/upper", "upper", 0);

      /* execl repaces the current process provided there is no error the *
       * following lines will not be executed.                            */

      fprintf(stderr, "could not exec upper. \n");
      exit(3);

    }
     

    When we run this program we give it a file to convert to uppercase: ex:
     
    $ ./useupper1 file.txt
    The program uses freopen()  to close the standard input and associate the file stream stdin with the file given as a program argument. It then calls execl()  to replace the running process code with that of the upper program. Since the open file descriptors are preserved across the call to execl()  the upper program runs exactly as it would havve under the shell command:

     Back to Index
     

    $ ./upper < file.txt

    Threads


    Another class of processes are known as threads. These are commonly available in Microsoft NT, Windows 95/98, Java, etc. They are distict from the notion of a process in that they are separate execution streams within a single process.  The main value of threads over multiple processes is that they are lightweight processes, using threads a programmer can acheive parrellellism at a low overhead, i.e. threads don't incur the cost of address space initializeations and process startups as seen with "heavyweight" operating system processes. Threads however do add certain complications in their requirements for synchronization.

    Untill recently there has not been a standard for using multiple threads of execution within a Unix environment. This has changed as a POSIX extension was approved in June of 1995. Since the recent adoption of a Posix standard for threads, threaded applications should be becoming more commonplace within the Unix environment.

    For simplicities sake threads will not be covered in this document at present. Perhaps they will be added at a later time, or perhaps a entire document devoted to threading will be provided. Meanwhile you may turn to other Unix programming sources for detailed information concerning Posix threads. For example see Chapter 9 "Posix Threads" of the book "Practical Unix Programming" by "kay A. Robbins & Steven Robbins", Published by Prentice Hall PTR, ISBN 0-13-443706-3. See:  Prentice hall   for book and bookstore information.

     Back to Index
     
     

    Signals


    A signal is an event that is generated by the Unix system in response to some condition. Upon receipt of a signal a process may take some sort of action. Signals are generated by some error conditions, such as memory segment errors, floating point processor errors, illegal instructions, etc. They are generated by the shell and terminal handlers to cause interrupts. They can also be explicitly sent from one process to another as a means of passing information or of modifying behavior. Signals can be generated, caught, and acted upon. In some cases theyu may be ignored.

    Signal names are defined in the header file signal.h. They all begin with the prefix SIG. The following chart lists some important signals and their meanings:

     Back to Index
     
               Signal Name          Description Implementation dependent action?
    SIGABORT Process Abort Yes
    SIGALARM Alarm Clock No
    SIGFPE Floating point Exception Yes
    SIGHUP Hangup No
    SIGILL Illegal Instruction Yes
    SIGINT Terminal Interrupt No
    SIGKILL Kill (Can't be caught or ignored) No
    SIGPIPE Write on a pipe with no reader No
    SIGQUIT Terminal Quit No
    SIGSEGV Invalid memory segment Access Yes
    SIGTERM Termination No
    SIGUSR1 User-defined signal 1 No
    SIGUSR2 User-defined signal 2 No
    If a process receives one of these signals without arranging to catch it, the process will be terminated immediately. Implementation dependent action implies that such implementation dependent actions may be taken. Usually a core file is written. The file called core is usually placed in the current directory. It is an image of the process and it can be quite usefull in debugging.

    There are some additional signals including:
     
     
               Signal name            Description
    SIGCHLD Child process has stopped or exited
    SIGCONT Continue exiting, if stopped
    SIGSTOP Stop executing (can't be caught or ignored)
    SIGTSTP Terminal stop signal
    SIGTTIN Background process trying to read
    SIGTTOU Background process trying to write
    SIGCHLD can be quite usefull for managing child processes. By default it is ignored. The remaining signals cause the processes receiving them to stop except for SIGCONT which will cause the process to resume. These are mainly used by shell programs for job control and are seldom used by user programs.
     

    If the shell & terminal driver are properly configured typing the interrupt charachter (often CNTL-C) on the keyboard will result in the signal SIGINT being sent to the foreground process which will cause the program to terminate unless it has arranged to catch the signal. Signals can be handled via the signal library function. It's syntax is as follows: 
     

    #include <signal.h>

    void (*signal(int sig, void (*func)(int)))(int);
     

    signal requires 2 parameters.
      The signal function itself returns a function of the same type, which is the previous value of the function set up to handle this signal, or, one of these 2 special values:
      On Linyux systems the default behavior is automatically restored after the specified signal has been caught. There is another special value, SIG_ERR which can never be used in a call to signal. It can only be returned in an error code.

    To make this all a bit clearer lets make a short program  that reacts to Cntl-C by printing the appropriate message rather than terminating. A second Cntl-C will terminate the program. Ex:

     Back to Index

    /* ctrl1.c */

    #include <signal.h>
    #include <stdio.h>
    #include <unistd.h>

    void ouch( int sig )
    {

      printf("OUCH!! -- I got the signal %d\n", sig);
      signal( SIGINT, SIG_DFL );
    }

    int main()
    {
       (void) signal(SIGINT, ouch);

       while(1) {
        printf("Hello Ball! \n");
        sleep(1);
       }
    }
     

    This program enables ouch to be called when we hit Ctrl-C while it is running. Ctrl-C gives it the SIGNINT signal. After that the default signal handling action is restored and the next time we hit Ctrl-C and the program receives a SIGINT the program terminates.

    Note! : This behavior is different for different Unix system. For instance, if we removed the line signal(SIGINT, SIG_DFL),  from the function ouch the default behavior would not return after we hit Ctrl-c and the program receives SIGINT. Thus, subsequent hits of Ctrl-C would be handled by ouch. On other systems the default behavior is restored after the first signal is received.

    The signal function returns the previous value of the signal handler for the specified signal, if there is one, or SIG_ERR otherwize, in which case errno will be set to a positive value. errno will be set to EINVAL if an invalid signal is specified or an attempt is made to handle a signal that may not be caught or ignored, such as SIGKILL.

     Back to Index

    sigaction(), A better signal()

    The current X/Open specification reccomends the use of a much more robust programming interface for signals. The sigaction() function. It is defined as follows:
      sa_handler, specifies the action to be associated with signum. and may be SIG_DFL (for the default action), SIG_IGN (to ignore this signal), or if neither of these 2 then it can be a pointer to a signal handling function.

    sa_mask: gives a mask of signals which should be blocked during execution of the signal handler. In addition, the signal which triggered the handler will be blocked unless the SA_NODEFER, or SA_NOMASK flags are used.

    sa_flags: specifies a set of flags which modifies the behavior of the signal handling process. It is formed by the bitwise OR of zero, or one or more of the following:
     

    Many of the system calls that a program uses are interruptable, i.e. when they receive a signal theywill return with an error and errno   will be set to EINTR to indicate that the function returned due to a signal. This therefore requires great care by an application which uses signals.For instance if SA_RESTART is set in the sa_flags field in a call to sigaction, a function which might otherwise be interrupted by a signal will instead be restarted once the signal handling function has been executed,

    Normally when a signal handling function is executed, the signal received is added to the process signal mask, for the duration of the handling function. This will prevent a subsequent occurrence of the same signal causing the signal handling function to run again. If the function is not re-entrant, it can sometimes cause problems.  setting the SA_NODEFER flag results in the signal mask no being altered when it receives it's signal.

    It is possible that a signal handling function might be interrupted in the middle, and then called again by something else. When we come back to the first call it is important that the signal handling function still operates properly. It must not be just recursive, in which it calls itself, but it should be re-entrant, where it can be executed again with no problems. For ex: interrupt service routined that deal with more than 1 device at a time  must be re-entrant.

    Check the following list of functions that are safe to call from within a signal handler.These are guaranteed by the X/OPEN specifications to be re-entrant and not  raising signals themselves:

     Safe Functions


     The following code sample sets the signal handler for SIGINT to mysighand.
     

    A signal handler is an ordinary function which 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 values. The usefullness of signal handlers is limited by the inability to pass values to them. This capability has been added to what is referred to as realtime signals.

    The following program causes the process to ignore SIGINT the first time it is delivered to the process. After that, the default signal handler is restored:

    When the program catches SIGINT the first time (generated by pressing Ctrl-C) it is handled by the signal handler foo()  and displays the message "I got signal  2" which is the value for SIGINT that is passed by the operating system to the program. When a second SIGINT is passed to the program (by pressing Ctrl-C again), the program stops because the action of the default signal handler has been restored. The reason it has been restored after the first signal is because we set: Setting this flag causes the default signal handler to be restored after the signall is received the first time.

     Back to Index

    Sending Signals

    A process can send a signal to itself by calling raise(). It's syntax  is:
    #include <signal.h>

    int raise( int sig );
     

    Any established signal handler for the specified signal sig will be executed. raise returns 0 on success, non-zero with errno set to EINVAL if the argument is not a valid signal number.

    A process can send a signal to itself, or another process via the kill() function. This is essentially the program equivalent of the shell command with the same name. It syntax is:
     
    #include <sys/types.h>
    #include <signal.h>

    int kill(pid_t pid, int sig);
     

    kill sends the specified signal sig to the process who's identifier is given by pid. If successfull it returns 0.
    In order to send a signal to a process the sending process must have permission to do so. In general this means that both processes must have the same user ID. In other words you can only send a signal to your own processes. The superuser however may send signals to any process...

    kill will fail, return -1 and set errno to EINVAL if the signal given is not a valid one. If it does not have permisson errno will be set to EPERM and if the specified process does not exist it will be set to ESRCH.


    Signals also provuide us with a useful alarm clock utility. The alarm  function can be used by a process to schedule a  SIGALRM signal at some time in the future. It's syntax is as follows:
     

    #include <unistd.h>

    unsigned int alarm(unsigned int seconds);
     

    This call schedules the delivery of a SIGALRM signal in seconds second's time. a value of 0 will cancel any outstanding alarm request. Calling alarm  before the signal is received will cause the alarm to be rescheduled. Each process can have only 1 outstanding alarm.

    alarm returns the number of seconds to go before any outstanding alarm call would be sent.

     This next program simulates the effect of alarm using fork, sleep, and signal. Thie following program starts a new process for the purpose of sending a signal at a later time.

     Back to Index

    The above program starts a new process via fork(). The child process sleeps for 5 seconds and then sends a SIGALRM to it's parent.  The parent arranges to catch SIGALRM and then pauses until a  signal is received.


    Note that the above program introduced a new function called pause() which simply causes a program to suspend execution untill a signal actually occurrs. Once it received a signal, any established handler is run and executtion will continue as normal. The pause() function has the following syntax:

     Back to Index

    This next program demonstrates the useage of the alarm() function:
        The program sends a SIGALRM to itself  and the signal is handled via the stop function which in turn raises the SIGTERM signal. SIGTERM causes the program to terminate since it can neither be caught or ignored.
    .
     Back to Index

    Signal Sets

    The following functions are used to manipulate sets of signals. these sets of signals are used in sigaction and other functions to modify the process behavior upon receipt of a signal. For a process the signal mask  gives the set of signals which are currently blocked. The signal set is of type sigset_t  defined in signal.h.

    Blocking of a signal is quite different than ignoring a signal. When a process ignores a signal the process throws the signal away upon it's delivery. However, when a process blocks a signal an occurence of the signal is held untill the process unblocks the signal. A process blocks a signal by modifying it's signal mask with sigprocmask.

    We can specify operations such as blocking or unblocking on groups of signals using signal sets of type sigset_t along with the following signal set functions:

     Back to Index


     

    Function
    Action Performed
    int sigemptyset(sigset_t *set) Initialized a signal set to contain no signals
    int sigfillset(sigset_t *set) initializes a signal set to contain all signals
    int sigaddset(sigset_t *set, int signo); Puts a specified signal into the set
    int sigdelset(sigset_t *set, int signo); Removes a specified signal from the set.
    int sigismember(const sigset_t *set, int signo); Checks is the specified signal is a member of the set.

    sigismember() - returns 1 if the specified signal is a member of the set, otherwise it returns 0.
    All the other functions listed in the chart above return 0 on success and -1 on error.

    The following code segment shows how a signal set called twosigs would be initialized to contain exactly two signals SIGINT  and SIGQUIT.
     

    #include <signal.h>

    sigset_t twosigs;

    sigemptyset(&twosigs);
    sigaddset(&twosigs, SIGINT);
    sigaddset(&twosigs, SIGQUIT);
     

    A process can examine or modify its process signal mask woth the sigprocmask() function. It has the following syntax:
    #include <signal.h>

    int sigprocmask(int how, const sigset_t *set, sigset_t *oset);
     

    The how parameter is an integer which indicates how the signal mask is to be modified. It can take on one of the following 3 values:

     Back to Index
     
    Parameter value
    What it Does
    SIG_BLOCK
    Add a collection of signals to those already blocked.
    SIG_UNBLOCK
    Delete a collection of signals from those currently blocked.
    SIG_SETMASK
    Set the collection of signals being blocked to the collection given.

    The set parameter points to the set of signals to be used for modification.

    The oset parameter is the address of a sigset_t variable used for holding the set of signals that were blocked before the call to sigprocmask(). Note, the second or third parameters may be null.

    If the second parameter const sigset_t *set is null the third parameter will provide the current signal mask without modification.

    If the 3rd parameter, sigset_t *oset is null then sigprocmask does not return the old value of the process signal mask in oset.

    Return values: sigprocmask()  returns -1 on error and sets errno. On succedd it returns 0.

    Example: The following code example adds SIGINT to the set of signals that the process has blocked.
     

    #include <stdio.h>
    #include <signal.h>

    sigset_t newsigset;

    sigemptyset(&newsigset);   // start with a new empty set of signals
    sigaddset(&newsigset, SIGINT);  //  adds SIGINT to the set
    if (sigprocmask(SIG_BLOCK, &newsigset, NULL) < 0)
       perror("Could not block the signal");

    If SIGINT is already blocked the sigprocmask() example above has no effect.

    The following program displays a message and blocks and unblockes SIGINT:
     

    /* sigs1.c */

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    #include <math.h>
    #include <signal.h>

    void main( int argc, char *argv[] )
    {

      double y;
      sigset_t intmask;
      int i, repeat_factor;

      if (argc != 2) {
        fprintf(stderr, "Useage: %s repeat_factor\n", argv[0]);
        exit(1);
      }

      repeat_factor = atoi(argv[1]);
      sigemptyset(&intmask);  /* Initialize set with no signals */
      sigaddset(&intmask, SIGINT); /* add SIGINT to set */
      for ( ; ; ) {
        sigprocmask(SIG_BLOCK, &intmask, NULL);
        fprintf(stderr, "SIGINT signal blocked...\n");
        for (i = 0; i < repeat_factor; i++) {
          y = sin((double)i);
          sleep(1);
        }
        fprintf(stderr, "Blocked calculation is finished...\n");
        sigprocmask(SIG_UNBLOCK, &intmask, NULL);
        fprintf(stderr, "SIGINT signal unblocked.\n");
        for (i = 0; i < repeat_factor; i++) {
          y = sin((double)i);
          sleep(1);
        }
        fprintf(stderr, "Unblocked calculation is finished.\n");
      }
    }
     
     

      If the user enters Cntl-c while the SIGINT is blocked the program finishes it;'s calculation and doesn't end untill the SIGINT is unblocked. However, if we hit Ctrl-C  while the signal is unblocked then the program will terminate immediately.

     When we use the function sigaction() we can use the sigset_t sa_mask parameter of struct sigaction  to define the signals which are to be blocked during the execution of the signal handler. The following code segment demonstrates this;

     Back to Index

    #include <signal.h>
    #include <stdio.h>

    struct sigaction newact;  /* define struct */
    newact.sa_handler = mysighandler;  /* set the new signal handler */
    sigaddset(&newacct.sa_mask, SIGPIPE); /* add SIGPIPE signal to lset */
    newact.sa_flags = 0;   /* no special flags or options */
    if (sigaction(SIGINT, &newact, NULL) == -1)
       perror("Could not intstall the SIGINT signal handler");
     

    A Few More Functions

    sigpending()

    When a signal is blocked by a process it is not discarded. It is not immediately delivered but it remains pending. A program can determine what signals are pending via the useage of the sigpending() function. it's syntax is:
     
    #include <sigpending.h>

    int sigpending(sigset_t *set);
     

    This will write the set of signals that are currently blocked from delivery into the signal set pointed to by *set. If successfull it returns 0. On error it returns -1 and sets errno.

     Back to Index

    sigsuspend()

    sigsuspend() can suspend the execution of a process untill the delivery of one of a set of signals by calling sigsuspend(). This is actually a more general form of the pause()  function. It's syntax is:
     
    #include <signal.h>

    int sigsuspend(const sigset_t *sigmask);
     

    The sigsuspend() function replaces the process signal mask with the signal set given by sigmask and then it suspends execution.. It will resume after the execution of a signal handling function. if the received signal terminates the program then sigsuspend() will never return. If a received signal doesn't terminate the program sigsuspend returns -1 with errno set to EINVAL.

     Back to Index
     
     

    Signal References

    The following tables list the various signals that are commonly used in develoiping Unix programs and their descriptions.

     Back to Index


     

    access
    fstat
    read
    sysconf
    alarm
    getegid
    rename
    tcdrain
    cfgetispeed
    geteuid
    rmdir
    tcflow
    cfgetospeed
    getgid
    setgid
    tcflush
    cfsetispeed
    getgroups
    setpgid
    tcgetattr
    cfsetospeed
    getpgrp
    setsid
    tcgetpgrp
    chdir
    getpid
    setuid
    tcsendbreak
    chmod
    getppid
    sigaction
    tcsetattr
    chown
    getuid
    sigaddset
    tcsetpgrp
    close
    kill
    sigdelset
    time
    creat
    link
    sigemptyset
    times
    dup2
    lseek
    sigfillset
    umask
    dup
    mkdir
    sigismember
    uname
    execle
    mkfifo
    signal
    ulink
    execve
    open
    sigpending
    utime
    _exit
    pathconf
    sigprocmask
    wait
    fcntl
    pause
    sigsuspend
    waitpid
    fork
    pipe
    sleep
    write
    stat

    Common Signal References

    The following charts list the signals that Unix programs frequently need to get involved with:

     Back to Index
     
     
    Signal Name
    Description
    SIGALRM
    Generated by the timer set by the alarm function.
    SIGHUP
    Sent to the controlling process by a disconnecting terminal. Or by the controlling process on termination of each foreground process.
    SIGINT
    Raised from the terminal by typing CTRL-C, or the configured interrupt charachter.
    SIGKILL 
    Mainly used from the shell to force termination of an errant process. Cannot be caught or ignored.
    SIGPIPE
    Generated if a pipe with no associated reader is written to.
    SIGTERM
    Sent as a request for processes to finish. Used by the system when shutting down to tell system daemons (services) to stop. Default signal from the kill() command.
    SIGUSR
    May be used by processes to communicate with each other, possibly to cause them to report status information.
    SIGUSR2
    Same as for SIGUSR.

    Default action for these signals is abnormal process termination with all the consequences of _exit,( _exit is like exit except that it performs no cleanup before returning to the kernel. ), except that the status is made available to wait and waitpid indicating abnormal termination by the specified signal.

     Back to Index
     
    SIGFPE
    Genrated by a floating point arithmetic exception.
    SIGILL
    An illegal instruction has been executed by the processor. Often caused by a corrupt program of an invalid shared memory module.
    SIGQUIT
    Usually raised from the terminal by the CTRL-\ or the configured quit charachter.
    SIGSEGV
    Segmentation violation, usually caused by reading or writing to an illegal location of memory often due to programming errors like exceeding array bounds or dereferencing an invalid pointer. Overriding a local array variable and corrupting the stack can cause this signal to be raised when a function returns an illegal address.

    By default, these signals also cause abnormal termination. Additional implementation dependent actions, such as creating a core file, may also occurr.

     Back to Index
     
    SIGSTOP
    Stop executing (can't be caught or ignored)
    SIGTSTP
    Terminal stop signal, often raised by CTRL-Z
    SIGTTIN
    Used by the shell to indicate that background processes have stopped because they need to be read from the terminal, or produce output.
    SIGTTOU
    same as for SIGTTIN

    By default, a process is stopped upon receipt of one of the  above signals.
     
     
    SIGCONT
    Continue executing, if stopped.

    Restarts a stopped process and is ignored when received by a process that is not stopped.
     
     
    SIGCHLD
    Raised when a child process stops or exits.

    By default, the SIGCHLD process is ignored.
     

     Back to Index
     
     

    For footnote information see  Footnotes Page
    Bibliography Information see:  Bibliography Info

    What's Your Opinion ?
    Vote on the quality of this page

    Did you find the information presented on this page helpful? Please take a second to evaluate this page. Please mark one of the checkboxes below and press the submit button. Your selection will help us to improve the quality of this site. Thank You...

      Extremly Helpful
      Helpful
      Slightly Helpful
      Not Helpful at All
      A Waste of Space