Linux System Programming · advanced · ~12 min

exec — replacing the process image

Run a different program inside the current process.

Overview

The exec* family of functions replaces the current process's program with a different one. The PID stays the same, but the code, stack, and heap are wiped and reloaded from a new executable. exec does not return on success — control passes to main of the new program.

Why it matters

fork alone just gives you two copies of the same program. exec is the second half: after forking, the child calls exec("/bin/ls") (for example) to become the new program. This pair — fork then exec — is how every shell launches every command.

Core concepts

Variants. execl, execv, execle, execve, execlp, execvp — they differ in how arguments and environment are passed and whether $PATH is searched. No return on success. If exec returns, it means the call failed — usually because the executable wasn't found. Inherits fds. The new program inherits any file descriptors the old one had open, except those marked close-on-exec.

Syntax notes

execl("/bin/ls", "ls", "-la", (char *)NULL);
/* if we get here, exec failed */
perror("execl");
_exit(127);

Lesson

The exec* family replaces the current process image with a new program. On success they do not return — the new program starts at its main. On failure they return -1 and set errno.

Pattern: fork, then exec in the child. The parent then waits for the child to finish.

Variants:

  • execvp(file, argv) — searches PATH for file, argv is an array.
  • execve(path, argv, envp) — most explicit; absolute path, explicit env.

Code examples

pid_t pid = fork();
if (pid == 0) {
    char *argv[] = { "ls", "-la", NULL };
    execvp("ls", argv);
    _exit(127);  // only reached if execvp fails
}
int st; waitpid(pid, &st, 0);

Common mistakes

  • Forgetting that exec only returns on failure — write the error path.

Debugging tips

strace -e trace=execve ./prog shows every exec attempt with its arguments. If exec fails with 'No such file or directory', double-check the first argument (the path); if it fails with 'Permission denied', the file isn't executable (chmod +x).

Memory safety

After exec succeeds, your current heap and stack vanish — any in-flight malloc'd buffers were leaked by definition. That's usually fine because the new program doesn't need them, but ASan / valgrind won't complain about that path.

Real-world uses

Every shell command launch, every system() call, Docker's entrypoint replacement, login: getty exec's login which exec's bash.

Practice tasks

  1. After fork, have the child exec /bin/ls -l / while the parent waits. 2. Use execvp to make the program look up $PATH. 3. Pass environment variables explicitly with execve.

Summary

exec swaps the running program. Pairs with fork: fork makes a copy, exec turns that copy into a different program. Without exec, fork would just make duplicates — together they implement the Unix process model.

Practice with these exercises