AlgoInsight

Free lesson · Operating Systems

On Linux, /proc/[pid]/status shows PCB-like information

Easy11 min readoperating-systemsosconcurrency

The OS Needs a Notebook for Every Process

In the previous topics, you learned that a process has a PID, a state, memory, open files, and a position in the code it is executing. But where does the operating system actually store all of this information? How does the OS keep track of hundreds of processes simultaneously, knowing exactly where each one left off so it can resume it later?

The answer is a data structure called the Process Control Block, or PCB. The OS creates one PCB for every process on the system. The PCB contains everything the OS needs to know about a process - its identity, its current state, where it is in its execution, what resources it owns, and how to resume it after a context switch.

Think of the PCB as a detailed patient chart in a hospital. When a doctor switches from one patient to another, they do not need to remember everything from memory. They read the chart: what medications the patient is on, what their vitals were last time, what treatment plan they are following. The chart lets any doctor pick up where the previous doctor left off. The PCB does the same thing for the OS. When the scheduler switches the CPU from Process A to Process B, it reads Process B's PCB to know exactly how to resume it.

The PCB is the most important kernel data structure for process management. Every operation the OS performs on a process - scheduling it, switching to it, killing it, checking its permissions - starts by looking up that process's PCB.

What Lives Inside a PCB

A PCB contains several categories of information. Each category serves a different part of the OS - the scheduler needs some fields, the memory manager needs others, and the I/O subsystem needs yet others. Together, they form a complete description of the process's state.

graph TD
    PCB["Process Control Block (PCB)"] --> ID["Process Identification"]
    PCB --> STATE["Process State Info"]
    PCB --> CPU["CPU State (Context)"]
    PCB --> MEM["Memory Management Info"]
    PCB --> IOF["I/O and File Info"]
    PCB --> SCHED["Scheduling Info"]
    PCB --> ACCT["Accounting Info"]
    
    ID --> ID1["PID, Parent PID,<br/>User ID, Group ID"]
    STATE --> STATE1["Current state:<br/>New, Ready, Running,<br/>Waiting, Terminated"]
    CPU --> CPU1["Program Counter,<br/>CPU Registers,<br/>Stack Pointer,<br/>PSW (flags)"]
    MEM --> MEM1["Page table pointer,<br/>Memory limits,<br/>Segment table info"]
    IOF --> IOF1["Open file table,<br/>I/O devices allocated,<br/>Pending I/O requests"]
    SCHED --> SCHED1["Priority, Scheduling queue,<br/>Time quantum remaining,<br/>CPU burst history"]
    ACCT --> ACCT1["CPU time used,<br/>Wall clock time,<br/>Resource usage limits"]
This diagram shows the Process Control Block as a central node branching into seven categories. Process Identification holds the PID, parent PID, user ID, and group ID. Process State Info holds the current state. CPU State holds the program counter, CPU registers, stack pointer, and processor status word. Memory Management Info holds the page table pointer, memory limits, and segment table info. I/O and File Info holds the open file table and I/O device information. Scheduling Info holds the priority, queue position, time quantum, and CPU burst history. Accounting Info holds CPU time used, wall clock time, and resource limits.

Let us go through each category and understand why it matters.

Process Identification

Every process needs to be uniquely identifiable. The PCB stores the Process ID (PID), a unique integer assigned when the process is created. It also stores the Parent Process ID (PPID), which identifies the process that created this one via fork(). On Unix systems, the PCB also stores the User ID (UID) and Group ID (GID) of the user who owns the process. These are used for security - the OS checks the UID and GID when the process tries to access files, network ports, or other resources to determine whether it has permission.

The PID is how every part of the system refers to a process. When you type kill 1234, the OS looks up PID 1234 in its table of PCBs, finds the corresponding process, and sends it a termination signal. When one process wants to communicate with another, it refers to the other process by PID. When the scheduler picks a process to run, it identifies it by PID.

Process State

The PCB stores which of the five states the process is currently in: New, Ready, Running, Waiting, or Terminated. The scheduler uses this field constantly. When looking for the next process to run, it scans for processes in the Ready state. When a disk I/O completes, the OS finds all processes in the Waiting state that were waiting for that I/O and transitions them to Ready.

This field changes frequently - every time the process is scheduled, preempted, blocked, or unblocked, the state field in the PCB is updated.

CPU State (The Process Context)

This is the most critical section of the PCB for understanding context switches. When the OS stops a process from running and switches to another process, it needs to save everything about where the stopped process was in its execution so it can resume exactly later. This saved state is called the process context, and it includes several components.

The program counter stores the address of the next instruction the process should execute. When the process is resumed, the CPU starts fetching instructions from this address. Without the saved program counter, the OS would not know where the process left off.

