The Linux task model: task_struct (pid/tgid, state, mm, files, signals), process states mapped to ps letters, fork with Copy-On-Write, clone flags (threads = CLONE_VM), kernel threads, the process tree/reparenting, and exit/zombies.
What a "process" actually is inside the kernel, how threads fit, and how the whole tree comes to life from
fork. Original, interview-focused notes on the Linux process model.
In Linux there's really one abstraction: a task (struct task_struct). A "process" and a "thread" are both tasks — the difference is just what they share. Threads of a process share the same address space (mm), files, and signal handlers; separate processes don't.
Every task is a task_struct holding its entire context: identity, state, and pointers to shared
resources.
| Field (concept) | What it holds |
|---|---|
pid / tgid | The thread ID and the thread-group ID — tgid is what userspace calls the "PID". All threads of a process share one tgid. |
state | Run state (below). |
mm | Address space (page tables, VMAs). Shared by threads; NULL for kernel threads. |
files, fs | Open file descriptors, cwd/root. |
signal / sighand | Signal state & handlers. |
parent / children | The process tree. |
| sched fields | Priority, vruntime, scheduling class (see Scheduling). |
Each task also has a small kernel stack with a thread_info; current
always points at the running task's descriptor.
| State | Meaning |
|---|---|
TASK_RUNNING (R) | Running or runnable (on a run queue). |
TASK_INTERRUPTIBLE (S) | Sleeping, wakeable by a signal or event. |
TASK_UNINTERRUPTIBLE (D) | Sleeping in a syscall (usually I/O); signals don't wake it — the "won't die" case. |
__TASK_STOPPED (T) | Stopped (SIGSTOP / ptrace). |
EXIT_ZOMBIE (Z) | Exited, waiting for the parent to wait() and reap the exit status. |
These map exactly to the ps letters — see the zombie vs D-state troubleshooting case.
fork() duplicates the calling process. The magic is Copy-On-Write:
the child gets the same physical pages marked read-only; a page is copied only when one side writes.
So fork is cheap even for a big address space.clone() is the real primitive; fork, vfork, and thread
creation are all clone with different flags selecting what's shared:
CLONE_VM (share address space → a thread), CLONE_FILES, CLONE_SIGHAND,
CLONE_THREAD, etc.CLONE_VM (via pthreads/NPTL). No separate "thread" object —
just tasks sharing an mm. Scheduling is per-task, so threads are scheduled independently.vfork suspends the parent and shares its memory until the child execs — an
optimization for the fork-then-exec pattern (see Program Execution).init/systemd) is the ancestor; every process has a parent. An orphan is
reparented to init (or a subreaper), which reaps it.kworker, ksoftirqd) have no user address space
(mm == NULL) and run only in kernel mode.wait(); only then is the task_struct freed. A parent that never waits leaks
zombies (and eventually PIDs)./proc/<pid>/ (status, fd, maps, stack…) — the
window into the descriptor.fork() cheap? (Copy-On-Write)clone(), and how do its flags produce a thread vs a process?ps letters.tgid vs pid? (userspace PID = tgid; threads share it)mm)