Skip to content

fork · wait · getpid

What pid 1 owes the machine

Being pid 1 is not a rank, it is a set of obligations — and an entrypoint that was not expecting them is the most common container bug there is.

Two jobs, and nobody else can do either

Every process whose parent exits before it does is reparented to pid 1. That is the kernel's doing and there is no opting out of it — so pid 1 accumulates orphans, and its first job is to wait for them. If it does not, every one becomes a zombie that nothing will ever remove.

Its second job is to forward signals. When the machine is being shut down, SIGTERM goes to pid 1, and everything else is expected to hear about it from there. A pid 1 that does not forward leaves every other process running until the grace period expires and SIGKILL arrives.

Neither job is optional and neither happens by default. A shell, an application server, a Python script — none of them do either, and none of them error when they are asked to be init.

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 after the middle process exits, and whether anything ever reaps it.

what-init-owes

Before you run it

A container entrypoint is a shell script that runs some setup and then starts the server as its last line.

Predict

The orchestrator sends SIGTERM. What does the server receive?

what-init-owes

`exec` replaces rather than starts

exec does not create a process. It replaces the program running in the one that is already there — same pid, same parent, same descriptors except the close-on-exec ones, same credentials unless the file is setuid.

That is why it is the fix. exec ./server means there is no shell left to be pid 1: the server is, and the signal arrives where it was always meant to.

Try this
#include <fcntl.h>
#include <unistd.h>

int main(void) {
  int fd = open("/work/data", O_RDONLY);
  /* Same pid, same descriptor, different program. */
  execve("/bin/reader", NULL, NULL);
  return 99;
}

Watch: That the pid never changes, and that the new program reads through a descriptor it never opened.

what-init-owes

Trace

Run one of the programs on the left.

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