The CPU registers are the set of small, fast storage locations in the CPU that hold the data the process was actively working with - the values of variables, pointers, arithmetic operands. Modern CPUs have dozens of general-purpose registers, and all of them need to be saved and restored during a context switch.

The stack pointer indicates the top of the process's call stack - where the most recent function call's local variables and return address are stored. Restoring the stack pointer is essential for the process to continue executing the function it was in the middle of.

The processor status word (PSW) contains flags that describe the current state of the CPU - whether the last arithmetic operation produced a carry, whether the result was zero, whether interrupts are enabled, and whether the CPU is in user mode or kernel mode.

Together, these fields form a complete snapshot of the CPU's state at the moment the process was paused. Saving this snapshot is the first step of a context switch. Loading another process's snapshot is the last step.

Memory Management Information

The PCB stores information that the memory manager needs to know about this process. The most important field is the page table pointer - a pointer to the process's page table, which maps virtual memory addresses to physical memory addresses. When the OS switches to a process, it loads this pointer into a special CPU register (called the PTBR or CR3 on x86) so the CPU knows how to translate the process's virtual addresses.

The PCB also stores the process's memory limits - how much memory it is allowed to use - and information about any memory segments the process is using. This information is what allows the OS to enforce memory isolation between processes and to manage virtual memory efficiently.

We will cover page tables and virtual memory in depth in Module 08. For now, the key point is that the PCB holds the pointer that connects a process to its virtual address space.

I/O and File Information

The PCB tracks every I/O resource the process is using. The most important structure here is the open file table - a list of all files the process has open, including the file descriptors, the current read/write position in each file, and the access mode (read, write, or both).

On Unix systems, file descriptors 0, 1, and 2 are pre-assigned to standard input (keyboard), standard output (screen), and standard error (screen), respectively. When you redirect output in a shell (ls > output.txt), the shell modifies the child process's file descriptor table between fork and exec, as we discussed in Topic 2.

The PCB also tracks which I/O devices are allocated to the process and what I/O operations are pending. When a process calls read() and blocks waiting for data, the OS records in the PCB what the process is waiting for so it can wake the process up when the data arrives.

Scheduling Information

The scheduler needs to know several things about each process to make good scheduling decisions. The PCB stores the process's priority (higher priority processes get CPU time first), the scheduling queue the process is in (the ready queue, an I/O queue, etc.), the time quantum remaining (how much of its current time slice is left), and a history of the process's CPU burst lengths (how long the process typically runs before blocking on I/O).

The CPU burst history is particularly interesting. Some scheduling algorithms, like Shortest Remaining Time First, use the process's historical behavior to predict how long it will run before blocking. A process that typically runs for 2 milliseconds before blocking is treated differently from one that typically runs for 200 milliseconds. The PCB stores this history so the scheduler can make informed predictions.

Accounting Information

The PCB tracks resource usage for accounting and enforcement purposes. This includes how much CPU time the process has consumed, how much wall clock time has elapsed since it started, how much memory it is using, and any resource limits imposed on the process (maximum CPU time, maximum memory, maximum number of open files).

On multi-user systems, accounting information is used for billing and fairness. In production environments, resource limits are used to prevent runaway processes from consuming all available resources. If a process exceeds its memory limit, the OS can terminate it. This is how container resource limits (cgroups) work at the OS level - they set limits in the process's accounting information.

The PCB and Context Switches

Now you can see the full picture of how a context switch works. When the OS decides to switch from Process A to Process B, the following happens:

sequenceDiagram
    participant CPU as CPU
    participant OS as OS Kernel
    participant PCBA as PCB of Process A
    participant PCBB as PCB of Process B
    
    Note over CPU: Running Process A
    
    CPU->>OS: Interrupt (timer, I/O, syscall)
    OS->>PCBA: Save program counter
    OS->>PCBA: Save CPU registers
    OS->>PCBA: Save stack pointer
    OS->>PCBA: Update state → Ready or Waiting
    
    Note over OS: Scheduler selects Process B
    
    OS->>PCBB: Load program counter
    OS->>PCBB: Load CPU registers
    OS->>PCBB: Load stack pointer
    OS->>PCBB: Update state → Running
    OS->>CPU: Load Process B's page table pointer
    
    Note over CPU: Now running Process B<br/>(resumes exactly where it left off)
This sequence diagram shows a context switch between Process A and Process B. An interrupt triggers the switch. The OS saves Process A's program counter, CPU registers, and stack pointer into Process A's PCB, and updates its state to Ready or Waiting. The scheduler selects Process B. The OS loads Process B's program counter, registers, and stack pointer from Process B's PCB, updates its state to Running, and loads its page table pointer. The CPU is now running Process B, resuming exactly where it left off.

