-
Notifications
You must be signed in to change notification settings - Fork 0
Core Thread Scheduling
JNode's thread scheduler uses cooperative yieldpoints inserted by the JIT compiler and preemptive timer interrupts to switch between runnable threads.
JNode implements a hybrid scheduling model on x86 that combines efficient cooperative scheduling with fair preemptive time-slicing. The scheduler is built around three core classes: VmThread (the schedulable unit), VmProcessor (the CPU core abstraction), and VmScheduler (the queue manager). The system uses a thread-switch indicator (TSI) pattern to control when switches can occur, and the @Uninterruptible annotation to protect critical sections from being interrupted.
The scheduler lives entirely in core/src/core/org/jnode/vm/scheduler/. There is one VmProcessor per physical CPU, each holding a reference to the current running thread and the next thread to run. The assembly layer (vm-ints.asm) handles hardware timer interrupts and yieldpoint traps (INT 0x30).
| Class / File | Role |
|---|---|
core/src/core/org/jnode/vm/scheduler/VmThread.java |
Schedulable unit with thread state, priority, queue entries |
core/src/core/org/jnode/vm/scheduler/VmProcessor.java |
CPU core abstraction, manages currentThread/nextThread, TSI flags |
core/src/core/org/jnode/vm/scheduler/VmScheduler.java |
Manages readyQueue, sleepQueue, allThreadsQueue; controls scheduling policy |
core/src/core/org/jnode/vm/scheduler/VmThreadQueue.java |
Priority-sorted run queue; also used for wait/sleep queues |
core/src/core/org/jnode/vm/scheduler/VmThreadQueueEntry.java |
Linked list entry embedded in VmThread for queue placement |
core/src/core/org/jnode/vm/scheduler/IdleThread.java |
Idle loop thread, runs when no other thread is runnable |
core/src/core/org/jnode/vm/scheduler/Monitor.java |
Object monitor with enter/exit; interacts with @Uninterruptible |
core/src/core/org/jnode/vm/scheduler/IRQManager.java |
IRQ dispatch; timer interrupt drives preemptive scheduling |
core/src/core/org/jnode/vm/scheduler/IRQThread.java |
Per-IRQ thread at MAX_PRIORITY that calls scheduler on interrupt |
core/src/native/x86/vm-ints.asm |
Timer interrupt handler, yieldpoint INT 0x30 handler |
core/src/vmmagic/org/vmmagic/pragma/Uninterruptible.java |
Interface marker that suppresses yieldpoint injection |
core/src/vmmagic/org/vmmagic/pragma/UninterruptiblePragma.java |
Exception class; methods with this in throws also skip yieldpoints |
VmThread manages these states (defined as int constants):
- CREATED (0) — constructed but not yet started
- RUNNING (1) — currently executing on a processor
-
SUSPENDED (2) — voluntarily paused by
suspend() -
ASLEEP (3) — waiting on a timer via
sleep(nanoseconds) -
STOPPED (4) — terminated by
stop() - DESTROYED (5) — fully cleaned up
- WAITING_ENTER (6) — blocked on a monitor
-
WAITING_NOTIFY (7) — in
wait()without a timeout -
WAITING_NOTIFY_TIMEOUT (8) — in
wait(timeout) - YIELDING (9) — voluntarily yielded, waiting to be rescheduled
VmProcessor uses a 4-bit TSI word to coordinate scheduling state:
static final int TSI_SWITCH_NEEDED = 0x0001; // Set by timer interrupt / yieldpoint
static final int TSI_SYSTEM_READY = 0x0002; // Set after VM init completes
static final int TSI_SWITCH_ACTIVE = 0x0004; // Context switch in progress
static final int TSI_BLOCK_SWITCH = 0x0008; // Lock held; yieldpoints suppressedTSI_SWITCH_REQUESTED is defined as TSI_SWITCH_NEEDED | TSI_SYSTEM_READY. A context switch only proceeds when threadSwitchIndicator & TSI_SWITCH_REQUESTED == TSI_SWITCH_REQUESTED && !isSwitchActive().
TSI_BLOCK_SWITCH is set whenever VmProcessor.lockCount > 0, which happens around critical sections like monitor acquisition. This forces cooperative yielding to wait.
The JIT compilers (L1 and L2) inject yieldpoints at method prologues and conditional branches. When a yieldpoint fires, the sequence is:
- The compiled code executes
INT 0x30(the yieldpoint interrupt). - Assembly handler
yieldPointHandlerinvm-ints.asmsaves thread state (registers, FPU, segment registers, model-specific registers). - It sets
TSI_SWITCH_ACTIVEand callsVmProcessor.reschedule(). - Java code in
VmProcessor.reschedule()dequeues the next ready thread from theVmScheduler's priority queue. - The handler restores the new thread's state and returns via
IRET.
Methods marked with @Uninterruptible (or declaring UninterruptiblePragma in their throws clause) have yieldpoints suppressed by the compiler. This means they run to completion atomically without yielding.
The hardware timer interrupt fires roughly every 8ms and is handled by timer_handler in vm-ints.asm:
- The handler sets
TSI_SWITCH_NEEDEDon all processors viabroadcastTimeSliceInterrupt(). -
IRQThreaddispatches the timer IRQ, waking any sleeping threads whosewakeupTimehas passed. - On the next yieldpoint or timer interrupt,
reschedule()will switch to a different thread.
A deadlock detector increments a counter on each timer tick and panics the system at 0x4000 iterations without a switch — an indication that a thread has been holding a lock too long without yielding.
reschedule()
1. Mark current thread (if runnable) as YIELDING, push to readyQueue
2. Wake sleeping threads: pop all from sleepQueue whose wakeupTime <= now
3. Pop the highest-priority thread from readyQueue as nextThread
4. Call wakeUpByScheduler() on nextThread → sets state to RUNNING
5. Swap context: currentThread = nextThread
6. Assembly layer restores next thread's registers and executes IRET
The VmThreadQueue is a priority-sorted run queue. Threads are ordered first by priority (lower number = higher priority), then by insertion order (FIFO within the same priority). The scheduler uses insertByPriority() and popFirst() as its core operations.
The @Uninterruptible interface (from org.vmmagic.pragma) is the primary mechanism to opt out of scheduling:
-
Compiler effect: Both L1 and L2 JIT compilers check for this interface. Methods implementing it (or declaring
UninterruptiblePragmain throws) receive no yieldpoint injections. -
Runtime effect: The
disableReschedule()/enableReschedule()pair increments/decrementsVmProcessor.lockCount. WhenlockCount > 0,TSI_BLOCK_SWITCHis set, causing yieldpoints to be deferred. - Purpose: Critical sections — particularly monitor enter/exit, SpinLock, and ProcessorLock operations — must complete atomically. If a thread could yield while holding a lock, deadlock or data corruption could result.
-
LogicallyUninterruptiblePragma: Used in rare cases where uninterruptibility is guaranteed by external means (e.g., interrupts are disabled). Marks intent without generating code.
-
Priority 0 is MAX: JNode uses lower numbers for higher priority. Priority 0 (
Thread.MAX_PRIORITY) is the highest. Priority 10 is the lowest. The idle thread runs at priority 10. - Idle thread starvation: The idle thread only runs when the ready queue is empty. On a single-processor system, this means the idle thread loops when no other thread is runnable.
- Yieldpoints are everywhere: In normal compiled code, yieldpoints are injected at almost every branch. This makes the system highly cooperative — a busy-waiting loop will yield frequently. This is intentional and efficient for I/O-bound workloads.
-
Timer interrupt is broadcast:
broadcastTimeSliceInterrupt()setsTSI_SWITCH_NEEDEDon everyVmProcessor, not just the current one. This ensures all CPUs participate in preemption. -
@Uninterruptibleis an interface, not an annotation: Theorg.vmmagic.pragma.Uninterruptibleis a marker interface. The compiler recognizes it and skips yieldpoint injection. This is distinct fromorg.jnode.annotation.Uninterruptible, which is a source-level annotation for documentation. -
Thread state is not a lock: Setting a thread to
YIELDINGorSUSPENDEDdoes not prevent a timer interrupt from settingTSI_SWITCH_NEEDED. The actual switch is deferred untilreschedule()is called. -
yieldPoint()is not a method call: The assembly codeINT 0x30is a software interrupt. The handler runs in kernel context before returning to Java.Unsafe.yieldPoint()is a native stub that triggers this interrupt.
- Architecture — High-level subsystem overview
- TSI — Thread Switch Indicator: atomic flags, assembly manipulation, state transitions
- VM-Magic — Unboxed types, @Uninterruptible pragma, Unsafe
- Boot-Sequence — Where the scheduler is initialized at boot
- Memory-Management — Stack allocation for new threads
- Glossary — VmProcessor, VmThread definitions