Scheduling & Execution

DIS scheduler, thread & process models, priority classes, context switching, and resource budgets.

Profile Reference

Scheduling & Execution

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.

Scheduler Trait

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
rust
pub trait Scheduler: Send + Sync {
fn name(&self) -> &'static str;
fn version(&self) -> &'static str;
fn init(&mut self, cpu_count: usize) -> ExecResult<()>;
5
6
// 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<()>;
10
11
// Thread state transitions
fn thread_ready(&self, id: ThreadId) -> ExecResult<()>;
fn thread_block(&self, id: ThreadId) -> ExecResult<()>;
fn yield_thread(&self, cpu: usize);
15
16
// Timer tick — called on every timer interrupt
2 refs
fn tick(&self, cpu: usize);
18
19
// Priority management
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;
24
25
// Optional with defaults
fn set_policy(&self, id: ThreadId, policy: SchedulingPolicy) -> ExecResult<()>;
fn migrate_thread(&self, id: ThreadId, target_cpu: usize) -> ExecResult<()>;
28
}
29
30
// Install your scheduler globally
static FRAMEWORK: SchedulerFramework;
32
framework().set_scheduler(Arc::new(MyRoundRobin::new()))?;
Index

Thread Model

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 Transitions8N · 11E
setup donescheduledpreemptedwaitsleep(n)SIGSTOPresource availtimer expiredSIGCONTexit()parent aliveCreatingThread being set up1ReadyIn run queue5RunningCurrently executing5BlockedWaiting for resource2SleepingTimed wait2StoppedSuspended by signal2DeadExecution finished2ZombieAwaiting parent coll…1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node
subsystems/execution/src/thread/mod.rs
rust
2 refs
pub struct ThreadId(u64); // Auto-incrementing, ::idle() = 0
2 refs
pub struct ProcessId(u64); // Auto-incrementing, ::kernel() = 0
3
4
bitflags! {
2 refs
pub struct ThreadFlags: u32 {
6
const KERNEL = 1 << 0;
7
const IDLE = 1 << 1;
8
const INIT = 1 << 2; // Init thread
9
const FPU = 1 << 3;
10
const TRACED = 1 << 4;
11
const KILL_PENDING = 1 << 5; // Pending kill signal
12
const IN_SIGNAL = 1 << 6; // Handling signal
13
const NO_MIGRATE = 1 << 7; // Pinned to CPU
14
}
15
}
16
pub struct Thread {
18
id: ThreadId, // Fields are private
19
process: ProcessId,
20
name: String,
21
state: AtomicU32, // ThreadState as atomic
22
priority: RwLock<Priority>,
23
flags: RwLock<ThreadFlags>,
24
context: Mutex<ThreadContext>,
25
kernel_stack: KernelStack, // 16 KiB default
26
affinity: RwLock<u64>, // Bitmask
27
cpu: RwLock<Option<usize>>,
28
}
29
30
#[repr(u32)]
2 refs
pub enum ThreadState {
32
Creating=0, Ready=1, Running=2, Blocked=3,
33
Sleeping=4, Stopped=5, Dead=6, Zombie=7,
34
}
35
pub enum BlockReason {
37
Io, Lock, Signal, Futex,
38
Ipc, Memory, Child, Timer, Other,
39
}
Index

Process Model

Processes own threads and represent an isolated execution environment with its own address space.

subsystems/execution/src/process.rs
rust
pub struct Process {
2
id: ProcessId, // Fields are private
3
parent: Option<ProcessId>,
4
name: String,
5
state: AtomicU32, // ProcessState as atomic
6
main_thread: RwLock<Option<ThreadId>>,
7
threads: RwLock<Vec<ThreadId>>,
8
children: RwLock<Vec<ProcessId>>,
9
exit_code: RwLock<Option<i32>>,
10
}
11
2 refs
pub enum ProcessState { Creating, Running, Stopped, Zombie, Dead }
13
14
// Global process registry
pub static REGISTRY: ProcessRegistry;
2 refs
pub fn registry() -> &'static ProcessRegistry;
Index

Priority Classes

The priority system maps numeric ranges to scheduling classes. Higher priority means shorter time slices and more frequent scheduling.

