Skip to main content

Chapter 5: The Fork System Call

This page is a generated reference surface for selective reading. It exists to keep the learner apps guide-first while still preserving source access.

Learning objectives

  • Explain the main ideas and vocabulary in The Fork System Call.
  • Work through the source examples for The Fork System Call without depending on raw chunk order.
  • Use The Fork System Call as selective reference when learner modules point back to Ostep.

Prerequisites

  • Earlier prerequisite concepts leading into Chapter 5: The Fork System Call.

Module targets

  • module-01-processes-scheduling
  • module-02-memory-management-virtual-memory

AI companion modes

  • Explain simply
  • Socratic tutor
  • Quiz me
  • Challenge my understanding
  • Diagnose my confusion
  • Generate extra practice
  • Revision mode
  • Connect forward / backward

Source-of-truth note

This unit is anchored to Ostep and the source chapter "Chapter 5: The Fork System Call". Use external resources only to clarify, extend, or modernize details without replacing the chapter's conceptual spine.

External enrichment

No chapter-specific enrichment resources are curated yet. Add them in the unit manifest when a source clearly improves learning.

Source provenance

  • Primary source: Ostep
  • Source chapter 05: Chapter 5: The Fork System Call
  • Raw source file: 024-5-1-the-fork-system-call.md
  • Raw source file: 025-5-3-finally-the-exec-system-call.md
  • Raw source file: 026-5-4-why-motivating-the-api.md
  • Raw source file: 027-5-5-other-parts-of-the-api.md

Merged source

The Fork System Call

5.1 The fork() System Call

5 Interlude: Process API

ASIDE: INTERLUDES

Interludes will cover more practical aspects of systems, including a particular focus on operating system APIs and how to use them. If you don't like practical things, you could skip these interludes. But you should like practical things, because, well, they are generally useful in real life; companies, for example, don't usually hire you for your non-practical skills.

In this interlude, we discuss process creation in UNIX systems. UNIX presents one of the most intriguing ways to create a new process with a pair of system calls: fork()andexec(). A third routine, wait(), can be used by a process wishing to wait for a process it has created to complete. We now present these interfaces in more detail, with a few simple examples to motivate us. And thus, our problem:

CRUX: HOWTOCREATEANDCONTROLPROCESSES

What interfaces should the OS present for process creation and control? How should these interfaces be designed to enable ease of use as well as utility?

The fork() system call is used to create a new process [C63]. However, be forewarned: it is certainly the strangest routine you will ever call1. More specifically, you have a running program whose code looks like what you see in Figure 5.1; examine the code, or better yet, type it in and run it yourself!

1Well, OK, we admit that we don't know that for sure; who knows what routines you call when no one is looking? Butfork()is pretty odd, no matter how unusual your routinecalling patterns are.

1 4 5 int

main(int argc, char *argv[])
{
printf("hello world (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
} else { // parent goes down this path (main)
printf("hello, I am parent of %d (pid:%d)\n",
rc, (int) getpid());
}
return 0;
}

Figure 5.1:Callingfork()(p1.c)

When you run this program (called p1.c), you'll see the following:

prompt> ./p1
hello world (pid:29146) hello, I am parent of
(pid:29146) hello, I am child (pid:29147)
prompt>
Let us understand what happened in more detail in p1.c. When it first started running, the process prints out a hello world message; included in that message is itsprocess identifier, also known as aPID. The process has a PID of 29146; in UNIX systems, the PID is used to name the process if one wants to do something with the process, such as (for

example) stop it from running. So far, so good.

Now the interesting part begins. The process calls the fork() system call, which the OS provides as a way to create a new process. The odd part: the process that is created is an (almost) exact copy of the calling process. That means that to the OS, it now looks like there are two copies of the program p1 running, and both are about to return from the fork() system call. The newly-created process (called the child, in contrast to the creating parent) doesn't start running atmain(), like you might expect (note, the "hello, world" message only got printed out once); rather, it just comes into life as if it had calledfork()itself.

You might have noticed: the child isn't anexactcopy. Specifically, although it now has its own copy of the address space (i.e., its own private memory), its own registers, its own PC, and so forth, the value it returns to the caller offork()is different. Specifically, while the parent receives the PID of the newly-created child, the child is simply returned a 0. This differentiation is useful, because it is simple then to write the code that handles the two different cases (as above).