The context switch is fundamentally a save-and-restore operation on the PCB. Save Process A's CPU state into its PCB. Load Process B's CPU state from its PCB. The CPU does not "know" that a switch happened - it just starts executing from the loaded program counter with the loaded register values, and from its perspective, it was running Process B all along.

Interview: The interviewer asks: "What is a Process Control Block?"

"A PCB is the kernel data structure that stores all information the OS needs to manage a process. It contains the process's identification (PID, parent PID, user ID), its current state, the saved CPU context (program counter, registers, stack pointer) needed to resume the process after a context switch, memory management information (page table pointer), I/O information (open files, pending I/O), and scheduling metadata (priority, time quantum). Every process has exactly one PCB, and the OS uses it for scheduling, context switching, resource management, and cleanup."

If the interviewer follows up with "what is a context switch?", you now have the vocabulary to explain: "A context switch is the OS saving the running process's CPU state into its PCB and loading another process's CPU state from its PCB. It is the mechanism that enables time-sharing - the illusion of multiple processes running simultaneously on a limited number of CPU cores."

The Process Table: Where All PCBs Live

The OS does not store PCBs randomly in memory. It organizes them in a data structure called the process table (also called the task list on Linux). The process table is essentially an array or linked list of PCBs - one entry per process on the system.

When the OS needs to find a specific process's PCB (for example, when you type kill 1234), it searches the process table for the entry with PID 1234. When the scheduler needs to find all Ready processes, it can scan the process table or, more commonly, maintain separate queues (the ready queue, I/O queues) that hold pointers to PCBs.

On Linux, the process table is implemented as a doubly-linked list of task_struct structures. The task_struct is Linux's version of the PCB, and it is a large structure - over 600 fields, covering everything from scheduling information to signal handlers to cgroup memberships to security credentials. You do not need to know all 600 fields for an interview, but knowing that the PCB is implemented as task_struct in Linux demonstrates awareness of how the theory maps to real systems.

import os

pid = os.getpid()
print(f"PID: {pid}")

# On Linux, /proc/[pid]/status shows PCB-like information
# Let's simulate what the PCB contains:
pcb_info = {
    "PID": pid,
    "Parent PID": os.getppid(),
    "User ID": os.getuid(),
    "Group ID": os.getgid(),
    "State": "Running",
    "Working Directory": os.getcwd(),
}

for key, value in pcb_info.items():
    print(f"  {key}: {value}")

Why the PCB Matters Beyond the Definition

Understanding the PCB is not just about knowing the definition - it is about understanding the costs that come from it.

Context switch cost. Every context switch involves saving and loading a PCB. The save-and-restore of registers is fast (nanoseconds), but the consequences are expensive. When you switch to a new process, the CPU caches are "cold" - they contain data from the previous process, not the current one. The TLB (translation lookaside buffer, which caches virtual-to-physical address translations) is also invalidated. The new process starts with cache misses, which means it runs slowly for its first microseconds until the caches warm up. This overhead is why context switches have a real performance cost and why systems that switch too frequently (because they have too many threads) suffer from thrashing.

Memory overhead. Each PCB takes memory. On Linux, a task_struct is several kilobytes. With 1,000 processes, that is several megabytes of kernel memory just for PCBs. This is one reason there is a limit on the maximum number of processes a system can support.

Scheduling latency. The scheduler needs to scan Ready processes to pick the next one to run. If the process table is very large, this scan takes longer, adding latency to every scheduling decision. This is why sophisticated schedulers (like Linux's CFS) use balanced trees rather than linear queues - to keep scheduling decisions fast even with many processes.

Tip: Connect the PCB to performance in your interviews. When discussing system design, you can mention that context switch overhead (which comes from PCB save/restore and cache invalidation) is one reason you would choose a thread pool of 200 over spawning 10,000 threads. This shows the interviewer that you understand the mechanism, not just the conclusion.

Exercise: Starter: On a Linux system, pick any running process and look at its /proc/[PID]/status file. Identify the fields that correspond to PCB categories we discussed: PID, parent PID, state, user ID, memory information. On macOS, use ps -p [PID] -o pid,ppid,stat,uid,%mem,%cpu for similar information.

Intermediate: Think about a context switch between two processes. List, in order, every piece of data the OS needs to save from the outgoing process's PCB and load from the incoming process's PCB. Then consider: what is not in the PCB that still needs to happen during a context switch? (Hint: think about the TLB and CPU caches.)

Advanced: Research the size of Linux's task_struct in the kernel source code. Calculate approximately how much kernel memory is consumed by PCBs on a system with 500 processes. Then consider a container orchestration system running 5,000 containers, each with 10-50 processes. How much kernel memory is devoted just to PCBs? Is this a meaningful fraction of total system memory?

Back to Operating Systems