Operating System Scheduling and Multiplexing
Multiplexing and Context Switching Mechanics
- Multiplexing creates the illusion of private virtual CPUs by time-sharing physical hardware among multiple executing processes.
- CPU switching executes in two primary scenarios: voluntarily when a process sleeps or waits for I/O, and involuntarily via periodic hardware timer interrupts.
- The context switch sequence moves from a user process to a kernel thread, transfers to the current CPU’s scheduler thread, swaps to a new process’s kernel thread, and returns to user space.
- Each CPU utilizes a dedicated scheduler thread with isolated saved registers and an independent stack.
- Dedicated scheduler stacks prevent multiple cores from simultaneously executing on the same yielded process stack.
- The
swtchfunction executes low-level context switching by saving and restoring 32 callee-saved RISC-V registers, which are organized within astruct context. swtchsaves the current CPU state, loads the new state, and captures thera(return address) register rather than the program counter directly.- Execution unconditionally resumes at the instruction pointed to by the restored
raregister, utilizing the newly restored stack pointer (sp).
Saving and restoring context establishes the fundamental hardware mechanism for swapping threads, which the scheduler directly leverages to cycle through available processes.
The Scheduler and Co-routines
- The scheduler exists as a specialized per-CPU thread running a continuous loop that evaluates the process table for
RUNNABLEprocesses. - Upon identifying a
RUNNABLEprocess, the scheduler updates the per-CPUc->procvariable, modifies the process state toRUNNING, and invokesswtchto execute it. - Processes voluntarily yielding the CPU acquire their process lock (
p->lock), update their internal state, and triggersched. - The
schedfunction invokesswtchto save the active process context and shift execution directly to the CPU’s scheduler context (cpu->scheduler). schedandschedulerfunction as co-routines, mutually transferring execution control back and forth to sustain continuous system operation.- The process lock (
p->lock) must remain held across theswtchboundary, actively passing lock ownership from the yielding thread to the newly executing thread.- Holding
p->lockduring the switch protects vital invariants, guaranteeing another CPU cannot attempt to run the yielding process on its original stack before the context switch finalizes.
- Holding
- During initial process creation,
allocprocexplicitly sets theraregister toforkret, routing the first context switch to a function that releasesp->lockprior to user space entry.
The continuous execution of these scheduler co-routines requires precise, real-time identification of the currently running process and the hardware CPU it occupies.
Per-CPU State and Execution Context
- Hardware CPUs are dynamically multiplexed across a shared process pool, requiring dedicated structures to safely track localized per-CPU states.
- A
struct cpuis allocated for each processor, recording the currently active process, the scheduler thread’s saved registers, and the nesting depth of acquired spinlocks. - The
mycpufunction extracts a pointer to the currentstruct cpuby reading the hardwaretpregister, which stores the RISC-Vhartid(hardware thread ID). - The
tpregister is initially populated in machine mode during boot sequence operations and is rigorously preserved across user-kernel transitions. - Interrupts must be disabled prior to invoking
mycpuormyproc.- Disabling interrupts prevents a timer interrupt from migrating the active thread to a different CPU mid-execution, which would critically invalidate the returned CPU reference.
- The
myprocfunction safely accesses the activestruct procpointer by disabling interrupts, callingmycpu, extractingc->proc, and immediately re-enabling interrupts.
Accurately tracking active CPU contexts enables complex thread coordination, such as conditionally pausing execution until a specific system state is achieved.
Conditional Synchronization: Sleep and Wakeup
- Sequence coordination relies heavily on
sleepandwakeupprimitives, allowing threads to voluntarily yield the CPU while waiting for asynchronous events. - The
sleep(chan, lock)architecture mandates a condition lock to eliminate the lost wake-up problem, wherein a thread sleeps waiting for a signal that was already transmitted. - When executed,
sleepimmediately acquires the process lock (p->lock) and subsequently drops the condition lock passed by the caller. - The process state transitions to
SLEEPING, the wait channel is logged, andschedis triggered to yield the processor. - The
wakeup(chan)function sequentially acquiresp->lockfor each evaluated process and transitions any process blocked on the matchingchantoRUNNABLE. - Maintaining
p->lockfrom before the condition is evaluated until after the process is markedSLEEPINGensureswakeupcannot bypass a blocking thread. - All
sleepinvocations must be encapsulated in awhileloop that iteratively re-evaluates the condition, safely filtering out spurious wakeups generated when multiple threads awake on the same channel.
These foundational sleep and wakeup primitives directly support higher-level synchronization structures, enabling coordinated data transfers between concurrently executing processes.
Producer-Consumer Synchronization: Pipes
- Pipes implement producer-consumer synchronization by combining
sleep,wakeup, and a dedicatedstruct pipe. - The pipe structure employs an internal lock (
pi->lock) to serialize access to read/write byte counters and the underlying data buffer. - Writing processes incrementally append data to the buffer and execute
wakeupon thepi->nreadchannel to notify waiting consumer threads. - If the buffer reaches capacity, the writer sleeps on the
pi->nwritechannel, automatically releasingpi->lockto allow readers to consume the data. - Reading processes extract data, increment read tracking counters, and issue a
wakeuponpi->nwriteto unblock suspended writers. - Deploying isolated wait channels for readers (
pi->nread) and writers (pi->nwrite) optimizes signal processing when multiple processes interface with a single pipe.
Similar conditional synchronization principles govern the lifecycle operations and inter-process coordination required during process termination.
Process Termination and Coordination: Wait, Exit, and Kill
- Process termination relies on conditional synchronization to securely transition memory resources and state data between exiting children and blocking parents.
- The
exitfunction shifts a process to theZOMBIEstate, signals the parent process, reparents active orphans toinit, and permanently yields the CPU. - The
waitfunction acquires the globalwait_lock—acting as the explicit condition lock—and scans the process table to clean upZOMBIEchildren and extract exit codes.- If no children have terminated,
waitinvokessleepand yields, waiting for a child to issue awakeup.
- If no children have terminated,
exitacquires bothwait_lockandp->locksequentially, guaranteeing a parent inwaitcannot miss the wakeup signal or process theZOMBIEstate before the child yields the CPU.- The
killfunction does not unilaterally destroy a process; it sets the target’sp->killedflag and issues awakeupif the target is currently sleeping. - System call loops verify the
p->killedflag upon returning fromsleepto trigger graceful termination, except during multi-step atomic I/O operations where aborting would corrupt system state.
Coordinating these process lifecycle events requires rigorous isolation of process state variables, governed by tightly controlled locking invariants.
Process Locking Mechanisms
- The process lock (
p->lock) actively shields high-level state structures from concurrent modifications across multiple hardware cores. - It protects essential fields including
p->state,p->chan,p->killed,p->xstate, andp->pid. p->lockprevents race conditions during process allocation, ensuring table slots are securely claimed during instantiation.- It strictly guarantees that only one scheduler thread can simultaneously transition a
RUNNABLEprocess to execution. - The lock interacts continuously with
wait_lockand condition locks to synchronizesleep,wakeup, and safe process destruction. - The
p->parentfield remains outsidep->lockjurisdiction; it is synchronized entirely by the globalwait_lockto serialize concurrent parent-child exits and prevent unhandled wakeups.
While the described scheduling and locking architectures are highly functional, they represent simplified baseline models compared to optimized production environments.
Advanced Scheduling and Real-World Equivalents
- The baseline round-robin scheduling policy cycles processes uniformly, whereas advanced operating systems deploy priority-based scheduling to favor targeted execution classes.
- Complex scheduling frameworks must actively manage priority inversion—where a low-priority process blocks a high-priority process by capturing a shared lock—and convoying.
- Explicit
sleepandwakeupmodels risk the “thundering herd” problem, wherein a singlewakeupschedules multiple competing processes that subsequently block. - Modern synchronization architectures replace raw wait channels with explicit wait queues, Rendezvous points, or condition variables that implement targeted
signalandbroadcastprimitives. - Production systems heavily utilize explicit semaphores, maintaining an internal integer count to completely bypass lost wakeups and mitigate thundering herd behaviors.
- Asynchronous process termination in advanced environments requires robust signal handling (e.g., returning
EINTR) to safely unwind stacks and interrupt sleeping processes.