Unix Process API

Contents

Process creation
fork
exec
Example:
Process suspension
Process removal
Process environment
Information sharing
X Windows and Processes

Process creation

The Unix API allows creation of asynchronous processes where each process has a parent. Each process has a process ID (a number) that can be used to tell them apart. This PID is shown in the user-level command ``ps''. Because the parent process can resume execution before a child terminates, the parent can continue to create processes. The children can also create processes
fork
This creates a tree of processes.
tree
or
tree

fork

The function call to do this is
#include <sys/types.h>
pid_t fork(void)
This splits the current process into two almost identical copies. A copy is made of the user and system stacks, of the allocated data space and of the registers. The major difference is that one has the PID of the parent, the other has a new PID. The fork returns a value that is a PID. The PID is zero if you are the child, or the PID of the child if you are the parent.
Example: This creates one child:
#include <stdio.h>
#include <sys/types.h>

int main(void)
{ pid_t pid;

  pid = fork();
  if (pid == -1) {
    fprintf(stderr, 
           "fork failed\n");
    exit(1);
  }
  if (pid == 0) {
    printf("I'm the child\n");
    exit(0);
  }
  if (pid > 0) {
    printf("I'm the parent \
with child %d\n", pid);
    exit(0);
  }
}


What order are these two messages printed in? The child and the parent can call any functions in the program, and execute as independent processes with the same program code. There is (at present) no communication between them.

exec

It is common to want to replace one of these processes so that it uses a different program. For example, suppose the process creates a file that it wants printed. In Unix it cannot print it itself, because it does not have access to the line printer device. The program ``lpr'' must be used. One way is
system("lpr myfile");
This runs synchronously, so until the line printer program terminates the calling program suspends. Printing an empty file takes about 150 milliseconds. A preferable way may be to fork a new process and let the parent continue with the main piece of work, while the child prints the file asynchronously. This is possible, but cruddy:
if (fork() == 0)
  system("lpr myfile");
else { /* parent */ }
It creates a child process that does nothing but wait for ``system'' to finish. Better is to replace the child with the ``lpr'' program. This is the job of the ``exec'' functions
int execlp(char *file,
           char *arg0,
           char *arg1,
           ....
           char *argn,
           (char *) 0);
The ``file'' argument is the name of the program to execute. This uses the PATH environment variable to find the file (like shell commands do). ``arg0'' is often the same as ``file''. This is used as the name of the process, as reported by ``ps''. ``arg1'' to ``argn'' are the arguments to the command. This must be terminated by a NULL pointer to show the end of the list. In normal operation ``exec'' never returns. If if fails, it returns -1.

Example:

Fork and use exec to print a file
if (fork() == 0)
  if (execlp("lpr",
             "lpr",
             "myfile", 
             (char *) 0) == -1)
    fprintf(stderr,
            "exec failed\n");
else { /* parent */ }
or more simply
if (fork() == 0)
{
  execlp("lpr",
         "lpr",
         "myfile", 
         (char *) 0);
  fprintf(stderr,
          "exec failed\n");
}
else { /* parent */ }
Here is a simple example:
#include <;stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

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

  if ((pid = fork()) < 0)
  {
    fprintf(stderr,
	    "fork failed\n");
    exit(1);
  }

  if (pid == 0)
  {
    execlp("echo",
	   "echo",
	   "Hello from",
	   "the child",
	   (char *) NULL);
    fprintf(stderr, 
    	   "execl failed\n");
    exit(2);
  }

  printf("parent carries on\n");
  exit(0);
}


(What happens if the exit(2) is removed and the execlp fails? Why?)

Process suspension

wait

Suppose the file was a temporary file. After printing, it should be deleted. The child cannot delete it, because after the ``exec'' it no longer exists. The parent cannot directly delete it because the ``lpr'' program may still be accessing it. (Actually, an option on lpr allows removal after printing - ignore this.) When ``lpr'' terminates, the parent can remove the file. So the parent has to be able to decide when a child has finished.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status)
This waits for a process to terminate and returns the PID of one that did. It also returns status information.
if ((pid = fork()) == 0)
  if (execlp("lpr",
             "lpr",
             "myfile", 
             (char *) 0) == -1)
    fprintf(stderr,
            "exec failed\n");
else {
  /* do lots of things while
     the printing takes place
   */

  /* now remove the file */
  while (wait(NULL) != pid)
    ;
  unlink("myfile");

  /* and do lots more things */
}

sleep

A process may suspend for a period of time using the sleep command
unsigned int sleep(seconds)
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <sys/types.h>

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

  if ((pid = fork()) == 0)
  { /* child */
    printf("sleeping...\n");
    sleep(5);
    printf("waking ... and exiting\n");
  } else {
    if (pid > 0)
    {
      printf("waiting for child\n");
      wait(NULL);
      printf("child woke up\n");
    }
  }
}


Process removal

exit

When a process executes the ``exit'' command it terminates.

kill

One process can send simple messages to another using the ``kill'' command
#include <sys/types.h>
#include <signal.h>
int kill( pid_t pid, int sig);
The process to which the signal is sent must have the same uid or be a ``super user'' process. The signal is often SIGINT or SIGKILL.
if (pid = (fork() == 0))
  ...
else {
  /* let the child run for
     10 seconds max
   */
  sleep(10);
  kill(pid, SIGINT);
}

signal

A process can catch certain signals by installing a signal handler which is a function invoked when the signal arrives.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/types.h>

void int_handler(int sig)
{
  printf("Ouch - shot in the ...\n");
  exit(2);
}

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

  if ((pid = fork()) < 0)
  {
    fprintf(stderr,
	    "fork failed\n");
    exit(1);
  }

  if (pid == 0)
  {
    signal(SIGINT, int_handler);
    while (1)
      printf("Running amok!!!\n");
  }

  sleep(3);
  kill(pid, SIGINT);
  exit(0);
}


Process environment

A process has an entry in a table of processes. This table contains all sorts of information (see ``internals'' lecture) including user ID, current directory, etc. A set of function calls allows access to much of this information:

Process ID

#include <:sys/types.h>
pid_t getpid(void);

User ID

#include 
uid_t getuid(void);

User name

char *getlogin(void);

Current directory

char *getcwd(char *dir,
             int size);
Example: Who is running this program?
#include <streamio.h>

int main(void)
{ char *login;

  if ((login = getlogin())
          != NULL)
    cout << login << endl;
}


Information sharing

When a new process is created from a parent, some information is new, some is copied and some id shared: Other facets of process information follow this:

X Windows and Processes

Process state does not include any information about X Windows - X is treated just like any other application. Creating an X process is no different to other applications. However, there are these cases:
Home Program Development using Unix Home
Jan Newmarch (http://jan.newmarch.name) <jan@newmarch.name>

Copyright © Jan Newmarch
Last modified: Wed Nov 19 18:44:59 EST 1997