excp_handler() called context::current() unconditionally, which panics
with 'not inside of context' when no context exists yet (before
context::init() runs in kmain/kmain_ap). On bare metal, a page fault
during BSP's start() — e.g. ACPI table access or device MMIO — caused
page_fault_handler() to return Err, falling through to excp_handler(),
which then panicked at context::current() instead of reporting the
actual fault.
Replace context::current() with context::try_current(). When None,
log the exception details (kind, code, faulting address) and panic
with a descriptive message. This turns an uninformative cascading
panic into a diagnostic one that reveals the real faulting address.
- Add try_idle_context() to ContextSwitchPercpu (switch.rs)
Cross-CPU paths (steal_work, migrate_one_context) use try_idle_context()
instead of idle_context() to avoid panic when APs haven't called
context::init() yet. Returns Option<context::Arc> instead of panicking.
- Pin rust-toolchain.toml to nightly-2026-04-11
- Remove build artifacts (kernel, kernel.all, kernel.sym) from git tracking
- This fixes the boot panic that occurred during multi-CPU scheduling
Two-sided fix for the lock-ordering deadlock discovered by
Oracle review (Issue 24):
1. wakeup_contexts (this fn) held IDLE_CONTEXTS while
waiting for SchedQueuesLock on its own CPU via
SchedQueuesLock::new(&percpu.sched). If another CPU's
steal_work was holding that SchedQueuesLock (via a victim
SchedQueuesLock) and waiting for IDLE_CONTEXTS, both
threads spin forever.
Fix: drop idle_contexts immediately after building the
wakeups Vec. The Vec is the only data we need; releasing
the lock here means steal_work on another CPU can proceed
while this CPU acquires its own SchedQueuesLock.
2. steal_work held a victim's SchedQueuesLock (victim_lock)
while calling idle_contexts(token.downgrade()).push_back
on a context that turned out to be Blocked. This is the
matching side of the deadlock: CPU A held IDLE_CONTEXTS and
waited for its own SchedQueuesLock; CPU B (steal_work) held
CPU A's SchedQueuesLock and waited for IDLE_CONTEXTS.
Fix: use idle_contexts_try (try_lock) instead of
idle_contexts (blocking lock). If IDLE_CONTEXTS is busy
(owned by wakeup_contexts on another CPU), skip the
push-back; the context will be re-checked on the next
wakeup round because it was not removed from IDLE_CONTEXTS
(the Blocked status was set, but it stayed in IDLE_CONTEXTS
because we never re-pushed it).
The original code at line 429 used idle_contexts (blocking)
which is what makes this a real deadlock. try_lock is safe
because:
- If try_lock succeeds, the context is correctly pushed
- If try_lock fails, the context is still in IDLE_CONTEXTS
(we never removed it), so the next wakeup_contexts will
find it again
The proc scheme's Self::Priority write handler used
'kernel_prio = (20 - nice) as usize' which maps:
nice -20 -> kernel_prio 40, clamped to 39
nice 0 -> kernel_prio 20
nice 19 -> kernel_prio 1
But SCHED_PRIO_TO_WEIGHT[39] = 15 (lowest weight, least CPU
time), and SCHED_PRIO_TO_WEIGHT[0] = 88761 (highest weight,
most CPU time). So the old formula gave processes that set
nice to the most favorable value (-20) the LEAST CPU time,
and processes that set nice to the least favorable value (+19)
the MOST CPU time. Completely inverted.
Correct formula: kernel_prio = (nice + 20) as usize, giving:
nice -20 -> kernel_prio 0 (highest weight, most CPU)
nice 0 -> kernel_prio 20
nice 19 -> kernel_prio 39 (lowest weight, least CPU)
The corresponding read path (kernel_prio -> nice) is
'nice = (context.prio as i32 - 20)'. The old read was
'(20 - context.prio as i32)' which had the same inversion
plus a clamp that hid the bug for prio 0 (-> nice 20, clamped
to 19, never returned the correct -20).
Also fix the self-contradictory doc comment on Context::
set_sched_other_prio which claimed 'prio 39 is the lowest nice
value (highest CPU weight)' — actually prio 0 is the highest
weight and highest priority.
Discovered by Oracle review of Phase 0c patches (Issue 29).
The bug was introduced in the original P5-proc-setschedpolicy
patch (before Phase 0c) and survived because the kernel
boots with default priority 20 (nice 0), so the inversion was
invisible during normal testing.
The P5-proc-setschedpolicy, P7-proc-setname, and P7-proc-setpriority
patches all call context.set_sched_policy() and
context.set_sched_other_prio() — but neither method existed in the
local fork. Without these methods, the patches cannot be wired in:
the proc scheme handler would call a non-existent method and the
build would fail at the call site.
Implement both methods on Context:
set_sched_policy(policy, rt_priority):
- Sets self.sched_policy
- Clamps rt_priority to 0..99 for SCHED_FIFO/SCHED_RR
- Maps POSIX rt_priority to kernel SCHED_PRIORITY_LEVELS via
the existing rt_priority_to_kernel_prio() helper
- Resets sched_rr_ticks_consumed to 0
- For SCHED_OTHER, leaves rt_priority at 0 (priority is set
via set_sched_other_prio)
set_sched_other_prio(prio):
- Clamps prio to 0..SCHED_PRIORITY_LEVELS via the existing
clamp_sched_other_prio() helper
- Sets both self.prio and self.sched_static_prio
These two methods are the missing bridge between userspace
sched_setscheduler/setpriority calls (via the proc scheme) and the
kernel's RT-priority and DWRR-weight machinery. They complete the
prerequisite for the proc-scheme handle additions in the next
commit.
Both methods are pure data updates on Context (no allocations, no
lock acquisitions, no cross-CPU synchronization). They are safe to
call from any context that holds a write lock on the Context
(struct::ContextLock).
cargo check: 1 error remains (pre-existing fadt.rs:110 type mismatch
unrelated to threading).
Phase 0c, plan orders #5, #10, #11.
P8-initial-placement: context::Context::spawn() now picks the
least-loaded CPU for new threads based on PercpuSched.balance,
replacing the old 'pin to birth CPU' default.
P9-numa-topology: adds src/numa.rs (NumaTopology, NumaHint types and
MAX_NUMA_NODES constant) and threads the get_percpu_block import
through context/mod.rs. NUMA discovery is performed by userspace
numad via /scheme/acpi/ and pushed to the kernel via scheme:numa;
the kernel stores a lightweight copy for O(1) scheduler lookups.
P9-proc-lock-ordering: fix to scheme/proc.rs acquire order to
prevent deadlock between proc scheme handles and the per-CPU
sched lock. Required after P8-percpu-wiring moved the scheduler
state to per-CPU.
After this commit, three more of the plan's eleven P5–P9 patches are
landed. Remaining unlanded: P5-sched-rt-policy, P6-vruntime-switch,
P7-cache-affine-switch (all touch switch.rs which now diverges from
the patch baselines), and P5-scheme-sched-id/P5-proc-setschedpolicy/
P7-proc-setname/P7-proc-setpriority (overlap on scheme/proc.rs:10X-14X
context handle enum).
cargo check: 1 error remaining (pre-existing src/acpi/fadt.rs:110
unrelated to threading work).
Phase 0c, plan orders #3, #4, #7.
P5-context-mod-sched: re-export SchedPolicy from context::mod (one-line
change to the use statement). The type is defined in context::context
by the previous P7-cache-affine-context commit; this just makes it
available as crate::context::SchedPolicy.
P8-percpu-sched: adds PerCpuSched struct to percpu.rs with SyncUnsafeCell-
wrapped run_queues, balance/last_queue/last_balance_time cells, and
take_lock/release_lock methods. Refactors PercpuBlock to embed
PerCpuSched as 'sched' field instead of standalone 'balance'/'last_queue'
fields. Adds get_percpu_block() helper.
P8-percpu-wiring: rewrites src/context/switch.rs to consume PerCpuSched:
- select_next_context reads from percpublock.sched.queues() instead
of the global RunContextData.set
- Initial placement chooses least-loaded CPU via PercpuSched.balance
- Load balance trigger fires periodically and migrates contexts
between per-CPU queues respecting sched_affinity
- Adds pub const fn to access per-cpu sched state safely
After this commit, the kernel builds with per-CPU run queues wired
into the scheduler. cargo check still has 1 pre-existing unrelated
error (src/acpi/fadt.rs:110 type mismatch) that predates the threading
work.
Combined with the P6-futex-sharding commit, this completes the
foundation for Phase 1 (Futex Completeness) and Phase 2 (SMP Scheduling
Quality).
The P7-cache-affine-context patch fails to apply because the current
fork's context.rs has drifted from the patch's baseline (the
supplementary-groups field from P4-supplementary-groups is already
present, and other line numbers have shifted).
This is a manual surgical insertion of the P7 hunks that the kernel
needs to compile with the in-progress P8-percpu-wiring:
- Add SchedPolicy enum + SCHED_PRIORITY_LEVELS/DEFAULT_SCHED_OTHER_PRIORITY/
DEFAULT_SCHED_RR_QUANTUM constants at top of context.rs
- Add rt_priority_to_kernel_prio() and clamp_sched_other_prio() helpers
- Add PhysicalAddress to the memory import (used by futex_pi_waiters)
- Add last_cpu: Option<LogicalCpuId> field next to cpu_id
- Add sched_policy/sched_rt_priority/sched_rr_ticks_consumed/
sched_static_prio/sched_rr_quantum/vruntime/futex_pi_boost/
futex_pi_original_prio/futex_pi_waiters fields after prio
- Initialize all new fields in Context::new() with sensible defaults
Combined with the earlier RUN_QUEUE_COUNT pre-flight, this unblocks
P8-percpu-sched and P8-percpu-wiring to apply cleanly. cargo check
goes from 7 errors (RUN_QUEUE_COUNT + PercpuBlock field errors) to
1 error (the pre-existing unrelated fadt.rs type mismatch).
Phase 0c, plan order pre-flight for P7. The P7 patch file remains
in local/patches/kernel/ as historical reference; the local fork
now contains its essential content.
Pre-flight for Phase 0c. The P8-percpu-sched and P8-percpu-wiring
patches both reference crate::context::RUN_QUEUE_COUNT but none of
the kernel P5–P9 patches define it (verified by grep). The downstream
patches have an incomplete dependency: they need this constant at
the module level but no patch supplies it.
Add 'pub const RUN_QUEUE_COUNT: usize = 40;' here, matching the
historical 40-priority DWRR queue count. The P7-cache-affine-context
patch separately defines 'pub const SCHED_PRIORITY_LEVELS: usize = 40;'
in context/context.rs which is a duplicate; both being 40 keeps the
existing SCHED_PRIO_TO_WEIGHT and quantum tables valid.