ClassRangeTime SliceUse Case
Real-Time0 — 99Small, fixedHard deadlines, interrupt handlers, drivers
Normal100 — 139DynamicInteractive + batch (nice − 20 to +19)
Idle139 (MIN)RemainderOnly 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.

Context Switch Engine

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
rust
3 refs
pub enum SwitchReason {
2
Yield, Preemption, Blocked, Exit, Interrupt, NewThread,
3
}
4
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);
8
}
9
pub static ENGINE: ContextSwitchEngine;
3 refs
pub fn engine() -> &'static ContextSwitchEngine;
12
13
// Perform a context switch — directly manipulates CPU state
14
unsafe { engine().switch(from, to, &mut from_ctx, &to_ctx, reason); }
15
16
// Register monitoring hooks
17
engine().add_hook(Arc::new(PerformanceMonitor::new()));
Index

DIS — Dynamic Intent Scheduling

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 Flow5N · 4E
declare intentclassifyparamsdispatchTask + IntentClassUser declares intent1DIS ClassifierDynamic Intent Sched…2Priority + SliceComputed scheduling …2SchedulerActual scheduling de…2CPU ExecutionThread runs on core1
100%
☝ Drag to pan·🤏 Pinch to zoom·Tap a node

Intent Classes

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
rust
2 refs
pub enum IntentClass {
2
RealTime, // Base priority 0, 1ms slice
3
SoftRealTime, // Base priority 20, 4ms slice
4
Interactive, // Base priority 40, 8ms slice
5
Server, // Base priority 60, 12ms slice
6
Batch, // Base priority 80, 20ms slice
7
Background, // Base priority 100, 50ms slice
8
Idle, // Base priority 130, unlimited
9
Critical, // Base priority 0, 0.5ms slice
10
}
11
2 refs
impl IntentClass {
pub fn base_priority(&self) -> u8;
pub fn default_time_slice(&self) -> Nanoseconds;
pub fn can_preempt(&self, other: &Self) -> bool;
16
}
Index

Task Builder

DIS provides a fluent builder API for creating tasks with fine-grained control over scheduling behavior.

subsystems/dis/src/api.rs
rust
1
let dis = DIS::new(SchedulerConfig::default());
2
3
// Simple task creation
4
let handle = dis.create_task(
5
"render_thread",
6
render_fn,
7
IntentClass::Interactive
8
)?;
9
10
// Fine-grained builder
11
let handle = TaskBuilder::new(&dis, "io_worker", worker_fn)
12
.with_intent(IntentClass::Server)
13
.on_cpus(&[CpuId(0), CpuId(1)]) // CPU affinity
14
.priority(45)
15
.interactive()
16
.spawn()?;
17
18
// Runtime adjustments
19
dis.set_priority(handle.id, 30)?;
20
dis.update_intent(handle.id, IntentClass::Batch)?;
21
dis.yield_now()?;
22
dis.sleep(Nanoseconds(10_000_000))?; // 10ms
23
dis.destroy_task(handle.id)?;

Resource Budgets & Security

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
rust
pub enum CpuBudget {
2
Unlimited,
3
Percent(u8), // Max CPU percentage
4
Quota { period: Nanoseconds, quota: Nanoseconds }, // Rate limiting
5
Shares(u32), // Fair-share weight
6
}
7
2 refs
pub enum DomainType { Kernel, User, Driver, Sandbox }
2 refs
pub enum IsolationLevel { None, Basic, Standard, Strict, Paranoid }
10
pub struct SecurityDomain {
12
pub id: DomainId,
13
pub name: String,
14
pub domain_type: DomainType,
15
pub isolation_level: IsolationLevel,
16
pub capabilities: CapabilitySet,
17
}
18
pub enum Capability {
20
SchedulerAdmin, // Can modify scheduler config
21
TaskManage, // Can create/destroy other tasks
22
PriorityElevate, // Can raise priority above base
23
CpuPin, // Can pin tasks to specific CPUs
24
RealtimeAccess, // Can use RealTime/Critical intents
25
SystemInfo, // Can query system-wide stats
26
}
Index

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.