Skip to main content
The SerenityOS kernel implements a priority-based preemptive multitasking scheduler that manages processes and threads. The scheduling subsystem is located in Kernel/Tasks/ and provides fair CPU time distribution across multiple threads.

Core Concepts

Process

A Process (Kernel/Tasks/Process.h) represents a program in execution with its own:
  • Address space (AddressSpace)
  • Open file descriptors
  • Credentials (UID, GID, groups)
  • Security context (pledge promises, unveil paths)
  • One or more threads
  • Process group and session membership
Processes are identified by a unique ProcessID (pid) and have a parent process (ppid).

Key Process State

Thread

A Thread (Kernel/Tasks/Thread.h) is the schedulable unit of execution. Each thread has:
  • Unique thread ID (ThreadID)
  • Reference to parent process
  • Priority level (1-31)
  • CPU affinity mask
  • Register state
  • Kernel and user stacks
  • Thread-local storage (TLS)

Thread States

Only threads in the Runnable state are eligible for scheduling.

The Scheduler

The Scheduler (Kernel/Tasks/Scheduler.h) is responsible for:
  • Selecting the next thread to run
  • Context switching between threads
  • Managing runnable thread queues
  • Timer-based preemption
  • Idle loop execution

Scheduling Algorithm

SerenityOS uses a priority-based round-robin scheduler with multiple priority queues:
Thread priorities range from THREAD_PRIORITY_MIN (1) to THREAD_PRIORITY_MAX (31):
  • Higher values = higher priority
  • Default priority: 30 (normal)
  • Idle threads: priority 1
The scheduler maintains a bitmask of non-empty queues for efficient queue selection:
When selecting the next thread, the scheduler uses bit_scan_forward() to find the highest-priority non-empty queue in O(1) time.

Time Slicing

Threads are given time slices based on their type:
When a thread’s time slice expires, the scheduler preempts it via timer interrupt.

Scheduling Operations

pick_next()

Selects the next thread to run:
  1. Checks for runnable threads in priority order
  2. Respects CPU affinity masks
  3. Skips threads already running on other cores
  4. Returns NoRunnableThreadFound if all threads blocked

yield()

Allows a thread to voluntarily give up the CPU:
The current thread is moved to the end of its priority queue and another thread is scheduled.

context_switch()

Performs the actual CPU context switch to a new thread:
  1. Saves current thread’s register state
  2. Switches page directory (address space)
  3. Loads new thread’s register state
  4. Updates TLS and stack pointers
  5. Restores execution
The scheduler lock (g_scheduler_lock) must be held during context switches to prevent race conditions.

Thread Management

Creating Threads

Threads are created via Thread::create():
User space creates threads via the create_thread syscall with parameters:

Thread Blocking

Threads block when waiting for resources using the BlockResult mechanism:
Common blocking scenarios:
  • WaitQueue: Waiting for events (e.g., child process exit)
  • Mutex: Waiting to acquire a lock
  • I/O: Waiting for data from files/sockets
  • Futex: User space synchronization primitives

Thread Finalization

When threads exit, they transition through Dying to Dead state. The finalizer thread (g_finalizer) performs cleanup:
The finalizer:
  1. Frees thread kernel stacks
  2. Releases thread resources
  3. Notifies joining threads
  4. Removes thread from process

Process Management

Process Creation

Processes are created via:
  • fork(): Duplicate current process (copy-on-write)
  • exec(): Replace process with new program
  • posix_spawn(): Combined fork+exec optimization

Process Groups and Sessions

Processes are organized into: Process Groups (ProcessGroup)
  • Collection of related processes
  • Share a process group ID (pgid)
  • Used for signal delivery to multiple processes
Sessions
  • Collection of process groups
  • Associated with controlling terminal
  • Managed via setsid(), getsid()

Process Security

Pledge

Restricts process capabilities via promises:
Once pledged, violations cause process termination.

Unveil

Restricts filesystem access to specific paths:
Pledge and unveil provide defense-in-depth security by limiting process capabilities after initialization.

CPU Affinity

Threads can be bound to specific CPUs via affinity masks:
The scheduler respects affinity when selecting threads:

Performance Tracking

The scheduler tracks CPU time usage:
Per-thread statistics include:
  • Time in user mode
  • Time in kernel mode
  • Context switches
  • Page faults

Work Queues

WorkQueue (Kernel/Tasks/WorkQueue.h) provides deferred work execution:
Work queues run at lower priority and don’t block critical paths.

Key Operations

Scheduling a Thread

Setting Thread Priority

Blocking and Unblocking

  • Kernel/Tasks/Scheduler.{h,cpp} - Core scheduler implementation
  • Kernel/Tasks/Thread.{h,cpp} - Thread abstraction
  • Kernel/Tasks/Process.{h,cpp} - Process management
  • Kernel/Tasks/ProcessGroup.{h,cpp} - Process group management
  • Kernel/Tasks/WaitQueue.{h,cpp} - Thread blocking mechanism
  • Kernel/Tasks/WorkQueue.{h,cpp} - Deferred work execution