Understanding Linux Process States and Managing Zombies in Containers
This article explains the classic three‑ and five‑state process models, Linux task_struct states, orphan and zombie processes, and how Docker and Kubernetes handle process IDs, providing practical solutions to prevent and clean up zombie processes in container environments.
Three‑state Process Model
At least three states are defined for a process during execution:
Running : the process holds the CPU and its program is executing. On a single‑CPU system only one process runs; on multi‑CPU systems many can run simultaneously.
Ready : the process has all resources except the CPU and will run as soon as the CPU is allocated. Ready processes are usually kept in a ready queue.
Waiting (blocked or sleeping): the process lacks a required condition (e.g., I/O completion) and cannot run even if the CPU is assigned.
Running → Waiting: waiting for resources such as I/O or manual intervention.
Waiting → Ready: resources become available (e.g., I/O finishes).
Running → Ready: time slice expires or a higher‑priority process preempts.
Ready → Running: CPU becomes idle and a ready process is scheduled.
Five‑state Process Model
The five‑state model adds New and Exit to the three‑state model.
New : the process is being created, resources are allocated, and it is placed into the ready queue.
Exit : the process has finished normally or terminated abnormally; it is no longer scheduled and will be removed from the system after cleanup.
NULL → New: a program is executed, creating a child process.
New → Ready: after the OS finishes creation steps and resources are sufficient.
Running → Exit: normal completion, fatal error, or termination by the OS or another privileged process.
Running → Ready: time slice expires or a higher‑priority process appears.
Running → Waiting: waiting for resources or I/O.
Ready → Exit: some OSes allow a parent to terminate a child directly.
Waiting → Exit: similar parent‑initiated termination.
Exit → NULL: cleanup is completed.
Linux Process States
Both processes and threads are represented in the Linux kernel by the task_struct structure, which is the basic scheduling unit.
Linux process states are:
TASK_RUNNING : ready or running (state R).
TASK_INTERRUPTIBLE : light sleep, can be awakened by signals (state S).
TASK_UNINTERRUPTIBLE : deep sleep, does not respond to signals (state D).
TASK_ZOMBIE : exited but parent has not yet reaped it (state Z).
EXIT_DEAD : final exit state at the moment of termination (state X).
TASK_STOPPED : stopped for debugging (state T).
Orphan Processes
An orphan process occurs when its parent exits while the child is still running; the init process (PID 1) adopts the orphan and later reaps it.
Zombie Processes
In Unix‑like systems a zombie is a process that has terminated but still occupies an entry in the process table until its parent calls wait(). Zombies waste PIDs and can exhaust the PID space.
[root@k8s-dev]# cat /proc/sys/kernel/pid_max
32768The kernel sets pid_max based on CPU count:
If CPUs ≤ 32, pid_max = 32768.
If CPUs > 32, pid_max = 1024 × (CPU count).
To clean zombies, send SIGCHLD to the parent, or terminate the parent so that init adopts the zombie. Common preventive measures:
Set the parent’s SIGCHLD handler to SIG_IGN.
Fork twice and kill the first child, making the second child an orphan that init will reap.
Processes in Docker Containers
PID in Containers
Docker relies on Linux PID namespaces; each container gets its own PID namespace, isolating IDs from other namespaces.
Container Exit
When a container is created, a PID namespace is created and the container’s init process (PID 1) runs inside it. When PID 1 exits, Docker destroys the namespace and sends SIGKILL to remaining processes.
On docker stop, Docker first sends SIGTERM to PID 1; if it does not exit within the default 10 seconds, Docker sends SIGKILL for a graceful shutdown.
Zombie Processes in Containers
Causes
Because containers often run a single application as PID 1, the usual init‑style reaping is missing, so exited children become zombies until the parent reaps them.
Solutions
Run a small init process such as tini (available via Docker’s --init flag) that forwards signals and reaps orphaned zombies.
Zombie Processes in a Kubernetes Pod
Kubernetes uses a pause container as the pod’s PID 1, sharing the namespace among all containers.
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
shareProcessNamespace: true
containers:
- name: nginx
image: nginx
- name: shell
image: busybox
securityContext:
capabilities:
add:
- SYS_PTRACE
stdin: true
tty: trueInside the pod, ps ax shows the pause process as PID 1 and other container processes as its children.
/ # kubectl attach POD -c CONTAINER
/ # ps ax
PID USER TIME COMMAND
1 root 0:00 /pause
8 root 0:00 nginx: master process nginx -g daemon off;
14 101 0:00 nginx: worker process
15 root 0:00 sh
21 root 0:00 ps axThe source of the pause image (pause.c) installs a SIGCHLD handler that reaps child processes:
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
static void sigdown(int signo) {
psignal(signo, "Shutting down, got signal");
exit(0);
}
static void sigreap(int signo) {
while (waitpid(-1, NULL, WNOHANG) > 0)
;
}
int main(int argc, char **argv) {
int i;
for (i = 1; i < argc; ++i) {
if (!strcasecmp(argv[i], "-v")) {
printf("pause.c %s
", VERSION_STRING(VERSION));
return 0;
}
}
if (getpid() != 1)
fprintf(stderr, "Warning: pause should be the first process
");
if (sigaction(SIGINT, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
return 1;
if (sigaction(SIGTERM, &(struct sigaction){.sa_handler = sigdown}, NULL) < 0)
return 2;
if (sigaction(SIGCHLD, &(struct sigaction){.sa_handler = sigreap, .sa_flags = SA_NOCLDSTOP}, NULL) < 0)
return 3;
for (;;)
pause();
fprintf(stderr, "Error: infinite loop terminated
");
return 42;
}The SIGCHLD handler calls waitpid(-1, NULL, WNOHANG) to reap any terminated child without blocking.
Conclusion
In a pod, the pause container provides the shared namespace foundation.
It also acts as the init process for all containers, reaping orphaned zombies.
Final Note
By understanding these concepts you can effectively prevent and clean up zombie processes in containerized environments.
Ops Development Stories
Maintained by a like‑minded team, covering both operations and development. Topics span Linux ops, DevOps toolchain, Kubernetes containerization, monitoring, log collection, network security, and Python or Go development. Team members: Qiao Ke, wanger, Dong Ge, Su Xin, Hua Zai, Zheng Ge, Teacher Xia.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
