Skip to content

Thread Scheduling

opencode-agent[bot] edited this page May 9, 2026 · 1 revision

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.

Overview

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.

Key Components

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)

How It Works

Thread States

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 vs Cooperative Scheduling

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:

  1. Adds the current thread back to the ready queue (if RUNNING)
  2. Checks for sleeping threads that have expired
  3. Selects the highest-priority thread from the ready queue
  4. 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() or sleep()

The @Uninterruptible Mechanism

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:

  1. No yieldpoints — The compiler skips safety checks that would otherwise trigger rescheduling
  2. Atomic execution — The method runs without interruption
  3. GC exclusion — The GC cannot stop this thread (it must wait)
  4. Use cases — Lock acquisition (Monitor.enter), interrupt handling, thread state changes

Rescheduling Flow

 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 ←───────────────────────────┘

Scheduler Queue Management

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.

Thread Switch Indicator (TSI)

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 (via disableReschedule())

Gotchas & Non-Obvious Behavior

  • Lock ordering mattersVmProcessor.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. Use VmProcessor.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 accessVmThread.currentThread() uses VmMagic.currentProcessor().getCurrentThread(), which is a fast inline operation.

Related Pages

Clone this wiki locally