Process abstraction and management

Required reading: Chapter 7 and remaining of chapter 12

Overview

The next set of lectures we will discuss the implementation of kernel services for process management, interprocess communication, I/O, and file systems. Today's topic is process management and the case study will be UNIX's process management services; all operating systems support something similar to what UNIX supports.

A process is one address space combined with one thread. Process management in UNIX include the functions fork, wait, exec, exit, kill, getpid, brk, nice, sleep, and trace. (All system calls in v6 are in the systent table on sheet 29; pick out the ones related to process management.)

To recall how they are used, remember the structure of the shell:

       while (1) {
	    printf ("$");
	    readcommand (command, args);
	    if ((pid = fork ()) == 0) {  // child?
	       exec (command, args, 0);   // arg 0 is name of program
	    } else if (pid > 0) {   // parent?
	       wait (pid);
	    } else {
	       printf ("Unable to fork\n");
            }
        }

As a side note, how does the shell get started? (Init fork+execs login, one per terminal; login execs the shell listed in the passwd file.)

How can a shell implement background jobs (a process group)?

        $ compute &
The shell just doesn't call wait, but instead reads the next command. The shell periodically polls jobs to find out what their status is by calling wait (passing a flag not to block). (Jobs are not supported by v6.)

A process terminates fully when (1) the process exited (perhaps because of kill); and (2) the parent has called wait. A process that has exited but the parent hasn't called wait, enters the zombie state. If a parent process terminates without waiting for all of its child processes to terminate, the remaining child processes are assigned the parent process 1 ID (the init process ID); init waits for processes to terminate, and thus will clean them up.

Why make process 1 the parent? Mostly for convenience. An alternative is the child cleans up itself, but that is a bit tricky since it must remove its own stack etc. It also results in a bit of code duplication, because it would be a different code path than if the parents cleans up. Another possibility is to make the grandparent the parent, but how does the child know who its grandparent is? What if the grandparent dies, then both the child and the parent need a new parent. It probably can all be made to work, but making pid 1 the parent is simpler and it is in a loop calling wait anyway.

Why separate fork and exec? Also, mostly for convience. They already had exec and a process table. Adding just fork was the least amount of new code.

V6 code examples