#include <sys/wait.h>

5 6 int

main(int argc, char *argv[])
{
printf("hello world (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
} else { // parent goes down this path (main)
int wc = wait(NULL);
printf("hello, I am parent of %d (wc:%d) (pid:%d)\n",
rc, wc, (int) getpid());
}
return 0;
}

Figure 5.2:Callingfork()Andwait()(p2.c)

You might also have noticed: the output (ofp1.c) is notdeterministic.

When the child process is created, there are now two active processes in the system that we care about: the parent and the child. Assuming we are running on a system with a single CPU (for simplicity), then either the child or the parent might run at that point. In our example (above), the parent did and thus printed out its message first. In other cases, the opposite might happen, as we show in this output trace:

prompt> ./p1
hello world (pid:29146) hello, I am child (pid:29147) hello, I am parent of
(pid:29146)
prompt>

The CPUscheduler, a topic we'll discuss in great detail soon, determines which process runs at a given moment in time; because the scheduler is complex, we cannot usually make strong assumptions about what it will choose to do, and hence which process will run first. Thisnondeterminism, as it turns out, leads to some interesting problems, particularly in multi-threaded programs; hence, we'll see a lot more nondeterminism when we studyconcurrencyin the second part of the book.

5.2 The wait() System Call

So far, we haven't done much: just created a child that prints out a message and exits. Sometimes, as it turns out, it is quite useful for a parent to wait for a child process to finish what it has been doing. This task is accomplished with thewait()system call (or its more complete siblingwaitpid()); see Figure 5.2 for details.

In this example (p2.c), the parent process callswait()to delay its execution until the child finishes executing. When the child is done, wait()returns to the parent.

Adding await()call to the code above makes the output deterministic. Can you see why? Go ahead, think about it.

(waiting for you to think .... and done)

Now that you have thought a bit, here is the output:

prompt> ./p2
hello world (pid:29266) hello, I am child (pid:29267) hello, I am parent of
(wc:29267) (pid:29266)
prompt>

With this code, we now know that the child will always print first.

Why do we know that? Well, it might simply run first, as before, and thus print before the parent. However, if the parent does happen to run first, it will immediately callwait(); this system call won't return until the child has run and exited2. Thus, even when the parent runs first, it politely waits for the child to finish running, thenwait()returns, and then the parent prints its message.


Finally The Exec System Call

5.3 Finally, The exec() System Call

A final and important piece of the process creation API is theexec() system call3. This system call is useful when you want to run a program that is different from the calling program. For example, callingfork() in p2.cis only useful if you want to keep running copies of the same program. However, often you want to run adifferent program;exec() does just that (Figure 5.3, page 5).

In this example, the child process callsexecvp()in order to run the programwc, which is the word counting program. In fact, it runswcon the source filep3.c, thus telling us how many lines, words, and bytes are found in the file:

prompt> ./p3
hello world (pid:29383) hello, I am child (pid:29384) 29
p3.c hello, I am parent of
(wc:29384) (pid:29383)
prompt>

2There are a few cases wherewait()returns before the child exits; read the man page for more details, as always. And beware of any absolute and unqualified statements this book makes, such as "the child will always print first" or "UNIXis the best thing in the world, even better than ice cream." 3Actually, there are six variants ofexec(): execl(),execle(),execlp(),execv(), andexecvp(). Read the man pages to learn more.

#include <string.h>
#include <sys/wait.h>

6 7 int

main(int argc, char *argv[])
{
printf("hello world (pid:%d)\n", (int) getpid());
int rc = fork();
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child (new process)
printf("hello, I am child (pid:%d)\n", (int) getpid());
char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p3.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // runs word count
printf("this shouldn't print out");
} else { // parent goes down this path (main)
int wc = wait(NULL);
printf("hello, I am parent of %d (wc:%d) (pid:%d)\n",
rc, wc, (int) getpid());
}
return 0;
}

Figure 5.3:Callingfork(), wait(), Andexec()(p3.c)

