Skip to content

fork · getpid · getppid · _exit

What exactly does the child get?

fork returns twice, and almost everything about the child is a copy — except the things that are shared, and those are where the surprises live.

One call, two returns

fork is the only call in the whole interface that returns twice. It is made once, by one process, and it returns in two — the original and a new one that is a copy of it, sitting at exactly the same point in the same program.

The only thing distinguishing them is the value. The child gets zero. The parent gets the child's pid. There is no other difference at the moment of the call, which is why every fork in every program is immediately followed by a branch on the return value.

The child is a copy of everything: the same variables with the same values, the same open descriptors, the same current directory, the same credentials. What it is not is the same process — it has its own pid, its own parent, and its own copy of the memory, so changing a variable in one does not change it in the other.

There is no fork syscall. Look at the trace: glibc issues clone with a flags word, and fork is one particular set of those flags. It is the same call that makes a thread.

Try this
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
  pid_t pid = fork();
  if (pid == 0) {
    getpid();
    _exit(0);
  }
  int status;
  waitpid(pid, &status, 0);
  return pid;
}

Watch: The pid each process reports, and which line appears under which pid in the trace.

fork-and-the-child

Before you run it

Here is a program that forks, and both halves make a call. Nothing synchronises them.

Predict

Which process's call appears first in the trace?

fork-and-the-child

When the parent goes first

A process whose parent exits before it does is an orphan, and it is not left dangling: the kernel reassigns its parent to pid 1. That is not an edge case — it is the mechanism, and it is the whole reason pid 1 is special.

Init's job, and it is nearly its only job, is to call wait for whatever turns up. A process that finds itself as pid 1 and does not do that — a shell script, say, or an application that was never meant to be init — leaves every orphan it inherits as a zombie forever.

This is the single most common thing wrong with a container's entrypoint, and the trace shows it plainly: children reparented to 1, and nothing ever calling wait4.

Try this
#include <unistd.h>
#include <sys/wait.h>

int main(void) {
  pid_t outer = fork();
  if (outer == 0) {
    pid_t inner = fork();
    if (inner == 0) {
      getppid();
      getppid();
      _exit(0);
    }
    _exit(0);
  }
  int status;
  waitpid(outer, &status, 0);
  getppid();
  return 0;
}

Watch: The grandchild's parent in the process tree after the middle process exits.

Job control, &, and what the shell does with a background process is that gym's subject. This one is about what the kernel does underneath it. shell.liter8.sh

fork-and-the-child

Trace

Run one of the programs on the left.

Everything you do here stays in this browser.Part of liter8.sh