-
Notifications
You must be signed in to change notification settings - Fork 0
Thread Scheduling
JNode uses a hybrid preemptive/cooperative scheduler where @Uninterruptible code runs without preemption, ensuring kernel code can execute atomically without being context-switched.
JNode's thread scheduling system is built around three core classes: VmThread, VmProcessor, and VmScheduler. The scheduler supports both preemptive and cooperative scheduling modes, with the @Uninterruptible annotation providing the mechanism for cooperative execution within the kernel.
The scheduler operates per-processor. Each VmProcessor maintains its own ready queue and handles thread switching independently. This design supports SMP (Symmetric Multiprocessing) where each CPU has its own scheduler instance.
| Class / File | Role |
|---|---|
core/src/core/org/jnode/vm/scheduler/VmThread.java |
Thread representation, states (RUNNING, ASLEEP, SUSPENDED, WAITING_ENTER, etc.), thread-local stack |
core/src/core/org/jnode/vm/scheduler/VmProcessor.java |
Per-CPU processor abstraction, manages current thread, handles yieldpoints and rescheduling |
core/src/core/org/jnode/vm/scheduler/VmScheduler.java |
Ready queue and sleep queue management, thread registration, priority-based scheduling |
core/src/core/org/jnode/vm/scheduler/VmThreadQueue.java |
Queue implementations for ready and sleep queues |
core/src/native/x86/vm-ints.asm |
Assembly interrupt handlers that trigger rescheduling (yieldpoints) |
A VmThread can be in one of these states:
- CREATED — Thread created but not started
- RUNNING — Currently executing on a processor
- SUSPENDED — Explicitly suspended (e.g., by another thread)
- ASLEEP — Sleeping (Thread.sleep())
- YIELDING — Explicitly yielded CPU (Thread.yield())
- WAITING_ENTER — Waiting to enter a monitor
- WAITING_NOTIFY — Waiting in a monitor (Object.wait())
- WAITING_NOTIFY_TIMEOUT — Waiting with timeout
- STOPPED / DESTROYED — Terminated
Preemptive Scheduling occurs when an interrupt (hardware timer or I/O) triggers a yieldpoint. The assembly code in vm-ints.asm calls VmProcessor.reschedule(), which:
- Adds the current thread back to the ready queue (if RUNNING)
- Checks for sleeping threads that have expired
- Selects the highest-priority thread from the ready queue
- Switches to the new thread
This happens transparently — the running thread has no control.
Cooperative Scheduling is achieved via the @Uninterruptible annotation. When a method is marked @Uninterruptible:
- The JIT compiler does NOT insert yieldpoint checks
- The thread cannot be preempted by the scheduler
- GC safepoints cannot stop this code
- The method runs to completion unless it explicitly calls
yield()orsleep()
The @Uninterruptible annotation (defined in org.jnode.annotation.Uninterruptible) is a directive to the JIT compiler:
@Uninterruptible
public final void interrupt() {
this.interrupted = true;
// ... no yieldpoints inserted here ...
}Key behaviors:
- No yieldpoints — The compiler skips safety checks that would otherwise trigger rescheduling
- Atomic execution — The method runs without interruption
- GC exclusion — The GC cannot stop this thread (it must wait)
- Use cases — Lock acquisition (Monitor.enter), interrupt handling, thread state changes
Hardware Timer IRQ → vm-ints.asm yieldpoint → VmProcessor.reschedule()
↓
┌──────────────────────────┴──────────────────────────┐
↓ ↓
Check sleep queue for expired threads Add current thread to ready queue
↓ ↓
Pop first sleeping thread (if wakeup time passed) Pop highest-priority ready thread
↓ ↓
└───→ Switch to new thread ←───────────────────────────┘
VmScheduler maintains two primary queues:
-
Ready Queue (
VmThreadQueue.ScheduleQueue) — Threads ready to run, ordered by priority -
Sleep Queue (
VmThreadQueue.SleepQueue) — Threads sleeping with a wakeup time
The scheduler uses ProcessorLock to protect queue operations, ensuring atomic access.
VmProcessor uses a threadSwitchIndicator word (first field in the class, accessed from assembly) with these flags:
-
TSI_SWITCH_NEEDED— Reschedule requested -
TSI_SYSTEM_READY— System ready for thread switching (set after boot) -
TSI_SWITCH_ACTIVE— Switch in progress -
TSI_BLOCK_SWITCH— Yieldpoints disabled (viadisableReschedule())
-
Lock ordering matters —
VmProcessor.disableReschedule(true)first blocks yieldpoints, then acquires the scheduler lock. This ordering prevents deadlock when calling scheduler methods from interrupt context. - @Uninterruptible is not transitive — Calling a non-@Uninterruptible method from within an @Uninterruptible method re-enables preemption. Be careful with inlining.
-
No voluntary yield in @Uninterruptible — Even within @Uninterruptible code, if you call
Thread.yield(), it will check yieldpoints. UseVmProcessor.yield()which respects the current interruptible state. -
Sleep queue is time-based — Threads in the sleep queue are ordered by wakeup time. The scheduler checks
canWakeup(curTime)before returning a thread. -
Priority boost for high-priority threads — In
VmProcessor.reschedule(), if a high-priority thread runs for too long (2500 iterations), a warning is logged. After 100000 iterations, it assumes a potential deadlock. -
Current processor access —
VmThread.currentThread()usesVmMagic.currentProcessor().getCurrentThread(), which is a fast inline operation.
- Architecture — System layers and where the scheduler fits
- VM-Magic — @Uninterruptible, @Inline, and other magic annotations
- Memory-Management — How thread stacks interact with the heap and GC
- Isolate-Implementation — How isolates affect thread isolation
- Exceptions-Implementation — How exceptions interact with thread state