Loading learning content...
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.
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.
A process is in the Running state when:
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:
| 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.
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.
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 │└───────────────────────────────────────────────────────────────────┘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.
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.
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 }}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.
From the OS perspective, these micro-architectural details are largely invisible. The OS sees a process that is "running" and making progress. However:
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.
A process in Running state constantly oscillates between user mode and kernel mode. Both are still "running"—but the privileges and code differ dramatically.
When executing in user mode, the process runs application code with restricted privileges:
This is the "normal" state of application execution—where your programs, libraries, and application logic run.
When a system call, exception, or interrupt occurs, the CPU switches to kernel mode:
Transitions between user and kernel mode are triggered by:
User → Kernel:
Kernel → User:
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 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.
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 code| 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 |
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:
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.
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.
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 │Most interrupts are transparent to the running process:
The process doesn't see the interruption—from its perspective, instructions just execute continuously. The only observable effect might be slightly longer execution time.
Interrupts can trigger state transitions:
Timer interrupt (time slice exhausted):
I/O completion for higher-priority process:
Signal delivery:
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.
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.
| 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 |
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.
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.
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.
Modern systems have multiple CPUs/cores, complicating the Running state—multiple processes can be Running simultaneously on different cores.
On an 8-core system:
1234567891011121314151617181920
8-Core System At Time T:═══════════════════════════════════════════════════════════════ Core 0: [Process A - RUNNING] ←── Executing user codeCore 1: [Process B - RUNNING] ←── In kernel (syscall) Core 2: [Process C - RUNNING] ←── ComputingCore 3: [IDLE - no runnable] ←── No process to runCore 4: [Process D - RUNNING] ←── Handling page faultCore 5: [Process E - RUNNING] ←── I/O submissionCore 6: [Process F - RUNNING] ←── User codeCore 7: [IDLE - no runnable] ←── No process to run Ready Queue: [G] → [H] → [I] → [J] → [K] 5 processes waiting for a CPU Wait Queues: Process M waiting for disk Process N waiting for network Process O waiting for child exit Total: 6 Running, 5 Ready, 3 WaitingProcesses may have CPU affinity—a preference or requirement to run on specific CPUs:
Soft Affinity: Scheduler prefers to keep process on same CPU (cache warmth), but may migrate if needed.
Hard Affinity: Process is restricted to specific CPU set. Set via taskset or sched_setaffinity().
A multi-threaded process can have multiple threads Running simultaneously:
Process P (4 threads):
Thread T1: Running on Core 0
Thread T2: Running on Core 2
Thread T3: Waiting (blocked on mutex held by T1)
Thread T4: Ready (waiting for a core)
This enables true parallelism—the same process making progress on multiple cores. This is the primary advantage of multi-threading over multi-processing.
Even on multi-core systems, context switching still occurs:
The goal is to keep all cores busy with Ready processes. When Ready queue is empty for a core, that core goes idle (saving power).
With hyperthreading (SMT), a single physical core presents as two logical cores. The OS sees 16 'CPUs' on an 8-core hyperthreaded system. Up to 16 processes can be 'Running,' but only 8 are truly executing in parallel—the hyperthread pairs share execution resources. For CPU-bound workloads, hyperthreading provides minimal benefit; for mixed workloads with stalls, it can improve throughput by 20-30%.
We've completed a comprehensive exploration of the Running state—the only state where computational progress actually occurs. Let's consolidate the key concepts:
What's next:
From the Running state, processes often transition to the Waiting (Blocked) state—halted not by scheduler preemption, but by the need for external events. We'll explore what causes processes to block, how waiting queues work, and how I/O completion returns processes to the Ready state.
You now understand the Running state deeply—from CPU context and instruction execution to user/kernel mode transitions, system calls, interrupts, and the five exit conditions. This knowledge is essential for understanding process behavior, debugging performance issues, and designing responsive systems.