Process States
Running State: Active CPU Execution
Read aloud is unavailable in this browser.
The Moment of Execution
In the grand choreography of process scheduling, the Running state is the brief but crucial solo performance. It is the only state where computation actually occurs—where instructions are fetched, decoded, and executed, where data is transformed, where the program makes actual progress toward its purpose.
Yet paradoxically, the Running state is also the most fleeting. On a typical desktop system, a process might spend milliseconds running, then return to Ready or Waiting for far longer. On a busy server with hundreds of processes, each may run for just a few milliseconds before yielding the CPU to another.
Understanding the Running state means understanding what the CPU does with a process—how it executes instructions, maintains context, and ultimately must relinquish control to ensure fair sharing of this most precious system resource.
By the end of this page, you will understand the Running state deeply—what it means to be running, how the CPU executes process instructions, what context is maintained during execution, the distinction between user and kernel mode execution, and the various exit conditions that terminate the Running state.
Defining the Running State
The Running state represents a process that is actively executing on a CPU. Among all processes in the system, only N processes can be in Running state simultaneously, where N is the number of CPU cores.
Formal Definition
A process is in the Running state when:
- The CPU's program counter points to an instruction in the process's address space
- The CPU's registers contain the process's context
- The MMU is configured with the process's page tables
- Instructions are being fetched and executed on behalf of this process
The Privilege of Running
The Running state is the only state where progress occurs. A process in any other state is waiting—waiting for CPU, waiting for I/O, waiting to be admitted, or already terminated. Only running processes:
- Execute application logic
- Perform calculations
- Access memory (triggering page faults if needed)
- Make system calls
- Generate output
| Characteristic | Value | Significance |
|---|---|---|
| CPU Ownership | Exclusive (on that core) | No other process executes on same core simultaneously |
| Instruction Stream | Active fetch-decode-execute | Program counter advances with each instruction |
| Memory Access | Full (virtual) access | All mapped addresses accessible via page tables |
| Time Limit | Bounded by time slice | Timer interrupt will preempt if slice exhausted |
| Count at Any Time | ≤ Number of CPU cores | On 8-core system, maximum 8 running processes |
A process in Running state isn't always executing user code. When a process makes a system call, the CPU switches to kernel mode and executes kernel code—but the process is still conceptually 'running.' The process's time accounting includes both user-mode and kernel-mode execution time. You can observe this with tools like 'time' (user vs sys time) or /proc/[pid]/stat.
CPU Context During Execution
When a process runs, the CPU must be configured with the process's execution context—the complete state needed to execute its instructions. This context was loaded during dispatch and is saved when the process exits Running state.
Components of CPU Context
- •Program Counter (PC/RIP) — Address of the next instruction to fetch. The CPU automatically increments this after each instruction; branches/jumps modify it explicitly.
- •Stack Pointer (SP/RSP) — Points to the top of the current stack. Essential for function calls, local variables, and return addresses.
- •General-Purpose Registers — RAX, RBX, RCX, ... (x86-64) or R0-R15 (ARM). Hold intermediate computation values, function arguments, return values.
- •Flags/Status Register — Zero flag, carry flag, overflow flag, etc. Store results of comparisons and arithmetic for conditional branches.
- •Floating-Point Registers — XMM0-XMM15, YMM registers (x86 SIMD). Hold floating-point values and SIMD data.
- •Segment Registers — CS, DS, SS, ES, FS, GS (x86). Mostly legacy but FS/GS used for thread-local storage.
- •Control Registers — CR0, CR3, CR4 (x86). CR3 holds page table base address. Kernel-only access.
- •CPU Mode Bit — Indicates user mode vs kernel mode. Restricts privileged operations in user mode.
123456789101112131415161718192021222324252627282930
┌───────────────────────────────────────────────────────────────────┐│ x86-64 CPU Context (per process) │├───────────────────────────────────────────────────────────────────┤│ ││ General Purpose Registers (64-bit): ││ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ││ │ RAX │ │ RBX │ │ RCX │ │ RDX │ ││ └─────────┘ └─────────┘ └─────────┘ └─────────┘ ││ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ││ │ RSI │ │ RDI │ │ RBP │ │ RSP │ ← Stack Pointer ││ └─────────┘ └─────────┘ └─────────┘ └─────────┘ ││ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ││ │ R8 │ │ R9 │ │ R10 │ │ R11 │ ││ └─────────┘ └─────────┘ └─────────┘ └─────────┘ ││ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ││ │ R12 │ │ R13 │ │ R14 │ │ R15 │ ││ └─────────┘ └─────────┘ └─────────┘ └─────────┘ ││ ││ Special Registers: ││ ┌─────────────────────┐ ┌─────────────────────┐ ││ │ RIP │ │ RFLAGS │ ││ │ (Program Counter) │ │ (Status Flags) │ ││ └─────────────────────┘ └─────────────────────┘ ││ ││ Segment Registers: CS, DS, SS, ES, FS, GS ││ SIMD State: XMM0-15, YMM0-15 (256 bits each for AVX) ││ FPU State: ST0-ST7, Control/Status words ││ ││ Total Context Size: ~800-1000 bytes depending on SIMD usage │└───────────────────────────────────────────────────────────────────┘Context Size and Switching Overhead
The context size determines context switch overhead:
| Context Component | Size (bytes) | Must Save/Restore? |
|---|---|---|
| General registers | ~128 | Always |
| RFLAGS, RIP | ~16 | Always |
| Segment registers | ~12 | Usually |
| FPU/x87 state | ~108 | If used (lazy save) |
| SSE state (XMM) | ~256 | If used |
| AVX state (YMM) | ~512 | If used |
| AVX-512 state | ~2688 | If used |
Modern CPUs implement lazy FPU context switching—floating-point state is only saved/restored if the process actually used floating-point or SIMD instructions. This optimizes the common case where many processes don't use these facilities.
Context switching isn't free. Saving and restoring hundreds of bytes of state takes time. More significantly, cache and TLB contents become stale after a switch—the new process must rebuild working sets. Direct context switch time: 1-10μs. Cache/TLB warmup cost: potentially much more. This is why excessive context switches degrade performance.
The Fetch-Decode-Execute Cycle
While a process is in Running state, the CPU continuously executes the fetch-decode-execute cycle (also called the instruction cycle). This is the fundamental rhythm of computation.
The Basic Cycle
- •Fetch: CPU reads instruction bytes from memory location pointed to by Program Counter (PC/RIP). Typical instruction: 1-15 bytes on x86-64.
- •Decode: Instruction decoder interprets opcode and operands. Determines what operation to perform and what registers/memory locations are involved.
- •Execute: ALU, FPU, or other execution units perform the operation—arithmetic, logic, memory access, branch decision.
- •Memory (if needed): For load/store instructions, access memory through cache hierarchy. May stall waiting for cache miss.
- •Write-back: Results are written to destination register. PC is updated to next instruction (or branch target).
- •Repeat: Cycle continues until interrupt, exception, or explicit halt.
1234567891011121314151617181920212223242526272829303132333435363738394041424344
// This is what the CPU does for EVERY instruction while process runs while (process_is_running) { // FETCH: Get instruction from memory instruction = memory[PC]; // DECODE: Interpret the instruction (opcode, operands) = decode(instruction); // EXECUTE: Perform the operation switch (opcode) { case ADD: result = operands[0] + operands[1]; break; case SUB: result = operands[0] - operands[1]; break; case LOAD: result = memory[operands[0]]; // Memory access break; case STORE: memory[operands[0]] = operands[1]; break; case JUMP: PC = operands[0]; // Modify PC directly continue; case SYSCALL: enter_kernel_mode(); // Process stays "running" in kernel handle_system_call(); return_to_user_mode(); break; // ... many more opcodes } // WRITE-BACK: Store result, advance PC destination_register = result; PC = PC + instruction_length; // CHECK: Any interrupts pending? if (interrupt_pending && interrupts_enabled) { save_context(); handle_interrupt(); // May cause state transition }}Pipelining and Superscalar Execution
Modern CPUs don't execute one instruction at a time. They use pipelining to overlap stages of multiple instructions:
Time → 1 2 3 4 5 6 7 8
Inst 1: F D E M W
Inst 2: F D E M W
Inst 3: F D E M W
Inst 4: F D E M W
Superscalar CPUs can execute multiple instructions in parallel—modern x86 CPUs can issue 4-6 instructions per cycle under ideal conditions.
Implications for Running State
From the OS perspective, these micro-architectural details are largely invisible. The OS sees a process that is "running" and making progress. However:
- Stalls (cache misses, branch mispredictions) slow execution but don't change state
- Traps/Exceptions (page faults, illegal instructions) trigger kernel intervention
- Interrupts may preempt the running process
The CPU handles billions of instructions per second, but any of them might trigger a state transition.
A 3 GHz CPU performs 3 billion cycles per second. With instruction-level parallelism, it might retire 10+ billion instructions per second. A typical 10ms time slice therefore allows execution of approximately 30-100 million instructions. This is why modern systems feel responsive despite rapid process switching.
User Mode vs Kernel Mode Execution
A process in Running state constantly oscillates between user mode and kernel mode. Both are still "running"—but the privileges and code differ dramatically.
User Mode Execution
When executing in user mode, the process runs application code with restricted privileges:
- Cannot execute privileged instructions (IN/OUT, MOV to CR3, HLT)
- Cannot access kernel memory (pages marked supervisor-only)
- Cannot modify MMU configuration
- Cannot disable interrupts
- Runs with the process's UID/GID permissions
This is the "normal" state of application execution—where your programs, libraries, and application logic run.
Kernel Mode Execution
When a system call, exception, or interrupt occurs, the CPU switches to kernel mode:
- All instructions are available (privileged instructions work)
- All memory is accessible (kernel addresses are valid)
- Can modify hardware state, page tables, and critical system structures
- Runs with kernel (root equivalent) privileges
- Still part of the same process's execution context
- •Application code execution
- •Limited instruction set
- •User-space virtual addresses only
- •UID/GID-based permissions
- •Cannot access hardware directly
- •Page faults for invalid access
- •Can be preempted at any time
- •Kernel code execution
- •Full instruction set access
- •All virtual addresses valid
- •Full system privileges
- •Direct hardware access
- •Errors cause kernel panic
- •May disable preemption briefly
The Mode Transition
Transitions between user and kernel mode are triggered by:
User → Kernel:
- System call instruction (SYSCALL on x86-64)
- Exception (page fault, division by zero, illegal instruction)
- Hardware interrupt (timer, I/O completion)
Kernel → User:
- Return from system call (SYSRET/IRET)
- Return from interrupt/exception handler
- Starting new process execution (first dispatch)
Time Accounting
The kernel tracks time spent in each mode:
$ time ./myprogram
real 0m5.234s # Wall clock time
user 0m4.012s # Time in user mode
sys 0m1.156s # Time in kernel mode
A program with high sys time is making many system calls or experiencing many page faults. A program with mostly user time is doing computation with little OS interaction.
Don't confuse process state (New/Ready/Running/Waiting/Terminated) with CPU mode (User/Kernel). A process in 'Running' state may be executing in either user mode or kernel mode. The process state describes the process's relationship to the scheduler; the CPU mode describes the privilege level of current execution.
System Calls: Controlled Kernel Entry
System calls are the mechanism by which a running process requests kernel services. Despite the mode transition, the process remains in Running state—it's just running kernel code instead of user code.
System Call Execution Flow
12345678910111213141516171819202122232425262728293031323334353637383940
// User space: open("/etc/passwd", O_RDONLY) // 1. C library wrapper prepares arguments// RAX = syscall number (2 for open)// RDI = pointer to filename// RSI = flags// RDX = mode (for O_CREAT) // 2. Execute SYSCALL instruction// This is a single CPU instruction that:// - Saves RIP to RCX// - Saves RFLAGS to R11// - Loads RIP from IA32_LSTAR MSR (kernel entry point)// - Sets CPL to 0 (kernel mode)// - Jumps to kernel's syscall entry // 3. Kernel entry (entry_SYSCALL_64)entry_SYSCALL_64: swapgs // Load kernel GS for per-CPU data movq %rsp, PER_CPU(rsp_scratch) // Save user stack movq PER_CPU(cpu_tss + TSS_sp0), %rsp // Load kernel stack pushq $__USER_DS // Build interrupt frame pushq PER_CPU(rsp_scratch) pushq %r11 // Saved RFLAGS pushq $__USER_CS pushq %rcx // Saved RIP // Save all registers SAVE_EXTRA_REGS // Call system call handler movq %rax, %rdi // Syscall number as first arg call *sys_call_table(, %rax, 8) // Dispatch to handler // Return value in RAX // 4. Return to user mode via SYSRET RESTORE_REGS swapgs sysretq // Return to user codeCommon Syscall Categories During Running
| Category | Examples | Running State Impact |
|---|---|---|
| I/O Operations | read(), write(), sendto() | May transition to Waiting if blocking |
| File Operations | open(), close(), stat() | Usually returns quickly to user |
| Memory Management | mmap(), brk(), mprotect() | May page fault, but stays Running |
| Process Control | fork(), exec(), exit() | May create new process or terminate |
| Information | getpid(), time(), uname() | Fast; returns immediately to user |
Blocking vs Non-Blocking Syscalls
Non-blocking syscalls (getpid, read from cache) return immediately. The process stays in Running state throughout—just briefly in kernel mode.
Blocking syscalls (read from slow disk, wait()) may cause state transition:
- Process Running → Waiting (blocked on I/O)
- Scheduler dispatches different process
- When I/O completes: Waiting → Ready
- Eventually: Ready → Running again
This is why blocking I/O causes the process to leave Running state, while non-blocking I/O keeps it running.
System calls exist because user code cannot directly call kernel functions—there's no way to 'call' an address in kernel space from user mode. The SYSCALL/SYSENTER instructions provide a controlled entry point that the kernel configures. This prevents user code from jumping to arbitrary kernel addresses, which would be a critical security vulnerability.
Interrupts: External Events During Execution
While a process runs, hardware interrupts can occur at almost any time. These asynchronous events temporarily suspend user code execution, handle the hardware event, and may or may not affect process state.
Interrupt Handling During Running
- •Timer Interrupt — Fires every 1-10ms (depending on kernel configuration). Scheduler checks if time slice is exhausted; may preempt (Running → Ready).
- •I/O Completion — Disk, network, or device completes operation. Waiting process awakened (Waiting → Ready). May trigger preemption if awakened process has higher priority.
- •Inter-Processor Interrupt (IPI) — CPU signaling another CPU. Used for TLB shootdown, scheduler rebalancing, etc.
- •External Hardware — Keyboard, mouse, network packet arrival. Handled by appropriate driver; process usually resumes after.
- •NMI (Non-Maskable Interrupt) — Critical hardware events (memory error, watchdog). Cannot be disabled. May cause immediate system action.
12345678910111213141516171819
Timeline of a 10ms Time Slice:─────────────────────────────────────────────────────────────────►│ ││ 0ms 3ms 6ms 8ms 10ms ││ ├─────────┬─────────┬─────────┬────────┤ ││ │ │ │ │ │ ││ │ Running │ Running │ Running │Running │ ││ │ (user) │ (kernel)│ (user) │(user) │ ││ │ │ syscall │ │ │ ││ │ │ │ │ │ ││ ▼ ▼ ▼ ▼ ▼ ││ Start syscall return network Timer ││ (read) to user IRQ Interrupt ││ (quick ││ handler) Preemption! ││ Running → Ready ││ ││ During entire 10ms, process is in "Running" state ││ Interrupts handled transparently unless state changes │Interrupt Transparency
Most interrupts are transparent to the running process:
- Interrupt arrives; CPU saves minimal state
- Kernel interrupt handler runs
- Handler returns; process execution resumes
- Process is unaware interrupt occurred
The process doesn't see the interruption—from its perspective, instructions just execute continuously. The only observable effect might be slightly longer execution time.
When Interrupts Change Process State
Interrupts can trigger state transitions:
Timer interrupt (time slice exhausted):
- Running process moves to Ready
- Scheduler selects new process
- Original process will run again later
I/O completion for higher-priority process:
- Sleeping process moves to Ready
- If higher priority than current: preemption
- Current process moves to Ready
Signal delivery:
- If process has handler: deliver signal
- If fatal signal (SIGKILL): process terminates (Running → Terminated)
The time between an interrupt arriving and its handler executing is 'interrupt latency.' For real-time systems, this must be bounded and minimal. Linux kernel preemption improvements aim to reduce worst-case interrupt latency. PREEMPT_RT patches can achieve latencies under 100 microseconds even under load.
Exit Conditions: Leaving the Running State
A process cannot run forever. Every running process eventually leaves the Running state through one of several exit conditions. Understanding these exits is key to understanding process lifecycle flow.
The Five Exit Paths
| Exit Condition | Destination State | Trigger | Example |
|---|---|---|---|
| Time Slice Expired | Ready | Timer interrupt | Process ran for 100ms; scheduler preempts |
| Voluntary Block | Waiting | Blocking syscall | read() on empty pipe; wait() for child |
| Priority Preemption | Ready | Higher-priority ready | Real-time process awakens; current demoted |
| Process Exit | Terminated | exit() or fatal signal | return from main(); SIGKILL received |
| Voluntary Yield | Ready | sched_yield() call | Process explicitly offers CPU to others |
1. Time Slice Exhaustion (Preemption)
This is the most common exit from Running state in preemptive systems:
1. Timer interrupt fires
2. Interrupt handler increments process's time-used counter
3. If time_used >= time_slice:
- Save process context
- Move process to Ready queue
- Select new process from Ready queue
- Dispatch new process
The preempted process will eventually be selected again, continuing where it left off.
2. Voluntary Block
When a process must wait for an external event:
1. Process calls blocking syscall (read, recv, wait, sleep)
2. Kernel determines data not available / condition not met
3. Process context saved
4. Process moved to appropriate wait queue
5. Process state → Waiting
6. Scheduler selects next Ready process
The process will remain Waiting until the awaited event occurs.
3. Priority Preemption
When a higher-priority process becomes Ready:
1. I/O completion or timer awakens high-priority process
2. Scheduler compares priorities
3. If awakened process > current process:
- Current process → Ready (preempted)
- High-priority process → Running
This ensures high-priority (often interactive) processes get CPU quickly.
12345678910111213141516171819202122232425
// Example: Voluntary yield in a cooperative workload#include <sched.h> void* worker_thread(void* arg) { while (should_continue) { // Do a small amount of work work_item item = get_next_work_item(); if (item != NULL) { process_item(item); } else { // No work available - be nice, let others run sched_yield(); // Running → Ready immediately // When we run again, check for more work } } return NULL;} // sched_yield() is rarely needed in modern systems// The scheduler is usually smart enough without hints// Use cases:// - Spin-locks that want to avoid pure busy-waiting// - Cooperative threading environments// - Avoiding priority inversion in specific scenariosBlocking and yielding are 'graceful' exits—the process chooses to leave Running state. Preemption (timer or priority) is a 'forced' exit—the scheduler takes the CPU away. Exit/termination can be either: graceful (return from main) or forced (SIGKILL). The distinction matters for cleanup: graceful exits allow orderly termination; forced exits may leave resources in inconsistent states.
KEEP LEARNING
There’s more to understand.
You’re reading the introduction. Create a free account to read the complete lesson, save notes and keep track of your progress.