What is the difference between the functions of the exec family of system calls like exec and exe...

Learn what is the difference between the functions of the exec family of system calls like exec and execve? with practical examples, diagrams, and best practices. Covers c, linux, exec development ...

Understanding the exec Family of System Calls in Linux

A diagram illustrating process replacement with a new program image

Explore the nuances of the exec family of system calls in Linux, including execve, execl, execle, execv, execvp, and execlp, and learn how they replace the current process image.

In Linux and other Unix-like operating systems, the exec family of system calls is fundamental for process management. Unlike fork(), which creates a new process by duplicating the current one, the exec functions replace the current process image with a new one. This means the program being executed by the process changes, but the Process ID (PID) remains the same. Understanding the differences between the various exec functions is crucial for writing robust and efficient C programs that interact with the operating system.

The Core Concept: Process Replacement

At its heart, the exec family performs a single, powerful operation: it loads a new program into the current process's address space and starts its execution from the main() function of the new program. The original program's code, data, and stack are discarded, but certain attributes like the PID, parent PID, open file descriptors (unless explicitly closed), and environment variables (depending on the specific exec call) are preserved. This mechanism is how a shell executes commands or how a program can launch another utility.

flowchart TD
    A[Current Process Running Program A] --> B{Call exec() family function}
    B --> C[Load Program B into Current Process Address Space]
    C --> D[Program A's Code/Data Discarded]
    C --> E[Program B Starts Execution]
    E --> F[PID Remains Same]
    F --> G[New Program B Running in Original Process]

Process replacement using an exec system call

Understanding the Variants: 'l', 'v', 'e', and 'p'

The exec family consists of several functions, each denoted by suffixes that indicate how arguments and environment variables are passed, and how the executable path is resolved. All these functions return -1 on error and do not return on success, as the calling process image is replaced.

Let's break down the suffixes:

  • l (list): Arguments are passed as a list of individual strings, terminated by a NULL pointer.
  • v (vector): Arguments are passed as an array (vector) of strings, with the array itself terminated by a NULL pointer.
  • e (environment): An explicit array of environment variables is passed. If this suffix is absent, the current process's environment is inherited.
  • p (path): The system searches for the executable in the directories specified by the PATH environment variable. If this suffix is absent, the executable path must be absolute or relative to the current working directory.

Detailed Breakdown of Each Function

Here's a closer look at the most commonly used exec functions:

execve(const char *pathname, char *const argv[], char *const envp[])

This is the most fundamental and powerful exec call. All other exec functions are typically implemented as wrappers around execve(). It requires the full path to the executable (pathname), an array of argument strings (argv), and an array of environment strings (envp).

execl(const char *pathname, const char *arg, ... /* (char *) NULL */)

execl takes arguments as a list of strings, terminated by a NULL pointer. The environment is inherited from the calling process. The pathname must be an absolute or relative path.

execle(const char *pathname, const char *arg, ... /* (char *) NULL, char *const envp[] */)

Similar to execl, but it allows you to specify a custom environment (envp) as the last argument after the NULL terminator for the argument list.

execv(const char *pathname, char *const argv[])

execv takes arguments as a null-terminated array of strings (argv). The environment is inherited from the calling process. The pathname must be an absolute or relative path.

execlp(const char *file, const char *arg, ... /* (char *) NULL */)

execlp is like execl, but it includes the p suffix, meaning it searches for the executable file in the directories specified by the PATH environment variable if file does not contain a slash (/).

execvp(const char *file, char *const argv[])

execvp is like execv, but it also includes the p suffix, searching for the executable file in the PATH environment variable if file does not contain a slash (/).

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

int main() {
    printf("This is the original process (PID: %d)\n", getpid());

    // Example 1: Using execlp to run 'ls -l'
    printf("\nExecuting 'ls -l' using execlp...\n");
    // execlp searches PATH for 'ls'
    execlp("ls", "ls", "-l", (char *)NULL);

    // If execlp succeeds, this code will NOT be reached.
    // If it fails, it returns -1.
    perror("execlp failed");
    exit(EXIT_FAILURE);

    return 0; // Unreachable if exec succeeds
}

Example of execlp replacing the current process.

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

int main() {
    printf("This is the original process (PID: %d)\n", getpid());

    // Example 2: Using execve to run '/bin/echo' with custom arguments and environment
    printf("\nExecuting '/bin/echo' using execve...\n");

    char *const argv[] = {"echo", "Hello from execve!", NULL};
    char *const envp[] = {"MY_VAR=CustomEnvValue", "ANOTHER_VAR=123", NULL};

    execve("/bin/echo", argv, envp);

    // If execve succeeds, this code will NOT be reached.
    // If it fails, it returns -1.
    perror("execve failed");
    exit(EXIT_FAILURE);

    return 0; // Unreachable if exec succeeds
}

Example of execve with custom arguments and environment variables.