“Glimpse of the Lepoard” is a literal translation of an idiom from Chinese “管中窥豹”. Which states that through looking at one single spot on a leopard, one is able to understand the whole creature. I wish to explain the usage of these three functions and give you a good understanding of system calls.

The Linux system is written in C and assembly language, and the system calls are encapsulated in the C language library. fork(), wait(), and exec() are all system calls. fork() is used to create a child process; exec() is used to execute the child process; and wait() is used to block the parent process to wait for the child process. This article is used to talk about how to use these system calls, and through this article to overview the operating mechanism of the process. This article tastes better if you read The principle of the Process before time.

fork()

fork() is used to create a child process. This function returns 0 for successful creation, and returns a negative number for failure.

Let’s take a look at the block of code below.

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <stdlib.h>

int main(){
  int apple = 5; //5 apples
  pid_t pid;

  pid = fork(); // create child process
  if(pid < 0){ // if failed to create the child process
    printf("Error: creating child process");
    exit(EXIT_FAILURE); // exit
  }

  if(pid == 0){// if succeed
    // anythign in this scpoe belongs to the child process
    apple -= 4; // eat 4 apples
  }else if(pid >= 0){ // else, if not in the child process
    printf("There are %d apples", apple); // print the number of apples.
  }
}

Guess whether the output is 1 or 5.

When we create a child process, this block of code, including all its current variables, are copied to the child process. In this way, no matter how the the child process is changed, it takes no effect on the parent process. See the diagram below.

p1

The answer is five apples, did you get it?1

Because the parent process doesn’t wait for the child process to be finished. so as soon as it creates the child process, it returns 5. Did the child eat the apple? We don’t know!

Among the variables in the code, the only difference is the pid, i.e. Process ID. The child’s PID is 0, while the parent’s PID is unknown prior time. We can’t say that the child process only takes effect in that if statement. In fact, we can try the following code to prove that both the child process and the parent process have their own apples and do not interfere with each other.

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdlib.h>

int main(){
  int apple = 5; //5 apples
  pid_t pid;

  pid = fork(); //create child process
  if(pid < 0){ //if failed to create
    printf("Error: creating child process");
    exit(EXIT_FAILURE); // exit
  }

  if(pid == 0){// If success
    //anything inside this scope belongs to the child process。
    apple -= 4; //eat 4 apples
  }
  printf("There are %d apples\n", apple); //print the number of apple
}

On execution, there are two outputs

There are 5 apples
There are 1 apples

The former out is the apple of the parent process, which has not been eaten, and the latter is the apple of the child process, and 4 have eaten. You see, it can be executed even outside the if statement. The if statement is only used to help determine which part of the code can be executed in the child process and which code can be executed in the parent process. Next, let’s talk about exec()

exec()

exec() is a name of a series of functions,It’s purpose is to execute a file. The functions include execl(), execlp(), execle(), execv(), execvp(), execvpe(). We’ll talk about the file system in the future, but for now, we only need to understand that exec() is used to run a program.

With so many functions, which one should I use? Don’t panic. exec as the prefix,we can determine which function to use by letters:

  • l means input is a single parameter, while v represents a list of parameters。
  • with e, we define the environment to execute, and without e if we don’t specify the environment.
  • with p, we define the executable file name, without p we specify the path to the specified executable file. (The function use the environment variable$PATH to determine where the executable file is, so if your program is not added to $PATH, you probably want to use p).

For example, if we use execvp() we know that v stands for list, so the program must intake a list of parameters, and we know p means a path to the file. So, to use execvp() we need to pass two variables to this function, one is a string that represents the path to the executable file, another one is an array of parameters that can be pass to the program.

The following code describes the difference between execlp() and execvp(). The program will output two identical results, which are the running results of the child process and the parent process. The difference is that the parent process uses execvp(), while the child process uses execlp()

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(){
  char args1[] = "ls";
  char args2[] = "-la";

  char* command[] = {args1, args2, NULL};
  pid_t pid1;

  pid1 = fork();
  if(pid1 < 0){
    printf("Error: creating child process");
  }

  if(pid1 == 0){
    //execlp accepts n parameters,the first one is the name of the executable file, anything that comes after are parameters。
    //The last parameter must be NULL
    execlp(args1, args1, args2, NULL);
  }else{
    wait(NULL);
    //execvp takes two arguments, another one is the list of parametesrs.
    execvp(args1, command);
  }
}

Both Child and Parent process achive the same thing, but using a different exec() function. Feel free to explore other members in the exec() function series.

wait()

We have used wait() above but did not introduce it in detail. Its usage is also simple, that is, it can be used in combination with fork(). wait(NULL) means to wait for the child process. if wait() is not used , nothing happens to the parent process, but the child process will not be reclaimed when parent process is terminated, which turns the child process a zombie process2. The return value of wait() is the ID of the child process.

For the parents to catch this return value from childrens, we have to define an integer status. This variable is used later to determine the exit status of the child process.

If you remember the first lesson in C/C++ or Java, you must remember the return 0 at the end of every main function. The textbook only told you that “return 0 means that the function has exit properly”, but you did not understand why 0 is returned and to whom it returns. Now that you understand the concept of parent process and child process, you know that this 0 is returned to the parent process and tells the parent process the exit status of the child process.

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(){
  pid_t pid;
  pid = fork();

  if(pid < 0){
    printf("Error: creating child process");
  }

  if(pid == 0){
    return 0; //Try to change this value to see the result.
  }else{
    int status;
    int id = wait(&status);
    printf("%d\n", status);
    printf("Child process return status is %d\n", WEXITSTATUS(status));
    printf("Child process id is %d", id);
  }
}

status is so special that we need to use WEXITSTATUS, a preset function, to view the return value of the function. There are many other functions available, click here for details

There is a big confusion here, you must be careful:

  1. wait() is not the return value of the child process, but the id of the child process.
  2. status is nor the return value of the child process, but the state of the process.

In addition, it should be noted that the wait()function does not only wait until the end of the child process, but returns when the state of the child process change. 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 signal3

In conclusion:

  • To create a child process, we need to use fork(). The return value of this function tells us whether the child process was created successfully.
  • Use exec() to run an executable file in a child process, there are multiple variants of this function, and which one to use depends on the type of variable that you want to pass to the function.
  • wait() Used by the parent process to wait for the status of the child process.

  1. This article was original written in Chinese, so in the graph The leftside is parent process, and the rightside is the child process ↩︎

  2. Zombie_process ↩︎

  3. wait(2) - Linux man page ↩︎