Make a zombie and a D-state process

short · 25 min · Objective 2.3

Task

Create the two process states that cannot be killed, observe each, and establish by experiment what actually clears them. These appear as exam scenarios and as 3am incidents, and both are much easier to remember once you have failed to kill one yourself.

Steps

  1. Create a zombie. Run a small script that forks a child, lets it exit, and then sleeps without reaping it: bash -c 'sleep 1 & sleep 300' & will not do it -- bash reaps. Use python3 -c "import os,time; os.fork() or os._exit(0); time.sleep(300)" & so the parent never calls wait.
  2. Find it with ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/'. Note the Z.
  3. Try to kill the zombie with kill -9 <pid>. Observe that nothing happens and explain why in one sentence.
  4. Kill the PARENT instead, and confirm the zombie disappears -- init adopts and reaps it.
  5. Observe the memory columns that mislead: run ps -eo pid,rss,vsz,comm --sort=-rss | head and note how much larger VSZ is than RSS for ordinary processes.
  6. Read the state letters for everything running: ps -eo stat | sort | uniq -c. Identify which are sleeping and which are runnable.

Verify

ps -eo pid,ppid,stat,comm | awk '$3 ~ /^Z/ {print}'    # after step 2: one line
kill -9 "$(ps -eo pid,stat | awk '$2 ~ /^Z/ {print $1; exit}')" 2>/dev/null
ps -eo stat | grep -c '^Z'                             # unchanged: still there
# after killing the parent:
ps -eo stat | grep -c '^Z'                             # 0

The middle step is the lesson: the kill succeeds, returns zero, and changes nothing, because the process is already dead. A zombie is a table entry, not a running program.

Notes

D state is harder to create safely -- it needs genuinely blocking I/O, such as an NFS mount to a host you then make unreachable. If you have a spare VM, that experiment is worth doing once: the load average climbs into double figures while the CPUs sit idle, which is the signature you are learning to recognise.