Helix provides two scheduling systems that work together. The Execution Framework (helix-execution) defines the core scheduler trait, thread/process models, and context switching. DIS (helix-dis) builds on top of it with intent-aware, adaptive scheduling that classifies workloads and adjusts scheduling strategy in real-time.
You can use either system independently. For simple kernels, the Execution Framework with a round-robin scheduler is sufficient. For production workloads, DIS provides sophisticated workload classification and security isolation.
This is the core interface that every scheduler implementation must satisfy. The framework calls these methods — you just provide the scheduling logic.
subsystems/execution/src/scheduler/traits.rs
pub trait Scheduler : Send + Sync {
fn name ( & self ) -> & 'static str ;
fn version ( & self ) -> & 'static str ;
fn init ( & mut self , cpu_count : usize ) -> ExecResult < ( ) > ;
// Core scheduling decisions
fn pick_next ( & self , cpu : usize ) -> Option < ThreadId > ;
fn add_thread ( & self , thread : SchedulableThread ) -> ExecResult < ( ) > ;
fn remove_thread ( & self , id : ThreadId ) -> ExecResult < ( ) > ;
// Thread state transitions
fn thread_ready ( & self , id : ThreadId ) -> ExecResult < ( ) > ;
fn thread_block ( & self , id : ThreadId ) -> ExecResult < ( ) > ;
fn yield_thread ( & self , cpu : usize ) ;
// Timer tick — called on every timer interrupt
fn tick ( & self , cpu : usize ) ;
fn set_priority ( & self , id : ThreadId , priority : Priority ) -> ExecResult < ( ) > ;
fn get_priority ( & self , id : ThreadId ) -> Option < Priority > ;
fn needs_reschedule ( & self , cpu : usize ) -> bool ;
fn stats ( & self ) -> SchedulerStats ;
// Optional with defaults
fn set_policy ( & self , id : ThreadId , policy : SchedulingPolicy ) -> ExecResult < ( ) > ;
fn migrate_thread ( & self , id : ThreadId , target_cpu : usize ) -> ExecResult < ( ) > ;
// Install your scheduler globally
static FRAMEWORK : SchedulerFramework ;
framework ( ) . set_scheduler ( Arc :: new ( MyRoundRobin :: new ( ) ) ) ? ;
Index Scheduler name version init pick_next add_thread remove_thread thread_ready thread_block yield_thread tick set_priority get_priority needs_reschedule stats set_policy migrate_thread FRAMEWORK
Threads are the basic unit of execution. Each thread has a unique ID, belongs to a process, and carries its own CPU context for context switching.
Thread State Transitions 8N · 11E setup done scheduled preempted wait sleep(n) SIGSTOP resource avail timer expired SIGCONT exit() parent alive ▶ Creating Thread being set up 1 Ready In run queue 5 Running Currently executing 5 Blocked Waiting for resource 2 Sleeping Timed wait 2 Stopped Suspended by signal 2 Dead Execution finished 2 ■ Zombie Awaiting parent coll… 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
subsystems/execution/src/thread/mod.rs
pub struct ThreadId ( u64 ) ; // Auto-incrementing, ::idle() = 0
pub struct ProcessId ( u64 ) ; // Auto-incrementing, ::kernel() = 0
pub struct ThreadFlags : u32 {
const INIT = 1 << 2 ; // Init thread
const KILL_PENDING = 1 << 5 ; // Pending kill signal
const IN_SIGNAL = 1 << 6 ; // Handling signal
const NO_MIGRATE = 1 << 7 ; // Pinned to CPU
id : ThreadId , // Fields are private
state : AtomicU32 , // ThreadState as atomic
priority : RwLock < Priority > ,
flags : RwLock < ThreadFlags > ,
context : Mutex < ThreadContext > ,
kernel_stack : KernelStack , // 16 KiB default
affinity : RwLock < u64 > , // Bitmask
cpu : RwLock < Option < usize >> ,
Creating = 0 , Ready = 1 , Running = 2 , Blocked = 3 ,
Sleeping = 4 , Stopped = 5 , Dead = 6 , Zombie = 7 ,
Ipc , Memory , Child , Timer , Other ,
Index ThreadId ProcessId ThreadFlags Thread ThreadState BlockReason
Processes own threads and represent an isolated execution environment with its own address space.
subsystems/execution/src/process.rs
id : ProcessId , // Fields are private
parent : Option < ProcessId > ,
state : AtomicU32 , // ProcessState as atomic
main_thread : RwLock < Option < ThreadId >> ,
threads : RwLock < Vec < ThreadId >> ,
children : RwLock < Vec < ProcessId >> ,
exit_code : RwLock < Option < i32 >> ,
pub enum ProcessState { Creating , Running , Stopped , Zombie , Dead }
// Global process registry
pub static REGISTRY : ProcessRegistry ;
pub fn registry ( ) -> & 'static ProcessRegistry ;
Index Process ProcessState REGISTRY registry
The priority system maps numeric ranges to scheduling classes. Higher priority means shorter time slices and more frequent scheduling.
Class Range Time Slice Use Case Real-Time 0 — 99 Small, fixed Hard deadlines, interrupt handlers, drivers Normal 100 — 139 Dynamic Interactive + batch (nice − 20 to +19) Idle 139 (MIN) Remainder Only runs when no other thread is ready
Priority uses struct Priority { static_priority: u8, dynamic_adjustment: i8 }. Lower number = higher priority. Effective priority = static + dynamic. The scheduler boosts interactive threads and penalizes CPU-bound ones automatically.
The context switch engine saves and restores CPU state when switching between threads. It also supports hooks for pre/post-switch actions — useful for performance monitoring and debugging.
subsystems/execution/src/context.rs
Yield , Preemption , Blocked , Exit , Interrupt , NewThread ,
pub trait ContextSwitchHook : Send + Sync {
fn pre_switch ( & self , from : ThreadId , to : ThreadId , reason : SwitchReason ) ;
fn post_switch ( & self , from : ThreadId , to : ThreadId , reason : SwitchReason ) ;
pub static ENGINE : ContextSwitchEngine ;
pub fn engine ( ) -> & 'static ContextSwitchEngine ;
// Perform a context switch — directly manipulates CPU state
unsafe { engine ( ) . switch ( from , to , & mut from_ctx , & to_ctx , reason ) ; }
// Register monitoring hooks
engine ( ) . add_hook ( Arc :: new ( PerformanceMonitor :: new ( ) ) ) ;
Index SwitchReason ContextSwitchHook pre_switch post_switch ENGINE engine
DIS extends basic scheduling with intent classification . Instead of just assigning numeric priorities, tasks declare their behavioral intent — "I'm an interactive UI thread" or "I'm a background batch job" — and DIS adapts its scheduling strategy accordingly.
DIS Scheduling Flow 5N · 4E declare intent classify params dispatch ▶ Task + IntentClass User declares intent 1 DIS Classifier Dynamic Intent Sched… 2 Priority + Slice Computed scheduling … 2 Scheduler Actual scheduling de… 2 ■ CPU Execution Thread runs on core 1
☝ Drag to pan · 🤏 Pinch to zoom · Tap a node
Ctrl+F Search
P Path
S Stats
F Fullscreen
E Export
Shift+Drag Move node
↑↓ Navigate
+/− Zoom
Each intent class defines a base priority, default time slice, and preemption rules. The scheduler uses these to make smarter decisions than raw priority numbers alone.
subsystems/dis/src/intent.rs
RealTime , // Base priority 0, 1ms slice
SoftRealTime , // Base priority 20, 4ms slice
Interactive , // Base priority 40, 8ms slice
Server , // Base priority 60, 12ms slice
Batch , // Base priority 80, 20ms slice
Background , // Base priority 100, 50ms slice
Idle , // Base priority 130, unlimited
Critical , // Base priority 0, 0.5ms slice
pub fn base_priority ( & self ) -> u8 ;
pub fn default_time_slice ( & self ) -> Nanoseconds ;
pub fn can_preempt ( & self , other : & Self ) -> bool ;
Index IntentClass IntentClass base_priority default_time_slice can_preempt
DIS provides a fluent builder API for creating tasks with fine-grained control over scheduling behavior.
subsystems/dis/src/api.rs let dis = DIS :: new ( SchedulerConfig :: default ( ) ) ;
let handle = dis . create_task (
let handle = TaskBuilder :: new ( & dis , "io_worker" , worker_fn )
. with_intent ( IntentClass :: Server )
. on_cpus ( & [ CpuId ( 0 ) , CpuId ( 1 ) ] ) // CPU affinity
dis . set_priority ( handle . id , 30 ) ? ;
dis . update_intent ( handle . id , IntentClass :: Batch ) ? ;
dis . sleep ( Nanoseconds ( 10_000_000 ) ) ? ; // 10ms
dis . destroy_task ( handle . id ) ? ;
DIS enforces per-task resource budgets and runs each task inside a security domain with specific capabilities. This prevents a runaway task from starving the system.
subsystems/dis/src/isolation.rs
Percent ( u8 ) , // Max CPU percentage
Quota { period : Nanoseconds , quota : Nanoseconds } , // Rate limiting
Shares ( u32 ) , // Fair-share weight
pub enum DomainType { Kernel , User , Driver , Sandbox }
pub enum IsolationLevel { None , Basic , Standard , Strict , Paranoid }
pub struct SecurityDomain {
pub domain_type : DomainType ,
pub isolation_level : IsolationLevel ,
pub capabilities : CapabilitySet ,
SchedulerAdmin , // Can modify scheduler config
TaskManage , // Can create/destroy other tasks
PriorityElevate , // Can raise priority above base
CpuPin , // Can pin tasks to specific CPUs
RealtimeAccess , // Can use RealTime/Critical intents
SystemInfo , // Can query system-wide stats
Index CpuBudget DomainType IsolationLevel SecurityDomain Capability
DIS is entirely optional. If your kernel doesn't need intent-aware scheduling, use the base Scheduler trait with a simple round-robin or priority implementation. DIS is a layer on top — it composes with the execution framework rather than replacing it.