The fork() system call is strange; its partner in crime,exec(), is not so normal either. What it does: given the name of an executable (e.g.,wc), and some arguments (e.g.,p3.c), itloadscode (and static data) from that executable and overwrites its current code segment (and current static data) with it; the heap and stack and other parts of the memory space of the program are re-initialized. Then the OS simply runs that program, passing in any arguments as theargvof that process. Thus, it doesnot create a new process; rather, it transforms the currently running program

(formerlyp3) into a different running program (wc). After theexec()

in the child, it is almost as ifp3.cnever ran; a successful call toexec() never returns.


Why Motivating The Api

5.4 Why? Motivating The API

Of course, one big question you might have: why would we build such an odd interface to what should be the simple act of creating a new process? Well, as it turns out, the separation offork()andexec()is essential in building a UNIXshell, because it lets the shell run codeafter the call to fork()butbefore the call to exec(); this code can alter the environment of the about-to-be-run program, and thus enables a variety of interesting features to be readily built.

TIP: GETTINGITRIGHT(LAMPSON'SLAW)

As Lampson states in his well-regarded "Hints for Computer Systems

Design" [L83], "Get it right. Neither abstraction nor simplicity is a substitute for getting it right." Sometimes, you just have to do the right thing, and when you do, it is way better than the alternatives. There are lots of ways to design APIs for process creation; however, the combination offork()andexec()are simple and immensely powerful. Here, the

UNIXdesigners simply got it right. And because Lampson so often "got it right", we name the law in his honor.

The shell is just a user program4. It shows you apromptand then waits for you to type something into it. You then type a command (i.e., the name of an executable program, plus any arguments) into it; in most cases, the shell then figures out where in the file system the executable resides, callsfork()to create a new child process to run the command, calls some variant ofexec()to run the command, and then waits for the command to complete by callingwait(). When the child completes, the shell returns fromwait()and prints out a prompt again, ready for your next command.

The separation offork()andexec()allows the shell to do a whole bunch of useful things rather easily. For example:

prompt> wc p3.c > newfile.txt

In the example above, the output of the programwcisredirectedinto the output filenewfile.txt(the greater-than sign is how said redirection is indicated). The way the shell accomplishes this task is quite simple: when the child is created, before calling exec(), the shell closes standard outputand opens the filenewfile.txt. By doing so, any output from the soon-to-be-running programwcare sent to the file instead of the screen.

Figure 5.4 shows a program that does exactly this. The reason this redirection works is due to an assumption about how the operating system manages file descriptors. Specifically, UNIX systems start looking for free file descriptors at zero. In this case, STDOUTFILENO will be the first available one and thus get assigned whenopen()is called. Subsequent writes by the child process to the standard output file descriptor, for example by routines such asprintf(), will then be routed transparently to the newly-opened file instead of the screen.

Here is the output of running thep4.cprogram:

prompt> ./p4
prompt> cat p4.output

32 109 846 p4.c

prompt>

4And there are lots of shells;tcsh, bash, andzshto name a few. You should pick one, read its man pages, and learn more about it; all UNIXexperts do.

#include <string.h>
#include <fcntl.h>
#include <sys/wait.h>

7 8 int

main(int argc, char *argv[])
{
int rc = fork();
if (rc < 0) { // fork failed; exit
fprintf(stderr, "fork failed\n");
exit(1);
} else if (rc == 0) { // child: redirect standard output to a file
close(STDOUT_FILENO);
open("./p4.output", O_CREAT|O_WRONLY|O_TRUNC, S_IRWXU);

18 19 // now exec "wc"...

char *myargs[3];
myargs[0] = strdup("wc"); // program: "wc" (word count)
myargs[1] = strdup("p4.c"); // argument: file to count
myargs[2] = NULL; // marks end of array
execvp(myargs[0], myargs); // runs word count 25 } else { // parent goes down this path (main)
int wc = wait(NULL);
}
return 0;
}

Figure 5.4:All Of The Above With Redirection (p4.c)

You'll notice (at least) two interesting tidbits about this output. First, whenp4is run, it looks as if nothing has happened; the shell just prints the command prompt and is immediately ready for your next command.

However, that is not the case; the programp4did indeed callfork()to create a new child, and then run thewcprogram via a call toexecvp().

You don't see any output printed to the screen because it has been redirected to the filep4.output. Second, you can see that when wecatthe output file, all the expected output from runningwcis found. Cool, right?

UNIXpipes are implemented in a similar way, but with thepipe() system call. In this case, the output of one process is connected to an inkernelpipe(i.e., queue), and the input of another process is connected to that same pipe; thus, the output of one process seamlessly is used as input to the next, and long and useful chains of commands can be strung together. As a simple example, consider looking for a word in a file, and then counting how many times said word occurs; with pipes and the utilitiesgrepandwc, it is easy -- just typegrep -o foo file | wc -l into the command prompt and marvel at the result.

Finally, while we just have sketched out the process API at a high level, there is a lot more detail about these calls out there to be learned and digested; we'll learn more, for example, about file descriptors when we talk about file systems in the third part of the book. For now, suffice it to say that thefork()/exec()combination is a powerful way to create and manipulate processes.


Other Parts Of The Api

5.5 Other Parts Of The API

ASIDE: RTFM -- READTHEMANPAGES

Many times in this book, when referring to a particular system call or library call, we'll tell you to read the manual pages, or man pagesfor short. Man pages are the original form of documentation that exist on

UNIX systems; realize that they were created before the thing calledthe webexisted.

Spending some time reading man pages is a key step in the growth of a systems programmer; there are tons of useful tidbits hidden in those pages. Some particularly useful pages to read are the man pages for whichever shell you are using (e.g.,tcsh, orbash), and certainly for any system calls your program makes (in order to see what return values and error conditions exist).

Finally, reading the man pages can save you some embarrassment. When you ask colleagues about some intricacy of fork(), they may simply reply: "RTFM." This is your colleagues' way of gently urging you to Read

The Man pages. The F in RTFM just adds a little color to the phrase...

Beyondfork(), exec(), andwait(), there are a lot of other interfaces for interacting with processes in UNIX systems. For example, the kill()system call is used to sendsignalsto a process, including directives to go to sleep, die, and other useful imperatives. In fact, the entire signals subsystem provides a rich infrastructure to deliver external events to processes, including ways to receive and process those signals.

There are many command-line tools that are useful as well. For example, using thepscommand allows you to see which processes are running; read theman pagesfor some useful flags to pass tops. The tool topis also quite helpful, as it displays the processes of the system and how much CPU and other resources they are eating up. Humorously, many times when you run it,topclaims it is the top resource hog; perhaps it is a bit of an egomaniac. Finally, there are many different kinds of

CPU meters you can use to get a quick glance understanding of the load on your system; for example, we always keepMenuMeters(from Raging

Menace software) running on our Macintosh toolbars, so we can see how much CPU is being utilized at any moment in time. In general, the more information about what is going on, the better.

5.6 Summary

We have introduced some of the APIs dealing with UNIXprocess creation: fork(), exec(), andwait(). However, we have just skimmed the surface. For more detail, read Stevens and Rago [SR05], of course, particularly the chapters on Process Control, Process Relationships, and

Signals. There is much to extract from the wisdom therein.

[C63] "A Multiprocessor System Design"

Melvin E. Conway

AFIPS '63 Fall Joint Computer Conference

New York, USA 1963

An early paper on how to design multiprocessing systems; may be the first place the termfork()was used in the discussion of spawning new processes.

[DV66] "Programming Semantics for Multiprogrammed Computations"

Jack B. Dennis and Earl C. Van Horn

Communications of the ACM, Volume 9, Number 3, March 1966

A classic paper that outlines the basics of multiprogrammed computer systems. Undoubtedly had great influence on Project MAC, Multics, and eventuallyUNIX.

[L83] "Hints for Computer Systems Design"

Butler Lampson

ACM Operating Systems Review, 15:5, October 1983

Lampson's famous hints on how to design computer systems. You should read it at some point in your life, and probably at many points in your life.

[SR05] "Advanced Programming in the UNIXEnvironment"

W. Richard Stevens and Stephen A. Rago

Addison-Wesley, 2005

All nuances and subtleties of usingUNIXAPIs are found herein. Buy this book! Read it! And most importantly,live it.