3. Processes, Threads, and Execution ModelsIntroduction and ContextAn operating system is not merely a collection of services for managing files, memory, and devices. Its central responsibility is to create the illusion that many activities can proceed independently and concurrently on a finite computer system. This illusion is constructed through processes, threads, scheduling, protection mechanisms, and communication facilities. A program stored on secondary storage is passive. A process is an active instance of that program, accompanied by a current execution state, address space, resources, and operating-system metadata. Threads provide the units within a process that actually execute instructions. The distinction is fundamental: processes establish resource ownership and protection boundaries, whereas threads enable concurrent execution within those boundaries. This chapter examines how operating systems represent processes, move them through execution states, save and restore their contexts, and allow them to communicate. It also compares user-level and kernel-level threads, explains execution on multicore systems, introduces asynchronous programming models, and considers process isolation as a foundation of reliability and security. Learning ObjectivesAfter studying this chapter, the reader should be able to: 1. Explain the process abstraction and its principal execution states. 2. Describe the contents and purpose of a Process Control Block. 3. Analyze context switching and its performance implications. 4. Distinguish processes, threads, user-level threads, and kernel-level threads. 5. Compare shared-memory and message-passing approaches to interprocess communication. 6. Explain signals, synchronization concerns, and common communication hazards. 7. Relate concurrency and parallelism to multicore execution. 8. Describe asynchronous programming and event-driven execution models. 9. Evaluate process isolation mechanisms and their security implications. 10. Apply these concepts to the design and diagnosis of operating-system-based systems. The Process as an Execution AbstractionA process comprises more than executable instructions. Its typical components include a text or code segment, initialized and uninitialized data, a heap for dynamic allocation, one or more stacks, processor-register values, open files, credentials, and scheduling information. The operating system uses this abstraction to allocate resources, enforce permissions, suspend and resume execution, and account for resource consumption. A process may be viewed through two complementary perspectives. From the programmer’s perspective, it is a running application with an address space and a collection of execution flows. From the kernel’s perspective, it is a managed object whose state must be recorded and protected. The same process can contain multiple threads, but all of those threads normally share the process’s code, data, heap, open files, and virtual-memory mappings. Process States and TransitionsThe familiar process-state model contains the following states: - New: the process is being created and its initial resources are being established.
- Ready: the process can execute but is waiting for assignment to a processor.
- Running: instructions are currently executing on a processor.
- Blocked or waiting: execution cannot proceed until an event occurs, such as completion of I/O or arrival of a message.
- Terminated: execution has finished or the process has been explicitly aborted.
A process usually moves from new to ready, and from ready to running when selected by a scheduler. A running process may return to ready when preempted, enter the blocked state when it requests a resource that is not immediately available, or terminate. A blocked process returns to ready when its awaited event occurs. Real systems refine this model. Linux, for example, distinguishes several forms of sleeping and stopped execution, while systems may maintain suspended-ready and suspended-blocked states when memory pressure or administrative action causes a process to be swapped or otherwise suspended. The state model is therefore a reasoning tool rather than a claim that every kernel uses exactly the same labels. The ready and waiting states are especially important for performance analysis. A large ready queue can indicate processor contention, whereas many blocked processes may indicate I/O dependence, lock contention, or insufficient external services. A process that appears idle may therefore be healthy rather than defective. The Process Control Block and Process IdentityThe operating system maintains a Process Control Block (PCB), sometimes called a process descriptor, for each process. The PCB is not a fixed universal data structure; its organization varies by operating system. Nevertheless, it usually records several categories of information: 1. Identification: process identifier, parent-process relationship, user and group credentials. 2. Processor context: program counter, general-purpose registers, stack pointer, processor-status information, and architecture-specific state. 3. Scheduling data: priority, policy, queue links, processor affinity, and accounting information. 4. Memory-management data: page-table references, segment information where applicable, protection attributes, and address-space metadata. 5. Resource information: open-file references, current directory, signals, timers, accounting counters, and communication endpoints. 6. Lifecycle information: state, exit status, children, pending events, and termination-related data. The PCB makes suspension and resumption possible. When the scheduler stops one execution context, the kernel preserves the information required to continue it later. The PCB also allows the kernel to distinguish processes that execute the same program but have different identities, arguments, files, privileges, and histories. Process creation commonly follows a parent-child relationship. A parent may create a child with inherited resources, copy-on-write memory, or a newly constructed address space. In Unix-like systems, `fork()` conceptually creates a child and `exec()` replaces the child’s program image. These operations separate process creation from program replacement. A parent may later wait for a child’s termination and collect its exit status. Improper handling of terminated children can produce zombie processes, while a parent that exits before its children may cause them to be reparented. Context Switching: Necessary Cost of MultiprogrammingA context switch occurs when the processor changes from one execution context to another. The kernel saves the current context and restores the selected context. Depending on the system, the saved information may include registers, program counter, stack pointer, address-space references, floating-point or vector state, and security-related processor state. Context switching enables preemptive multitasking, but it is not free. Direct costs include saving and restoring registers, entering kernel mode, updating scheduler structures, and possibly changing address-space mappings. Indirect costs arise when caches and translation lookaside buffers become less effective. A process that resumes may suffer instruction-cache, data-cache, or TLB misses because another process displaced useful entries. The cost depends on architecture, kernel design, whether the switch is between threads in the same address space or between processes, and whether hardware support such as tagged TLB entries is available. Consequently, measuring only the time spent in the scheduler can underestimate the actual performance cost. A context switch must be distinguished from a mode switch. A system call can change execution from user mode to kernel mode while continuing on behalf of the same thread. A context switch changes the executing thread or process. A single system call may cause both: an I/O request may enter the kernel and then block the caller, allowing another thread to run. Threads and the Structure of Concurrent ExecutionA thread is an independently schedulable sequence of instructions within a process. Each thread has its own program counter, register set, stack, and thread-local storage. Threads within one process share the process’s address space and many resources. This sharing makes communication fast, but it also creates the possibility of data races and inconsistent state. A process is therefore often described as a resource container and a thread as an execution vehicle. Multiple threads are useful when activities can overlap, such as accepting network requests, performing computation, and waiting for storage. They are also useful for responsiveness: a graphical interface can remain interactive while a worker thread performs a long operation. Thread sharing creates a design obligation. If two threads update shared data without suitable synchronization, the result may depend on timing. A race condition exists when correctness depends on an uncontrolled ordering of concurrent operations. A critical section is a region that accesses shared state and must be protected according to the required correctness policy. Mutexes, semaphores, read-write locks, monitors, atomic instructions, and message-passing designs are common solutions. User-Level and Kernel-Level ThreadsUser-level threads are managed primarily by a runtime library rather than directly by the kernel. They can be created and switched quickly because many operations do not require a system call. The runtime can implement specialized scheduling policies and may support very large numbers of lightweight execution contexts. Their principal weakness is blocking behavior. If the kernel sees only one schedulable entity for a process and that entity performs a blocking system call, all user-level threads may stop. Pure user-level threading also cannot exploit multiple cores simultaneously unless the runtime maps execution over multiple kernel-visible entities. Kernel-level threads are known to and scheduled by the operating system. A blocking operation by one thread need not stop other threads in the same process, and separate threads can run simultaneously on different cores. The costs are greater management overhead, kernel scheduling activity, and more expensive creation or switching in many implementations. The major mapping models are: - Many-to-one: many user threads map to one kernel thread. This is efficient but prevents true parallelism and may suffer from process-wide blocking.
- One-to-one: each user thread maps to a kernel thread. It supports parallelism and independent blocking, but resource consumption may be higher.
- Many-to-many: many user threads are multiplexed over a smaller or adaptable set of kernel threads. It seeks to combine flexibility with parallel execution, although implementation complexity is significant.
Modern runtimes often combine these ideas. Language-level tasks, coroutines, and worker pools may be scheduled by a runtime over kernel threads, while the operating system schedules those kernel threads over processors. Interprocess Communication and CoordinationIndependent processes normally do not share writable memory by default. Interprocess communication (IPC) provides controlled mechanisms for exchanging information and coordinating action. The two broad models are shared memory and message passing. In a shared-memory model, processes map a common memory region into their address spaces. After setup, communication can be extremely fast because data is read and written directly. However, the operating system does not automatically make compound operations safe. Processes must coordinate through semaphores, mutexes, futex-like facilities, atomic operations, or carefully designed lock-free structures. Shared memory also raises concerns about stale data, memory ordering, false sharing, and accidental corruption. Message passing provides operations such as send and receive. Messages may travel through pipes, named pipes, queues, mailboxes, sockets, or kernel-supported channels. Message passing can provide stronger separation and clearer ownership, particularly when processes are distributed across machines. Its costs include copying, buffering, serialization, scheduling, and possible blocking. Communication may be direct, where a sender names a receiver, or indirect, where processes communicate through a mailbox or channel. It may be synchronous, requiring a sender and receiver to rendezvous, or asynchronous, allowing a sender to continue after placing a message in a buffer. Capacity and failure semantics matter: a full queue, a closed endpoint, or a crashed receiver must be represented and handled explicitly. Signals and Exceptional NotificationA signal is a concise asynchronous notification delivered to a process or thread. Signals can report events such as termination requests, illegal operations, timer expiration, or child-process status changes. A process may accept a default action, ignore some signals, or install a handler where permitted. Signals are appropriate for notification, not for transporting complex application data. Their asynchronous arrival makes handlers difficult to reason about. A handler should perform only operations that are safe in that context; many ordinary library functions are not asynchronous-signal-safe. A robust design often records a minimal fact in the handler and performs substantial work later in the normal execution path. Signal masking and disposition must also be considered when multiple threads are present. IPC does not eliminate synchronization. A message may arrive before the receiver is ready, a shared-memory producer may overrun a consumer, and two processes may deadlock while waiting for resources. Deadlock requires conditions such as mutual exclusion, hold-and-wait, no preemption, and circular wait. Timeouts, ordered resource acquisition, bounded queues, cancellation policies, and clear ownership can reduce these risks. Multicore Execution: From Concurrency to ParallelismConcurrency means that multiple activities make progress over an interval; parallelism means that multiple activities execute simultaneously. On a single core, concurrency is achieved through interleaving and context switching. On a multicore processor, independent threads may execute at the same time. The operating system’s scheduler assigns runnable threads to logical processors while considering priority, fairness, affinity, load balancing, and cache locality. Processor affinity can improve locality by keeping a thread near data in a particular cache, but excessive affinity may prevent effective load balancing. Simultaneous multithreading can expose multiple logical processors sharing some physical resources, so the number of runnable threads does not directly equal the number of fully independent execution units. Parallel speedup is limited by serial work, synchronization, communication, and unequal workload distribution. Amdahl’s law expresses this constraint: if fraction s of a workload is inherently serial, the theoretical speedup with p processors is at most 1 divided by s + (1-s)/p. The formula is an analytical guide, not a complete performance model; memory bandwidth, contention, and scheduling overhead can reduce speedup further. Correct multicore programs require attention to atomicity, visibility, ordering, and progress. A lock may prevent races but introduce contention. An apparently independent data structure may share cache lines and suffer false sharing. Testing must therefore include varied timing, processor counts, workloads, and failure conditions. Asynchronous and Event-Driven ExecutionAsynchronous programming separates initiating an operation from receiving its result. A thread may submit an I/O request and continue with other work; completion is later reported through a callback, future, promise, event, or message. This model is particularly valuable for high-latency operations such as network and storage I/O. An event loop repeatedly waits for events, dispatches handlers, and returns to waiting. A single event-loop thread can manage many connections when handlers are short and nonblocking. For CPU-intensive work, the event loop commonly delegates tasks to a worker pool. This arrangement avoids wasting threads while waiting, but it introduces challenges involving callback ordering, cancellation, error propagation, back pressure, and resource lifetime. Asynchronous does not necessarily mean parallel. An event loop may execute all handlers on one core and merely interleave their progress. Conversely, asynchronous operations may be implemented by kernel workers or runtime thread pools and therefore involve parallel execution. The distinction should be made explicitly when analyzing performance and correctness. Process Isolation and Protection BoundariesProcess isolation limits the damage that one process can cause to another. Virtual memory normally gives each process a private address-space view. Privileged kernel mechanisms prevent ordinary instructions from arbitrarily reading or modifying another process’s memory or hardware state. File permissions, user identities, capabilities, sandbox policies, and system-call restrictions add further controls. Isolation is not absolute. Processes can intentionally communicate through permitted IPC, and vulnerabilities in the kernel, device drivers, shared services, or speculative-execution defenses can weaken boundaries. Shared-memory regions, inherited descriptors, environment variables, temporary files, and signals should therefore be treated as security-relevant interfaces. Containers provide process isolation using mechanisms such as namespaces, resource control groups, capability restriction, and filesystem layering. A container generally shares the host kernel, whereas a virtual machine typically provides a stronger hardware-virtualization boundary with a separate guest kernel. The appropriate choice depends on threat model, performance requirements, operational complexity, and trust assumptions. Isolation also supports fault containment. A failed worker process can be restarted without terminating the entire service. Supervisors, service managers, and orchestration systems use this principle to monitor processes, enforce resource limits, and replace failed instances. However, restarting a process does not automatically repair corrupted external state; recovery protocols and durable data design remain necessary. An Illustrative Case: A Multicore Web-Service ArchitectureBackgroundConsider a fictional online document service that receives upload, conversion, search, and download requests. Its first implementation uses one process with a thread for each request. Under light traffic it performs adequately, but under load response time becomes unpredictable. Situation and AnalysisProfiling shows that conversion is CPU-intensive, storage operations are blocking, and shared in-memory metadata is protected by a single global mutex. Excessive threads increase context-switching and memory overhead. Requests waiting for storage occupy threads that could serve other clients, while conversion threads compete for processor time. A failure in a parsing library can also terminate the entire service process. The engineering team distinguishes three dimensions of the problem: resource isolation, concurrency structure, and parallel execution. They decide that not every activity should be represented by an independent long-lived thread. ApproachThe service is redesigned as several cooperating processes. A front-end process accepts connections and performs lightweight validation. A bounded message queue sends conversion jobs to a pool of worker processes. Each worker contains a small number of kernel threads and uses asynchronous storage operations where supported. Search metadata is accessed through a dedicated service rather than through unrestricted shared memory. The queue is bounded to provide back pressure. When it is full, the front end rejects or delays new conversion requests instead of consuming unlimited memory. Worker processes run with reduced privileges and restricted filesystem access. A supervisor restarts failed workers and records exit status. Within each worker, immutable job descriptions reduce synchronization, while a limited lock protects unavoidable shared counters. OutcomeThe redesigned system improves fault containment and makes resource consumption more predictable. CPU-intensive conversion is parallelized across cores, while storage waits no longer require an unlimited number of request threads. The bounded queue makes overload visible rather than allowing latency and memory use to grow without control. The design does not remove all problems. Queue capacity must be selected carefully, cancellation must clean up temporary files, and the service must preserve job state if a worker crashes. Monitoring therefore measures queue depth, process restarts, CPU utilization, I/O latency, lock contention, and failed requests. Learning PointsThe case demonstrates that process and thread design is a systems decision rather than a matter of selecting the largest possible thread count. Process boundaries provide protection and fault containment; threads provide efficient shared execution; asynchronous operations manage waiting; queues coordinate work and apply back pressure. Correctness, performance, and security must be considered together. Challenges, Limitations, and Professional PracticeCommon mistakes include confusing a program with a process, assuming that threads are independent processes, treating asynchronous execution as automatic parallelism, and using shared memory without a synchronization model. Other failures arise from unbounded thread creation, unbounded queues, signal handlers that perform unsafe operations, holding locks during blocking I/O, and ignoring cancellation or partial failure. A disciplined design begins by identifying ownership: which process owns a resource, which thread may modify it, and how lifetime is represented. It then specifies communication semantics, blocking behavior, failure handling, and resource limits. Measurement should include throughput, latency, runnable-queue length, context-switch rate, CPU migrations, cache effects, memory use, and IPC overhead. Microbenchmarks are useful, but production-like workloads are required because contention and external delays often dominate. Ethically and professionally, process isolation must not be treated as a substitute for authorization. Developers should minimize privileges, protect sensitive data in IPC, validate messages across trust boundaries, and log administrative actions without exposing confidential contents. Operational policies should define who may terminate, inspect, trace, or attach to processes. Systems handling personal or regulated data require appropriate retention, access-control, and audit practices. Current Directions and Best PracticesContemporary systems increasingly combine operating-system threads with language runtimes, coroutines, structured concurrency, asynchronous I/O, and service-level isolation. Structured concurrency attempts to make task ownership and cancellation explicit, reducing orphaned work and hidden failure. User-space networking and specialized I/O interfaces can reduce kernel overhead, but they also increase the responsibility placed on applications for memory safety and scheduling correctness. Useful practices include: - Choose processes, threads, tasks, or event handlers according to isolation, sharing, latency, and parallelism requirements.
- Bound queues, worker pools, memory use, and outstanding operations.
- Keep critical sections small and define lock ordering.
- Prefer immutable data or message ownership transfer where practical.
- Treat cancellation, timeout, and process failure as normal cases.
- Measure context switching, contention, I/O wait, and cache behavior rather than relying on intuition.
- Apply least privilege and restrict IPC interfaces.
- Test under overload, failure, multicore scheduling variation, and abnormal termination.
Chapter SummaryA process is an operating-system-managed execution and resource unit, while a thread is an execution flow within a process. Process states describe whether execution is new, ready, running, blocked, or terminated, and the PCB records the information needed for management and resumption. Context switching enables multiprogramming but introduces direct and indirect costs. Threads improve responsiveness and concurrency but require disciplined synchronization. User-level and kernel-level threading offer different trade-offs concerning overhead, blocking, and multicore parallelism. IPC may use shared memory or message passing, and signals provide limited asynchronous notification. Multicore systems expose genuine parallelism, but speedup is constrained by serial work, synchronization, and hardware effects. Asynchronous programming overlaps waiting with useful work without necessarily implying parallel execution. Finally, process isolation supports security and fault containment through virtual memory, permissions, restricted interfaces, and controlled deployment boundaries. Key TermsProcess: An active program instance with execution state, address space, resources, and an operating-system identity. Thread: A schedulable sequence of instructions within a process. Process Control Block: Kernel data structure containing process identity, state, context, scheduling, memory, and resource information. Ready State: State in which a process can execute but awaits processor assignment. Blocked State: State in which execution waits for an event or resource. Context Switch: Change from one executing thread or process to another, involving context preservation and restoration. Address Space: The virtual memory view available to a process. User-Level Thread: A thread managed mainly by a runtime library rather than directly by the kernel. Kernel-Level Thread: A thread represented and scheduled by the operating system kernel. Interprocess Communication: Mechanisms through which processes exchange information or coordinate. Shared Memory: IPC method in which processes access a common mapped memory region. Message Passing: IPC method based on explicit send and receive operations. Signal: Asynchronous notification delivered to a process or thread. Race Condition: Behavior whose correctness depends on uncontrolled timing among concurrent activities. Critical Section: Code region that accesses shared state and requires controlled synchronization. Parallelism: Simultaneous execution of multiple activities on multiple processing units. Asynchronous Programming: Execution model in which initiating an operation is separated from handling its later completion. Event Loop: Mechanism that waits for events and dispatches associated handlers. Process Isolation: Restriction of one process’s access to another process’s memory, resources, and privileges. Back Pressure: A mechanism that limits incoming work when downstream capacity is exhausted. Review QuestionsShort Answer Questions1. What information is typically stored in a Process Control Block? 2. Distinguish a mode switch from a context switch. 3. Why can a blocking system call be problematic for many-to-one user-level threads? 4. What is the purpose of a bounded IPC queue? 5. Why does asynchronous execution not necessarily imply parallel execution? Long Answer Questions1. Explain the process-state model and discuss how scheduling and I/O cause transitions between states. 2. Compare user-level, kernel-level, and many-to-many thread models, including their performance and blocking trade-offs. 3. Explain shared-memory and message-passing IPC, including synchronization, buffering, and failure concerns. 4. Analyze the direct and indirect costs of context switching and explain why they matter on multicore systems. 5. Discuss process isolation as a mechanism for security and fault containment, including the role of permissions, virtual memory, and restricted IPC. Analytical / Application Questions1. A server has a large number of threads, high context-switch rates, and many requests waiting for storage. Propose an execution model and justify your choices. 2. Two processes share a ring buffer, but data corruption appears only on multicore machines. Identify likely causes and recommend a synchronization and memory-ordering strategy. 3. Design a process and thread architecture for a document-conversion service that must handle CPU-intensive jobs, blocking storage, overload, and worker failure. Explain how isolation, queues, and asynchronous operations should interact. 22. Multiple Choice Questions with AnswersQuestion 1Which event most directly causes a running process to enter the blocked state? A. The scheduler assigns it a higher priority B. It requests an I/O operation that cannot complete immediately C. Another process enters the ready queue D. Its program counter is incremented Correct Answer: B. It requests an I/O operation that cannot complete immediately Question 2Which item is normally preserved as part of a thread’s processor context? A. The contents of every file in the filesystem B. The system-wide process table C. The program counter and processor registers D. The source code of the executing program Correct Answer: C. The program counter and processor registers Question 3A mode switch differs from a context switch because a mode switch can occur while: A. The same thread continues execution across privilege levels B. The processor changes to a different physical machine C. A process changes from blocked to ready D. A new address space is always created Correct Answer: A. The same thread continues execution across privilege levels Question 4What is a principal weakness of a many-to-one user-level thread model? A. It cannot share data within a process B. A blocking system call may block all user threads C. It requires one kernel thread for every user thread D. It always creates more context switches than kernel threads Correct Answer: B. A blocking system call may block all user threads Question 5Which IPC choice generally provides direct data access but requires explicit synchronization for shared updates? A. Shared memory B. A signal handler C. A process identifier D. A context switch Correct Answer: A. Shared memory Question 6A bounded work queue in a service primarily provides which benefit? A. It guarantees that all jobs execute in parallel B. It eliminates the need for process isolation C. It applies back pressure and limits resource growth D. It prevents every possible deadlock Correct Answer: C. It applies back pressure and limits resource growth Question 7Which statement correctly distinguishes concurrency from parallelism? A. Concurrency requires multiple physical cores B. Parallelism means activities are merely interleaved C. Concurrency concerns overlapping progress, while parallelism involves simultaneous execution D. They are identical terms in operating-system design Correct Answer: C. Concurrency concerns overlapping progress, while parallelism involves simultaneous execution Question 8Why can a process switch cost more than the visible scheduler operations suggest? A. It always recompiles the process B. It may disrupt caches and address-translation entries C. It permanently deletes the old PCB D. It converts all threads into user-level threads Correct Answer: B. It may disrupt caches and address-translation entries Question 9Which practice is most appropriate for a signal handler? A. Perform extensive non-reentrant library operations B. Start an unrestricted set of worker threads C. Record minimal notification state and defer complex work D. Modify arbitrary shared data without synchronization Correct Answer: C. Record minimal notification state and defer complex work Question 10In the illustrative document-service case, why were separate worker processes useful? A. They ensured that storage operations never block B. They provided fault containment and restricted privileges C. They removed the need for queues D. They made all computation sequential Correct Answer: B. They provided fault containment and restricted privileges
|