Back to the map

Module 03 · Processes and threads · Lesson 6 of 7

The process control block

Every field the kernel keeps per process and thread: identity, state, saved registers, scheduling data, memory and file pointers, credentials and accounting. Linux's task_struct, PID limits, and reading it from /proc and ps.

CoreShould-knowMid 11 min Updated Nov 2026

Why it matters

When ps shows a worker stuck in state D, when a container hits "too many open files", when a pod dies of its PID limit, or when a kill lands on a reused PID, the answer is in the record the kernel keeps per process. Every field in it can be read with ps or under /proc, and knowing which field answers which question is most of process-level debugging.

The idea

The process control block (PCB) is the kernel's record for one process: everything it needs to stop the process, run something else, and later resume it exactly where it was. The Processes lesson drew it as a box; this lesson opens the box. On Linux the structure is task_struct, defined in include/linux/sched.h, several kilobytes per task before counting the structures it points to.

What the record holds

Field groupWhat it holdsWhere you read it
Identity and statePID, parent PID, thread group ID; running, sleeping (S), uninterruptible (D), stopped (T), zombie (Z)ps -o pid,ppid,stat; /proc/PID/status
CPU context and schedulingSaved registers, kernel stack pointer; nice value, virtual runtime, affinityps -o ni,psr; /proc/PID/sched
MemoryPointer to the memory descriptor: page-table root, mapped regions, resident size/proc/PID/maps; VmRSS in status
FilesOpen file table, current and root directory/proc/PID/fd; FDSize in status
Credentials and signalsUser and group IDs, capabilities, pending and blocked signals, handlersUid, CapEff, SigPnd in status
Accounting and containmentCPU time, faults, switch counts, limits, namespaces, cgroup/proc/PID/stat, limits, ns, cgroup
PID 4821 · PPID 1200 · state Ssaved registers, kernel stacksched: nice, vruntime, CPUcreds, signals, accountingmm → memory descriptorfiles → open file tabletask_struct 1200parent (bash)mm_structpage tables, VMAs, RSSfiles_structfd → struct fileparentthreads share mm and files; each has its own task_struct
A task_struct is mostly pointers. The address space and file table live in their own structures so threads can share them; the parent link builds the process tree.

One per thread, not one per process

Linux has no separate thread structure. Every thread is a task_struct; a process is a thread group whose members share the memory descriptor, open file table, signal handlers and credentials. The leader's PID is the thread group ID (Tgid), which ps shows as the PID; ps -L and /proc/PID/task/ list the individual threads. clone creates a thread with flags that say "share the mm, the files and the signal handlers", so its record is mostly pointers to shared structures plus what is truly its own: registers, a 16 KB kernel stack, state and scheduling data.

The process table and PID limits

The kernel reaches every task through a PID lookup structure and a list of all tasks, together the process table. PIDs come from a counter that wraps at /proc/sys/kernel/pid_max: 32,768 on older defaults, 4,194,304 where systemd raises it. kernel.threads-max caps tasks system-wide, ulimit -u per user and cgroup pids.max per container (the pod PID limit in Kubernetes); a thread leak hits one of these as fork: Resource temporarily unavailable with memory to spare. A zombie is a task_struct kept after exit, holding only the PID and exit status until the parent calls wait. Because PIDs wrap, a stored PID can come to name a different process; pidfd_open gives a handle that cannot.

What a context switch saves

The Context switching lesson covers the switch itself; what it saves is a narrow slice of the PCB. On entry to the kernel the user registers are pushed onto the task's kernel stack; the switch stores the kernel stack pointer and callee-saved registers in thread_struct, and floating-point state only if the task used it. The scheduler then compares the two tasks' mm pointers: another thread of the same process shares the descriptor, so page tables and TLB are left alone; a different process means loading a new page-table root.

Reading it

  1. 1

    /proc/PID/status

    State, Tgid, PPid, Uid, VmRSS, Threads, FDSize, SigPnd, CapEff and the voluntary and nonvoluntary switch counts. Start here.
  2. 2

    /proc/PID/maps and smaps

    Every mapped region with permissions and backing file, straight from the memory descriptor; smaps adds resident size per mapping.
  3. 3

    /proc/PID/fd

    One symlink per open descriptor: a path, a socket or pipe inode, or an epoll instance. Counting the entries is the descriptor-leak check.
  4. 4

    ps -o

    ps -o pid,ppid,stat,ni,psr,rss,nlwp,wchan,comm prints PCB fields as columns; wchan names the kernel function a sleeping task waits in, the first clue for a D state.

Trade-offs

A rich record costs memory on every task: a task_struct plus its kernel stack is around 20 KB, so 10,000 threads hold roughly 200 MB of kernel memory before their user stacks. Accounting in the PCB is what makes top, cgroup limits and the out-of-memory killer's choice possible, at the cost of counters updated on every tick and fault. The failure modes are operational: zombies piling up under a parent that never calls wait, PID exhaustion from a thread leak, a kill landing on a recycled PID, and a D state hiding a hung NFS mount.

In an interview

Use the record to explain what a symptom means: "The STAT column comes straight from the task_struct; D means uninterruptible sleep inside a system call, almost always I/O, so I would read wchan and check the storage before blaming the application." Or on thread leaks: "Threads are task_structs sharing one memory descriptor, so a leak shows up as a climbing Threads count in /proc/PID/status before RSS moves."

Check yourself

ps shows a PID in state Z for hours with RSS 0, and such entries multiply daily. What is it, and what does it cost?

The process control block · Backplane