Skip to content
Part IA Lent Term

fork, exec, and Process Creation in UNIX

Overview

UNIX creates new processes through two system calls:

  • fork(): creates an exact child copy of the calling process. The child inherits the parent’s memory, file descriptors, and execution context.
  • execve(): replaces the calling process’s address space with a new programme loaded from disk. The PID, file descriptors, and environment survive; everything else is replaced.

Together, fork + exec is the standard pattern: the shell forks, and the child execs the desired command.

fork() in detail

pid_t fork(void);

fork() returns twice — once in the parent (returning the child’s PID) and once in the child (returning 0). This is the fundamental UNIX idiom; every process (except PID 1) was created by fork().

The kernel must duplicate:

ResourceHow it is duplicated
Address spaceThe parent’s page table is copied. Pages are initially shared between parent and child with copy-on-write (COW) — the actual copy is deferred until either process writes to a shared page.
Open file descriptorsThe child’s file descriptor table is a copy of the parent’s. Both share the same kernel file object, with the same file offset. A lseek() by the child affects the parent’s next read().
Signal handlersInherited by the child.
Environment and current directoryInherited.
Process group and sessionInherited.

The child gets a new, unique PID. The parent-child relationship is recorded in the process tree.

Copy-on-write efficiency

Naively copying the entire address space (potentially gigabytes) on every fork() would be ruinous. COW avoids this:

  1. During fork(), the kernel marks all pages in both parent and child as read-only.
  2. No physical copy is made — both processes read from the same physical frames.
  3. When either process attempts a write, the MMU raises a page fault (protection fault — writing a read-only page).
  4. The page-fault handler sees that the page is a COW page. It allocates a new physical frame, copies the page’s data, updates the faulting process’s page table to point to the new frame (now read-write), and retries the faulting instruction.
  5. The other process continues to share the original frame (also now marked read-write again if the writer was the only other sharer).

The common fork()-immediately-exec() case is optimised further: exec() will replace the entire address space anyway, so copying is entirely wasted. Modern kernels use vfork() or clone() with flags that avoid even the COW setup.

execve() in detail

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

If successful, execve() does not return — the calling process is replaced. The old address space is discarded; the kernel:

  1. Validates that the file at pathname exists, is executable, and the caller has execute permission.
  2. Optionally, if the file has a SetUID or SetGID bit, changes the process’s effective UID/GID.
  3. Loads the executable’s segments into memory (text at the virtual address specified by the ELF headers, data, BSS).
  4. Sets up a new stack with the argv and envp strings, and auxiliary vectors.
  5. Sets the program counter to the executable’s entry point.

File descriptors survive exec() unless they were opened with the FD_CLOEXEC flag. This allows the shell to set up pipes and redirections before the child execs — the child inherits the file descriptors and uses them transparently.

The fork–exec pattern

pid_t pid = fork();
if (pid == 0) {
    // Child
    close(pipefd[0]);          // close read end
    dup2(pipefd[1], STDOUT_FILENO);
    execlp("ls", "ls", "-l", NULL);
    // execlp never returns on success
    perror("exec failed");
    _exit(1);
} else {
    // Parent
    close(pipefd[1]);          // close write end
    wait(NULL);                 // reap the child
}

Summary

  • fork() duplicates the process; exec() replaces the programme.
  • Copy-on-write makes fork() cheap — no physical copy of memory until a write occurs.
  • File descriptors survive exec(), enabling I/O redirection.
  • The shell’s job is to fork, set up pipes and redirections, and exec the command.