The UNIX operating environment presents a special challenge when it comes to starting a new process. The only way for a new process to be created is for an existing process to ask the system to make a copy, a complete "clone" of the process environment (see Figure 9-1). A parent process is said to fork a "child" process: this is the computer equivalent of cellular mitosis in living organisms. Figure 9-1. Parent Creates a Child
Once the fork is completed, there are two essentially identical process environments under kernel control. They have the same process logical memory view, identical file descriptors (to facilitate features such as pipes and I/O redirection), and they map the same shared memory objects. The only notable differences are that the new process has a unique process ID (p_pid in its proc structure) and its own virtual memory view, and that all of its scheduling-related parameters, performance counters, and pending signal counts have been initialized. Both the parent and child share the same execution trace and return from the same system call, but they have different return values passed to them. In this manner, each process can determine its role. A parent may create any number of identical copies of itself through fork calls, which may be desirable in the case of a system daemon wishing to create a number of clones to handle individual client requests. Since there is an endless variety of programs we may wish to run, a process needs a way to morph itself into a different process changing its stripes, so to speak. To this end, another call is key in process creation: once a child process has been created and has determined that its function is to usher in a new process image, it places the exec() system call. When the exec() call is made, the kernel is asked to change out one process logical view for another while maintaining its kernel-resident run environment and basic identity. To put it another way, the kernel structures that make a process unique, are required for its management, and contain its environmental credentials are preserved, but all those directly associated with the current process's text and data are removed. The kernel then rebuilds the logical view in accordance with the new process's requirements (as defined in its executable program file). In effect, the kernel waves a magic wand and changes one process into another! As the work of a process is finished, one of its threads must make one final system call to exit(). The kernel then starts the termination process. It is at this time that an interesting communication takes place between the kernel and the parent process of the process that is being deconstructed. When a process calls exit(), the kernel notifies the parent of the process of the impending death of its child. Before the kernel may complete the deconstruction and cleanup of the child's system resources, the parent process must acknowledge receipt of the kernel's signal and respond appropriately. Failure to do so results in the child process remaining in limbo and becoming what is commonly called a zombie process. Most versions of UNIX label a process in this state as defunct (political correctness rears its ugly head again we wouldn't want to offend the undead!). Before we explore each of these system calls in detail, let's consider a simple parent child scenario. A Simple Scenario Consider the simple case of a user running a POSIX shell and issuing the command: $ more /etc/hosts In the beginning, we have a process environment for the initial POSIX shell program (pid = 100). The resulting child (pid = 101) will become a run environment for the more program. Let's break it down into specific actions: The shell must parse the line and isolate the first argument. The argument must be checked against the shell's list of intrinsic shell keywords, aliases, and function names. Assuming no match, the shell must search for an executable file that matches the command name; the contents of the shell's PATH variable tells it where and in what order to conduct the search. Once the executable has been located, its access and permissions are checked. If suitable, then the parent shell creates a clone of itself using either a fork() or vfork() system call. In the case of a typical interactive shell, the parent process requests that it be put to sleep and awoken when the child process requests an exit(). This is commonly called running the command in the foreground and is accomplished by the parent process making a wait() system call with the appropriate arguments immediately upon the return from the fork call. The child process calls exec() immediately upon return from the fork call. It requests that its process environment be replaced by that of the more program and that execution continue with the first executable line of the more program code. Once the child completes its task, it calls exit(), resulting in the parent being notified of the death of the child, and since the parent is currently waiting on just such a signal, it will wake and proceed. The kernel is now free to complete the deconstruction of the child and free its resources. In most cases, the shell then issues another prompt to the user, and the process starts all over again. As a side note, consider that a parent process may choose not to wait for the death of its child (called running the child in background mode), but it still must respond to the receipt of the death-of-child signal with one of several variations of the wait() system call. The kernel cannot complete the deconstruction of a child process until the parent's wait handshake is received. When a child calls exit(), it is placed in a zombie state until the parent handshake is received (or until the system is rebooted). Another point to consider is that if a process is multithreaded, the child resulting from a fork will have only one initial thread regardless of how many active threads the parent had at the time the call was made. As a related point, if any active thread of a multithreaded process calls exit(), then the process and all sibling threads are halted and deconstructed. The process and the thread that made the exit() call remain in the zombie state until the parent handshake is received. Which Came First? So now, the age old question: which came first, the chicken or the egg? Since all processes start out as the cloned child of a parent, how does the first process get its start? In the world of HP-UX (and UNIX in general), that auspicious honor belongs to the first user-level process named init and assigned process identifier p_pid=1. The init process is handcrafted by the kernel during system initialization and linked to a process and thread table structure (more on this later). The init process takes its marching orders from the /etc/inittab file, starting many processes, including the infamous /sbin/rc program, which in turn initiates most of the system daemons and services. Next time you are asked the chicken and egg question, simply smile a knowing smile and answer "init!" (If there was ever any doubt, your UNIX-savvy friends will now know for sure you are a fellow geek!) Now that we have taken a first look at the process life cycle, we examine each of the related system calls in greater detail. |