The fork System Call Revisited

The fork system call is unique in that while it is called once, it returns twiceto the child and to the parent processes. As noted in Chapter 1, "Programs and Processes," if the fork system call is successful, it returns a value of 0 to the child process and the process ID of the child to the parent process. If the fork system call fails, it returns a -1 and sets the global variable errno . The failure of the system to generate a new process can be traced, by examination of the errno value, to either exceeding the limits on the number of processes ( systemwide or for the specific user ) or to the lack of available swap space for the new process. It is interesting to note that in theory the operating system is always supposed to leave room in the process table for at least one superuser process, which could be used to remove (kill) hung or runaway processes. Unfortunately, on many systems it is still relatively easy to write a program (sometimes euphemistically called a fork bomb) that will fill the system with dummy processes, effectively locking out system access by anyone , including the superuser.

After the fork system call, both the parent and child processes are running and continue their execution at the next statement after the fork . The return from the fork system call can be examined, and the process can make a decision as to what code is executed next. The process receiving a 0 from the fork system call knows it is the child, as 0 is not valid as a PID. Conversely, the parent process will receive the PID of the child. An example of a fork system call is shown in Program 3.1.

Program 3.1 Generating a child process.

File : p3.1.cxx /* Generating a child process */ #include + #include #include using namespace std; int main( ){ 10 if (fork( ) == 0) cout << "In the CHILD process" << endl; else cout << "In the PARENT process" << endl; return 0; + }

There is no guarantee as to the output sequence that will be generated by this program. For example, if we issue the command-line sequence

linux$ p3.1 ; echo DONE ; p3.1 ; echo DONE ; p3.1

numerous times, sometimes the statement In the CHILD process will be displayed before the In the PARENT process , and other times it will not. The output sequence is dependent upon the scheduling algorithm used by the kernel. Keep in mind that commands separated by a semicolon on the command line are executed sequentially, with the shell waiting for each command to terminate before executing the next. The effects of process scheduling are further demonstrated by Program 3.2.

Program 3.2 Multiple activities parent/child processes.

File : p3.2.cxx /* Multiple activities PARENT -- CHILD processes */ #include + #include #include #include using namespace std; int 10 main( ) { static char buffer[10]; if (fork( ) == 0) { // In the child process strcpy(buffer, "CHILD..."); } else { // In the parent process + strcpy(buffer, "PARENT.."); } for (int i=0; i < 3; ++i) { // Both processes do this sleep(1); // 3 times each. write(1, buffer, sizeof(buffer)); 20 } return 0; }

Figure 3.1 shows the output of this program when run twice on a local system.

Figure 3.1 Output of Program 3.2.

linux$ p3.2 PARENT..CHILD...CHILD...PARENT..PARENT..CHILD...linux$ linux$ p3.2 PARENT..CHILD...PARENT..CHILD...PARENT.. $ CHILD...

There are several interesting things to note about this program and its output. First, the write (line 19) system call, not the cout object, was used in the program. The cout object (an instance of the ostream class defined in ) is buffered and, if used, would have resulted in the three-message output from each process being displayed all at one time without any interleaving of messages. Second, the system call sleep (sleep a specified number of seconds) was used to prevent the process from running to completion within one time slice (which again would produce a homogenous output sequence). Third, one process will always end before the other. If there is sufficient intervening time before the second process ends, the system will redisplay the prompt, thus producing the last line of output where the output from the child process is appended to the prompt (i.e., linux$ CHILD... ).

Keep in mind the system will flush an output stream (write its data to the physical media) in a variety of circumstances. This synchronization occurs when (a) a file is closed, (b) a buffer is full, (c) in C++ the flush or endl manipulators are placed in the output stream, or (d) a call is made to the sync system call.

EXERCISE

When the following program is compiled and run,

File : funny.cxx /* A very funny program ... */ #include + #include #include using namespace std; int main( ) { 10 fork( ); cout << "hee " << endl; fork( ); cout << "ha " << endl; fork( ); cout << "ho " << endl; return 0; }

assuming all fork system calls are successful, how many lines of output will be produced? Is it ever possible for a ho to be output before a hee ? Why is this? Would the number of hee s, ha s and ho s be different if the << endl was left out of each of the cout statements? Why?

Категории