Lecture 03: Process Concepts and Process Control Block
- What is a Process? (Defining the Core Unit)
- Process States & Transitions (The Lifecycle of Execution)
- The Process Control Block (PCB) (The OS’s Ledger)
- Context Switching & Queues (Enabling Multitasking)
- Real-world Observations & Conclusion
Slide Deck
Key Takeaways
- A process is the fundamental unit of execution in an OS, representing a program with its state and resources.
- The Process Control Block (PCB) is the OS’s vital data structure, holding all metadata required for process management.
- Processes cycle through distinct states (New, Ready, Running, Waiting, Terminated), driven by events and the OS scheduler.
- Context switching is the mechanism that enables multitasking, allowing the CPU to rapidly switch between processes, despite its inherent overhead.
- The OS uses various queues (Job, Ready, Device) to organize and manage processes for efficient scheduling and resource allocation.
Following up on the topics covered in Lecture 03, this addendum addresses specific questions raised during our class discussion regarding process management, process hierarchies, communication, and kernel-level structures.
1. Process Maintenance and the Process Tree
The OS maintains process information using a data structure called the Process Control Block (PCB). In Linux, this is specifically implemented as a C structure called task_struct.
While we often visualize processes running independently, the OS organizes them strictly as a tree. When the system boots, it creates a root process (traditionally init, or systemd in modern systems, with PID 1). Every subsequent process is created via a fork() system call from an existing process. The task_struct contains specific pointers to maintain this hierarchy: it has a pointer to its parent, a list of its children, and pointers to its siblings.
2. The Necessity of the Parent-Child Relationship
You might wonder why we enforce this tree structure instead of just throwing all processes into a flat list. The parent-child relationship is required for several critical reasons:
- Resource Allocation and Quotas: A parent can restrict the resources (memory, CPU time) a child is allowed to consume, preventing runaway processes from crashing the system.
- Job Control: It allows a parent process (like your shell terminal) to suspend, resume, or terminate its children as a group.
- Exit Status: When a process completes, it often needs to pass a success or failure code back to the entity that launched it. The hierarchy guarantees a designated receiver for this message.
3. Orphans, init, and the “Direct Report” Dilemma
Why does an orphan need to be adopted by init? Why can’t it just tell the OS directly that it is done, and let the OS clean it up?
The fundamental design of Unix-like operating systems dictates that a process’s entry in the process table must remain until its exit status is read. The OS cannot simply erase the process because doing so would discard the exit code before anyone checks it. The OS kernel itself is passive; it doesn’t “read” exit codes, it simply stores them in the PCB.
If a parent dies before reading the child’s exit code, the child becomes an orphan. To ensure that the child’s exit code is eventually read (and its PCB memory freed), the OS re-parents the orphan to init. The init process is designed with an infinite loop that constantly calls wait(), acting as a global vacuum cleaner to read exit codes and allow the OS to finally wipe the orphaned PCBs.
4. Eliminating Zombie Processes
A zombie process occurs when a child has terminated, but its parent is still alive and has not yet called wait() to read the exit status. The child is dead, but its PCB still haunts the process table.
Here are the primary ways to eliminate or prevent zombies:
- Direct
wait()/waitpid(): The parent explicitly calls these functions to reap the child. - Signal Handling: When a child terminates, it sends a
SIGCHLDsignal to its parent. The parent can implement a signal handler to catchSIGCHLDand callwaitpid()asynchronously. - Ignoring
SIGCHLD: The parent can explicitly tell the OS it doesn’t care about the exit status by setting theSIGCHLDaction toSIG_IGN. The OS will then reap the child immediately upon termination. - Terminating the Parent: If the parent is stuck and won’t reap the zombie, killing the parent turns the zombie into an orphan. It is then adopted by
initand instantly reaped.
5. Exit Codes, Sleeping Parents, and SIG_IGN
When a child process terminates, it must communicate its final status back to its parent. This is done via Exit Codes (or Error Codes).
The Purpose and Usage of Exit Codes
An exit code is an 8-bit integer (0-255) returned by a process to the OS upon termination. By universal convention, an exit code of 0 means success, while any non-zero value indicates a specific failure or error condition.
- Usage: The OS stores this code inside the zombie’s PCB. When the parent calls
wait(&status), the OS retrieves this code and passes it to the parent (which can extract it using theWEXITSTATUS(status)macro). This is how shell scripts know whether a command succeeded or failed (e.g., checking$?in bash).
Does a SIGCHLD get lost if the parent is sleeping?
If a parent process is executing a blocking system call like sleep() and a child terminates, the SIGCHLD signal does not get lost.
Instead, the OS marks the signal as “pending” for the parent process. A pending signal will immediately interrupt the parent’s sleep, waking it up to execute the signal handler. However, there is a crucial catch: standard POSIX signals do not queue. If three children terminate at the exact same microsecond while the parent is blocked, the OS might only register a single pending SIGCHLD. This is why robust SIGCHLD handlers use waitpid(-1, &status, WNOHANG) in a while loop to reap all available zombies, rather than assuming one signal equals one dead child.
How wait() relieves the system
When the parent finally calls wait(), it actively requests the OS to perform the final cleanup. The OS reads the exit code, hands it to the parent, destroys the PCB, and releases the process ID (PID) back to the system pool. The zombie is officially eliminated.
The SIG_IGN Strategy
If a parent process does not care whether its children succeed or fail, it can tell the OS to automatically discard their exit codes by setting the signal handler to “Ignore”:
signal(SIGCHLD, SIG_IGN);
6. Standard POSIX Signals and Their Usage
Signals are limited forms of inter-process communication (IPC) used in Unix, Unix-like, and other POSIX-compliant operating systems. They are essentially software interrupts sent to a program to indicate that an important event has occurred.
Here are the most common signals you will encounter and use:
SIGINT(Interrupt - 2): Sent when the user typesCtrl+Cin the terminal. It gently asks the process to terminate. A process can catch this signal to perform cleanup (saving files, closing sockets) before exiting.SIGKILL(Kill - 9): The absolute termination signal. It forces the process to stop immediately. Crucially, this signal cannot be caught, blocked, or ignored by the process. The OS simply rips the process out of memory.SIGTERM(Terminate - 15): The default signal sent by thekillcommand. It is a polite request to terminate, allowing the program to run its shutdown hooks gracefully.SIGSEGV(Segmentation Fault - 11): Sent by the OS when a process makes an invalid virtual memory reference (e.g., dereferencing a null pointer). It usually results in an immediate crash and a core dump.SIGCHLD(Child Status Changed - 17): As discussed above, this is automatically sent to a parent process whenever one of its child processes terminates, is interrupted, or resumes.SIGSTOP(Stop - 19): Pauses a process’s execution (often triggered byCtrl+Zin the terminal). LikeSIGKILL, it cannot be caught or ignored. The process remains frozen in memory until aSIGCONT(Continue) signal is sent to wake it back up.
7. Exploring the /proc Filesystem
To actually “see” the OS data structures in action without writing kernel code, Linux provides the /proc filesystem.
/proc is a pseudo-filesystem. It does not exist on your hard drive. Instead, it is generated on-the-fly by the kernel in RAM. It acts as a window into the OS’s process table and internal data structures.
You can read it using standard terminal commands just like normal files:
cat /proc/cpuinfo: View details about the physical CPU hardware.ls /proc: You will see a list of numbered directories. Every running process has a directory named after its Process ID (PID).cat /proc/[PID]/status: Shows human-readable information about that specific process, including its State (Sleeping, Running, Zombie), its Parent PID (PPid), and its memory usage.ls -l /proc/[PID]/fd: Shows all open File Descriptors for that process. This proves that standard input, standard output, and sockets are all just treated as files by the OS!
8. Linux Kernel Source Code References
For those interested in exploring the actual system code, here are direct links to the Torvalds Linux kernel repository on GitHub. These specific files contain the core logic for what we discussed today:
- The PCB (
task_struct): The massive structure that represents a process. - Process Creation (
kernel_clone/fork): This file contains the logic for how a process is duplicated, how the PCB is copied, and how PIDs are assigned. - The Core Scheduler (
__schedule): This is the heart of the OS. This function contains the logic that chooses which process runs next and physically swaps the CPU context. - Signal Delivery (
do_send_sig_info): The internal mechanisms of how the OS packages and delivers signals between processes.
9. Niche and Advanced Learning Resources
If you want to dive deeper into these topics beyond the standard textbook:
- Bootlin Elixir Cross Referencer (elixir.bootlin.com): This is arguably the best tool for browsing the Linux kernel. It allows you to click on any struct or function (like
task_struct) and instantly see where it is defined and every place it is used across the kernel. - OSTEP (Operating Systems: Three Easy Pieces): A fantastic, freely available online book by Arpaci-Dusseau. The chapters on process API and execution are exceptionally clear.
- Linux Kernel Development by Robert Love: A highly readable book that bridges the gap between academic OS theory and actual Linux kernel implementation.
Review Questions
- Write a simple C program where a parent process deliberately creates a zombie process by sleeping before calling
wait(). Use thepscommand in a separate terminal to verify the zombie state (look forZ+or<defunct>). - Modify the program above to use a
SIGCHLDsignal handler to asynchronously reap the child process without blocking the parent’s execution. - Write a small script or C program that reads
/proc/self/statusand prints out its own Parent PID.
📎 Attached Resources
- >> Detailed slides set (application)