wait · waitpid · _exit
Who reaps, and what is left if nobody does
A process that has exited is not gone. What remains is a row in the process table holding an exit status, and only its parent can remove it.
A zombie is a row in a table
When a process exits, almost everything about it goes immediately: its memory, its open descriptors, its address space. What stays is one record holding how it ended, because somebody might still want to know.
That record is the zombie. It uses no memory and no CPU. It cannot be killed, because it is already dead — kill -9 on a zombie does nothing at all, which surprises people who have just found a screenful of them. The only thing that removes it is its parent calling wait.
A few zombies are normal and transient. A great many of them is a parent that has stopped reaping, and the fix is always in the parent.
#include <unistd.h>
#include <sys/wait.h>
int main(void) {
pid_t pid = fork();
if (pid == 0) {
_exit(3);
}
getppid();
getppid();
int status;
waitpid(pid, &status, 0);
return 0;
}Watch: The child's state in the process tree between its exit and the parent's `wait4`.
`status` is not the exit code
The integer wait gives you back is not the number the child passed to exit. It is a packed word: the low seven bits are the signal that killed it, the next bit says whether it dumped core, and the exit code is in the byte above that.
Which is why the macros exist, and why using the raw value is a bug that works until the day a process is killed rather than exiting. WIFEXITED(status) is really "are the low seven bits zero", and WEXITSTATUS(status) is really "shift right by eight".
It is also why exit(256) looks like a clean exit of zero: only the bottom byte survives.
Predict
A child is killed by SIGKILL. What does WIFEXITED(status) say?