The Linux system is written using a combination of C language and assembly, and system calls are encapsulated within the C standard library. fork(), wait(), and exec() are all system calls. Specifically, fork() is used to create a child process, exec() is used to execute a program within a process, and wait() is used to block the parent process until a child process terminates. In this article, we will discuss how to use these system calls and get a glimpse of how processes work. This post is best read in conjunction with my previous post on Process Concepts.
fork()
fork() is used to create a new child process. The function returns 0 in the child process if creation is successful, and a negative value in the parent process if creation fails.
Let’s look at a code snippet:
1 |
|
Guess whether the output is 1 or 5.
When we create a child process, the entire code, including all current parameters (variables and their values), is copied into the child process. Thus, any changes the child process makes are only effective within the child process itself. See the diagram below:
The answer is “There are 5 apples”. Did you guess correctly?
Among these parameters, the only difference is the pid. In the child process, the pid returned by fork() is 0, while in the parent process, it is the process ID of the newly created child process. It is important to understand that the child process’s scope is not limited to that if statement; it does not cease to be a child process once it moves outside the if block. To prove that both the child and parent processes possess their own copy of the apple variable, and that they are independent, try the following code:
1 |
|
The shell will show two lines of output:
1 | There are 5 apples |
(The order of the two outputs is not guaranteed.)
One output is from the parent process, where the apples were not eaten; the other is from the child process, where four apples were eaten. As you can see, code outside the if statement is executed by both processes. The if statement is only used as a conditional to separate code that should run in the child process from code that should run in the parent process. Next, let’s talk about exec().
exec()
exec() is a general name for a family of functions used to execute a specific file. They include execl(), execlp(), execle(), execv(), execvp(), and execvpe(). I will follow up with a post about the file system later. For now, assume that exec() can be used to run a program.
With so many functions, which one should I use? Don’t panic. With exec as the prefix, we can determine the correct function by looking at the remaining letters:
lstands for a variable length List of arguments, andvstands for an Vector (an array of arguments).- Functions with
emean you need to set the Environment; functions withoutedo not. - Functions with
pmean you input the filename of the executable, and it will search for it in your$PATHenvironment variable. Functions withoutprequire the full Path to the executable file.
The code snippet below demonstrates the difference between execlp() and execvp(). The program outputs two identical results: one from the child process and one from the parent process. What’s different is that the child process uses execlp(), and the parent process uses execvp(). According to the rules above, execlp() accepts a list of arguments and a filename, while execvp() accepts an argument array (vector) and a filename. You can try to change to different functions to see their effects.
1 |
|
The purpose of the NULL argument is to tell the function that this is the end of the argument list.
wait()
We used wait() in the previous section without a detailed introduction. Its usage is also straightforward and is commonly paired with fork(). wait(NULL) means to wait for any child process to terminate. If wait() is not used, the main (parent) process will not be affected and will continue execution. However, when the child process terminates, its resources are not fully reclaimed, and it becomes a zombie process^1. The return value of wait() is the ID of the terminated child process.
If you remember writing your very first Hello World program, you surely remember the return 0; at the end of the main() function. Textbooks will tell you: “return 0 means this function returns the value 0,” but you might not understand why 0 is returned, or to whom. Now that you understand the concepts of parent and child processes, you know that this 0 is returned to the parent process to inform it of the child process’s exit status.
To catch this return value, we need to define an integer status. We will use this variable later to determine the child process’s exit status.
1 |
|
WEXITSTATUS is a predefined macro in <sys/wait.h> that is used to extract the exit code from the process status. There are many other macros available; for details, check here.
Here is a big question that you must not get confused about:
- The return value of
wait()is not the child process’s exit code, but the child process’s ID. statusis not the child process’s exit code itself, but a collection of information about the process’s state.
Additionally, it is worth noting that the wait() function does not just wait for the child process to terminate, but returns when there is a state transition in the child process. According to the documentation:
A state change is considered to be: the child terminated; the child was stopped by a signal; or the child was resumed by a signal^2.
A process is considered to have had a state transition when the child terminates, is suspended by a signal, or is reactivated by a signal.
Let’s summarize:
- To create a child process, we use
fork(), and this function’s return value tells us whether the creation was successful and in which process we are. - To execute a program within a child process, we use
exec(). This function has multiple variations; the specific function to use depends on the parameters you have at hand. wait()is used by the parent process to wait for and inquire about the status of its child process.