diff options
Diffstat (limited to 'na/kernel/sync')
| -rw-r--r-- | na/kernel/sync/mutex.cc | 156 | ||||
| -rw-r--r-- | na/kernel/sync/spinlock.cc | 11 |
2 files changed, 166 insertions, 1 deletions
diff --git a/na/kernel/sync/mutex.cc b/na/kernel/sync/mutex.cc new file mode 100644 index 0000000..c4d3e9d --- /dev/null +++ b/na/kernel/sync/mutex.cc @@ -0,0 +1,156 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* Copyright (C) 2026 Richard Qin */ + +/* + * Mutex — Linux-style sleeping mutex (no PI). + * + * Implementation notes (mirroring kernel/locking/mutex.c, simplified): + * + * - Fast path (uncontended): wait_lock_ -> see owner_==null -> claim -> unlock. + * - Slow path (contended): enqueue MutexWaiter on stack, sleep, race for + * ownership after each wake. unlock() wakes only the head waiter. + * - No optimistic spin, no handoff anti-starvation, no interruptible wait. + * These belong to follow-up work (see mutex.md §5). + * + * Reference: docs/specs/mutex.md + */ + +#include <lume/mutex.h> +#include <lume/task.h> +#include <lume/sched.h> +#include <lume/klog.h> +#include <arch/cpu.h> + +namespace { + +struct MutexWaiter { + list_node link; + TaskControlBlock* task; +}; + +} // namespace + +/* ------------------------------------------------------------------ */ +/* init */ +/* ------------------------------------------------------------------ */ + +void Mutex::init(const char* name) +{ + owner_ = nullptr; + name_ = name; + wait_lock_.init(name != nullptr ? name : "mutex"); + wait_list_.init(); +} + +/* ------------------------------------------------------------------ */ +/* try_lock — non-blocking, safe from any context */ +/* ------------------------------------------------------------------ */ + +bool Mutex::try_lock() +{ + LockGuard g(wait_lock_); + if (owner_ != nullptr) { + return false; + } + owner_ = current_task(); + return true; +} + +/* ------------------------------------------------------------------ */ +/* lock — blocking, must be called in schedulable context */ +/* ------------------------------------------------------------------ */ + +void Mutex::lock() +{ + TaskControlBlock* curr = current_task(); + + if (curr == nullptr) { + kernel_panic("Mutex::lock: no current task"); + } + if (curr->preempt_count > 0) { + kernel_panic("Mutex::lock: preemption disabled (holding spinlock?)"); + } + + wait_lock_.acquire(); + + if (owner_ == nullptr) { + owner_ = curr; + wait_lock_.release(); + return; + } + + if (owner_ == curr) { + wait_lock_.release(); + kernel_panic("Mutex::lock: recursive acquire by same task"); + } + + /* Slow path: enqueue ourselves and sleep until we are head + lock free. */ + MutexWaiter waiter; + waiter.link.init(); + waiter.task = curr; + list_add_tail(&waiter.link, &wait_list_); + + for (;;) { + /* Set state inside wait_lock_ so unlock-side sees a coherent view: + * either we are on the list AND Sleeping (will be woken), or we + * already took ownership above (not in this loop). */ + curr->state = TaskState::Sleeping; + wait_lock_.release(); + + schedule(); + + wait_lock_.acquire(); + + if (owner_ == nullptr && wait_list_.next == &waiter.link) { + owner_ = curr; + list_del_init(&waiter.link); + wait_lock_.release(); + return; + } + /* Spurious wake / lost race — keep waiting. */ + } +} + +/* ------------------------------------------------------------------ */ +/* unlock */ +/* ------------------------------------------------------------------ */ + +void Mutex::unlock() +{ + TaskControlBlock* curr = current_task(); + + wait_lock_.acquire(); + + if (owner_ != curr) { + wait_lock_.release(); + kernel_panic("Mutex::unlock: not the owner"); + } + + owner_ = nullptr; + + if (!wait_list_.is_empty()) { + list_node* first = wait_list_.next; + MutexWaiter* w = list_entry<MutexWaiter, &MutexWaiter::link>(first); + TaskControlBlock* victim = w->task; + + /* Race-tolerant wake: only enqueue if still sleeping. If the + * waiter is already Runnable (e.g. it was already woken by a + * timer-driven schedule reentry and didn't yet retry the lock), + * sched_enqueue would double-link it. */ + if (victim->state == TaskState::Sleeping) { + victim->state = TaskState::Runnable; + sched_enqueue(victim); + } + } + + wait_lock_.release(); +} + +/* ------------------------------------------------------------------ */ +/* Debug accessors */ +/* ------------------------------------------------------------------ */ + +bool Mutex::is_owned_by_current() const +{ + return owner_ == current_task(); +} diff --git a/na/kernel/sync/spinlock.cc b/na/kernel/sync/spinlock.cc index bca4684..f649fe7 100644 --- a/na/kernel/sync/spinlock.cc +++ b/na/kernel/sync/spinlock.cc @@ -17,6 +17,7 @@ #include "cpu_state.h" #include <arch/cpu.h> #include <lume/klog.h> +#include <lume/task.h> // ============================================================ // Per-CPU sync state (BSS zero-initialized) @@ -88,6 +89,10 @@ void Spinlock::acquire() { // Step 4: record owner cpu_id_ = arch::cpu::id(); + + // Step 5: bump preempt_count — prevents schedule() from preempting + TaskControlBlock* curr = current_task(); + if (curr) curr->preempt_count++; } void Spinlock::release() { @@ -102,7 +107,11 @@ void Spinlock::release() { // Step 3: release the lock with release semantics locked_.store(0, __ATOMIC_RELEASE); - // Step 4: restore interrupt state if this was the last lock + // Step 4: drop preempt_count + TaskControlBlock* curr = current_task(); + if (curr) curr->preempt_count--; + + // Step 5: restore interrupt state if this was the last lock sync::pop_intr(); } |
