Creating processes with fork and exec in Linux

Fork and exec in Linux

1.0 fork and exec system calls

Suppose we wish to write a "shell program" which would execute another program. Now, in a computing system, a process executes a program. So this shell program (or, process, at run time) needs to create a process which would execute a program. Here, two system calls are of interest, fork and exec.

1.1 fork system call

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

pid_t fork (void);

When a process makes the fork system call, a new process is created which is a clone of the calling process. The code, data and the stack of the new process is copied from the calling process. The newly created process is called the child process, whereas the calling process is termed the parent process. However, there is a difference between the parent and child processes. The return value of fork in the child process is 0, whereas, in the parent process, the process id of the child process is returned. Indeed, the two processes use this difference to figure out whether they are the parent or child.

What is the use of having a process which is a copy of its parent? Not much. But, then, the fork system call is mostly used in conjunction with a variation of exec. In Linux, there is an execve system call and there are six functions with names starting with exec and are front-ends to the execve system call. When we say exec in context of Linux, we mean either the execve system call or one of the six functions described later in this tutorial.

1.2 execve system call

The execve system call (execve(2)) is the starting point of our discussion on exec.

#include <unistd.h>

int execve (const char *filename, char *const argv [],
            char *const envp []);

What happens when a process makes the execve system call? Its code and data segments are initialized by the program contained in the file identified by filename. The most important thing to note is that it is the same process (its pid is the same as before), but is executing a new program. Incidentally, execve is one example where the difference between a program and a process cannot be emphasized enough. A process is an entity created by the kernel to execute a program written to do some tasks. argv is an array of arguments to the program, where the zeroth element in the array is the file name of the program itself. envp is an array of environment variables in the format, name = value. The last element in both argv and envp must be NULL. Both argv and envp can be accessed in the main function of the program, which is called as,

int main (int argc, char *argv [], char *envp [])

However, the third parameter, envp is not specified in POSIX.1, which stipulates that the environment variables should be accessed via the external variable environ, (environ (7)).

execve does not return on success. It can't, for the code segment has been initialized from the new program being executed and the return address (in the previous) program is lost for ever. However, if execve is unsuccessful, -1 is returned, and errno is set accordingly.

1.3 exec family of functions

There is an exec family of six functions (exec(3)), which provide front-ends to the execve system call. These are,

#include <unistd.h>

extern char **environ;

int execl (const char *path, const char *arg0, const char *arg1, ..., (char *) NULL);
int execlp (const char *file, const char *arg0, const char *arg1, ..., (char *) NULL);
int execle (const char *path, const char *arg0, const char *arg1, ..., (char *) NULL, char *const envp []);
int execv (const char *path, char *const argv[]);
int execvp (const char *file, char *const argv[]);
int execvpe (const char *file, char *const argv[], char *const envp[]);

The names of the first five of above functions are of the form execXY. X is either l or v depending upon whether arguments are given in the list format (arg0, arg1, ..., NULL) or arguments are passed in an array (vector). Y is either absent or is either a p or an e. In case Y is p, the PATH environment variable is used to search for the program. If Y is e, then the environment passed in envp array is used. In case of execvpe, X is v and Y is e. The execvpe function is a GNU extension. It is named so as to differentiate it from the execve system call (execve (2)).

1.4 Difference between fork and exec

The major difference is that in case of fork, a new child process is created, which is a clone of the parent process. When a process executes exec, no new process is created. The calling process is overwritten by the program whose filename is passed as the first argument. In most cases, a process calls fork, which creates a new child process. The child process, then, exec's the program to be executed. So, fork and exec are mostly used together. Without fork, exec is of limited use. And, without exec, fork is hardly of any use.

2.0 An example

As an example, let's write two programs, parent and child. We will execute parent from the shell's command line. The parent would fork a child process and the latter would exec the child program in it. The parent would wait for the child to do its work and terminate.

2.1 Parent

// parent.c: the parent program

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

int main (int argc, char **argv)
{
    int i = 0;
    long sum;
    int pid;
    int status, ret;
    char *myargs [] = { NULL };
    char *myenv [] = { NULL };

    printf ("Parent: Hello, World!\n");

    pid = fork ();

    if (pid == 0) {

        // I am the child

        execve ("child", myargs, myenv);
    }

    // I am the parent

    printf ("Parent: Waiting for Child to complete.\n");

    if ((ret = waitpid (pid, &status, 0)) == -1)
         printf ("parent:error\n");

    if (ret == pid)
        printf ("Parent: Child process waited for.\n");
}

2.2 Child

//  child.c: the child program

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define A 500
#define B 600 
#define C 700
    
int main (int argc, char **argv)
{   
    int i, j;
    long sum;

    // Some arbitrary work done by the child

    printf ("Child: Hello, World!\n");

    for (j = 0; j < 30; j++ ) {
        for (i =0; i < 900000; i++) {
            sum = A * i + B * i * i + C;
            sum %= 543;
        }
    }

    printf ("Child: Work completed!\n");
    printf ("Child: Bye now.\n");

    exit (0);
}

2.3 Executing the parent (and child)

$ # compile parent
$ gcc parent.c -o parent
$ #compile child
$ gcc child.c -o child
$ # run parent (and child)
$ ./parent
Parent: Hello, World!
Parent: Waiting for Child to complete.
Child: Hello, World!
Child: Work completed!
Child: Bye now.
Parent: Child process waited for.
$

We took a rather simple example just to illustrate the concepts of parent and child programs and the respective processes. A more realistic example is the shell. The shell is the parent process. It reads each line of input from the command line, forks a child shell process, which in turn exec's the command. The shell parent process waits for the child to complete and then prompts for the next command.