From f902ab48a120812256e8d89585c8f4f83acc3f3f Mon Sep 17 00:00:00 2001 From: Daniel Hammer Date: Tue, 24 Mar 2026 07:40:37 +0100 Subject: [PATCH] fix: improve scheduling, memory, timer implementations --- kernel/src/Api/Common.hpp | 23 +++-- kernel/src/Api/Process.hpp | 39 +-------- kernel/src/Drivers/PS2/Mouse.cpp | 17 +++- kernel/src/Fs/Vfs.cpp | 2 + kernel/src/Hal/Cpu.hpp | 19 +++++ kernel/src/Hal/IDT.cpp | 9 +- kernel/src/Hal/IDT.hpp | 2 +- kernel/src/Hal/SmpBoot.cpp | 31 ++++++- kernel/src/Hal/SmpBoot.hpp | 1 + kernel/src/Main.cpp | 15 +++- kernel/src/Memory/Heap.cpp | 13 ++- kernel/src/Sched/Scheduler.cpp | 122 ++++++++++++++++++++++++--- kernel/src/Sched/Scheduler.hpp | 18 ++-- kernel/src/Timekeeping/ApicTimer.cpp | 22 +++-- 14 files changed, 239 insertions(+), 94 deletions(-) diff --git a/kernel/src/Api/Common.hpp b/kernel/src/Api/Common.hpp index 7b962de..c9e3841 100644 --- a/kernel/src/Api/Common.hpp +++ b/kernel/src/Api/Common.hpp @@ -12,17 +12,26 @@ namespace Montauk { return Sched::GetProcessByPid(proc->parentPid); } - static void RingWrite(uint8_t* buf, uint32_t& head, uint32_t /*tail*/, uint32_t size, uint8_t byte) { - buf[head] = byte; - head = (head + 1) % size; + // SPSC ring buffer helpers. On x86 TSO, atomic-width aligned + // loads/stores are naturally atomic. The compiler barrier ensures + // the data write is visible before the head update. + static void RingWrite(uint8_t* buf, volatile uint32_t& head, uint32_t /*tail*/, uint32_t size, uint8_t byte) { + uint32_t h = head; + buf[h] = byte; + asm volatile("" ::: "memory"); // compiler barrier + head = (h + 1) % size; } - static int RingRead(uint8_t* buf, uint32_t& head, uint32_t& tail, uint32_t size, uint8_t* out, int maxLen) { + static int RingRead(uint8_t* buf, volatile uint32_t& head, volatile uint32_t& tail, uint32_t size, uint8_t* out, int maxLen) { int count = 0; - while (tail != head && count < maxLen) { - out[count++] = buf[tail]; - tail = (tail + 1) % size; + uint32_t t = tail; + uint32_t h = head; + while (t != h && count < maxLen) { + out[count++] = buf[t]; + t = (t + 1) % size; } + asm volatile("" ::: "memory"); // compiler barrier + tail = t; return count; } } \ No newline at end of file diff --git a/kernel/src/Api/Process.hpp b/kernel/src/Api/Process.hpp index c27a898..12b90ba 100644 --- a/kernel/src/Api/Process.hpp +++ b/kernel/src/Api/Process.hpp @@ -97,43 +97,6 @@ namespace Montauk { } static int Sys_Kill(int pid) { - // Refuse to kill PID 0 (init) - if (pid == 0) return -1; - // Refuse to kill the caller's own process - if (pid == Sched::GetCurrentPid()) return -1; - - auto* proc = Sched::GetProcessByPid(pid); - if (!proc) return -1; - - // Clean up any windows owned by this process (unmaps pixel pages from desktop) - WinServer::CleanupProcess(pid); - - // Free I/O redirect buffers - if (proc->outBuf) { - Memory::g_pfa->Free(proc->outBuf); - proc->outBuf = nullptr; - } - if (proc->inBuf) { - Memory::g_pfa->Free(proc->inBuf); - proc->inBuf = nullptr; - } - - // Free all user-space pages and page table structures - Memory::VMM::Paging::FreeUserHalf(proc->pml4Phys); - - // Free kernel stack (safe — killed process isn't running on single-core) - if (proc->stackBase != 0) { - Memory::g_pfa->Free((void*)proc->stackBase, Sched::StackPages); - proc->stackBase = 0; - } - - // Free the PML4 page - if (proc->pml4Phys != 0) { - Memory::g_pfa->Free((void*)Memory::HHDM(proc->pml4Phys)); - proc->pml4Phys = 0; - } - - proc->state = Sched::ProcessState::Terminated; - return 0; + return Sched::KillProcess(pid); } }; \ No newline at end of file diff --git a/kernel/src/Drivers/PS2/Mouse.cpp b/kernel/src/Drivers/PS2/Mouse.cpp index de39635..b846aa7 100644 --- a/kernel/src/Drivers/PS2/Mouse.cpp +++ b/kernel/src/Drivers/PS2/Mouse.cpp @@ -181,20 +181,31 @@ namespace Drivers::PS2::Mouse { } int32_t GetX() { - return g_State.X; + g_StateLock.Acquire(); + int32_t x = g_State.X; + g_StateLock.Release(); + return x; } int32_t GetY() { - return g_State.Y; + g_StateLock.Acquire(); + int32_t y = g_State.Y; + g_StateLock.Release(); + return y; } uint8_t GetButtons() { - return g_State.Buttons; + g_StateLock.Acquire(); + uint8_t b = g_State.Buttons; + g_StateLock.Release(); + return b; } void SetBounds(int32_t maxX, int32_t maxY) { + g_StateLock.Acquire(); g_MaxX = maxX; g_MaxY = maxY; + g_StateLock.Release(); } void FlushState() { diff --git a/kernel/src/Fs/Vfs.cpp b/kernel/src/Fs/Vfs.cpp index c5794ec..aba7192 100644 --- a/kernel/src/Fs/Vfs.cpp +++ b/kernel/src/Fs/Vfs.cpp @@ -200,12 +200,14 @@ namespace Fs::Vfs { } int VfsDriveList(int* outDrives, int maxEntries) { + vfsLock.Acquire(); int count = 0; for (int i = 0; i < MaxDrives && count < maxEntries; i++) { if (driveTable[i] != nullptr) { outDrives[count++] = i; } } + vfsLock.Release(); return count; } diff --git a/kernel/src/Hal/Cpu.hpp b/kernel/src/Hal/Cpu.hpp index 9fa6e4a..0062b52 100644 --- a/kernel/src/Hal/Cpu.hpp +++ b/kernel/src/Hal/Cpu.hpp @@ -12,6 +12,25 @@ namespace Hal { // Enable SSE/SSE2 — required for userspace programs compiled with SSE. // CR0: clear EM (bit 2), set MP (bit 1) // CR4: set OSFXSR (bit 9) and OSXMMEXCPT (bit 10) + // Check if MONITOR/MWAIT is supported (CPUID.01H:ECX bit 3) + inline bool HasMwait() { + uint32_t ecx; + asm volatile("cpuid" : "=c"(ecx) : "a"(1) : "ebx", "edx"); + return (ecx & (1 << 3)) != 0; + } + + // Idle using MWAIT if available, otherwise HLT. + // MWAIT can enter deeper C-states (C1E/C3/C6) for better + // power and thermal efficiency than HLT (C1 only). + // The monitored address is arbitrary -- we just need MONITOR + // to arm the wake trigger; any interrupt wakes MWAIT. + inline void IdleWait(volatile uint64_t* monitorAddr) { + // MONITOR: set up the address monitoring range + asm volatile("monitor" :: "a"(monitorAddr), "c"(0), "d"(0)); + // MWAIT: hint=0x00 (C1 state, platform-dependent deeper states) + asm volatile("mwait" :: "a"(0x00), "c"(0)); + } + inline void EnableSSE() { uint64_t cr0; asm volatile("mov %%cr0, %0" : "=r"(cr0)); diff --git a/kernel/src/Hal/IDT.cpp b/kernel/src/Hal/IDT.cpp index 4685135..44961ed 100644 --- a/kernel/src/Hal/IDT.cpp +++ b/kernel/src/Hal/IDT.cpp @@ -118,7 +118,7 @@ namespace Hal { return result; } - void IDTEncodeInterrupt(size_t i, void* handler, uint8_t type_attr) { + void IDTEncodeInterrupt(size_t i, void* handler, uint8_t type_attr, uint8_t ist) { uint64_t offset = (uint64_t)handler; auto ptr = GetInterruptDescriptor(i); @@ -126,7 +126,7 @@ namespace Hal { .Offset1 = (uint16_t)(offset & 0x000000000000ffff), .Selector = 0x08, - .IST = 0x00, + .IST = ist, .TypeAttributes = type_attr, .Offset2 = (uint16_t)((offset & 0x00000000ffff0000) >> 16), @@ -139,7 +139,10 @@ namespace Hal { template struct SetHandler { static void run() { - IDTEncodeInterrupt(I, (void*)ExceptionHandler, TrapGate); + // Use IST1 for NMI (2) and Double Fault (8) so they get a + // known-good stack even if the kernel stack has overflowed. + uint8_t ist = (I == 2 || I == 8) ? 1 : 0; + IDTEncodeInterrupt(I, (void*)ExceptionHandler, TrapGate, ist); SetHandler::run(); } }; diff --git a/kernel/src/Hal/IDT.hpp b/kernel/src/Hal/IDT.hpp index 6f2bbf2..5371217 100644 --- a/kernel/src/Hal/IDT.hpp +++ b/kernel/src/Hal/IDT.hpp @@ -25,6 +25,6 @@ namespace Hal { }__attribute__((packed)); void IDTInitialize(); - void IDTEncodeInterrupt(std::size_t i, void* handler, uint8_t type_attr); + void IDTEncodeInterrupt(std::size_t i, void* handler, uint8_t type_attr, uint8_t ist = 0); void IDTReload(); }; \ No newline at end of file diff --git a/kernel/src/Hal/SmpBoot.cpp b/kernel/src/Hal/SmpBoot.cpp index a657930..ad3625e 100644 --- a/kernel/src/Hal/SmpBoot.cpp +++ b/kernel/src/Hal/SmpBoot.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -60,6 +61,14 @@ namespace Smp { memset(&cpu.cpuTss, 0, sizeof(Hal::TSS64)); cpu.cpuTss.iopbOffset = sizeof(Hal::TSS64); + // Allocate a 4KB IST1 stack for Double Fault and NMI. + // These exceptions need a known-good stack to avoid triple + // faults when the normal kernel stack overflows. + void* istPage = Memory::g_pfa->AllocateZeroed(); + if (istPage) { + cpu.cpuTss.ist1 = (uint64_t)istPage + 0x1000; // top of stack + } + // Copy the standard GDT layout cpu.cpuGdt = { {0xFFFF, 0, 0, 0x00, 0x00, 0}, // 0x00 Null @@ -122,10 +131,17 @@ namespace Smp { bsp.lapicId = Hal::LocalApic::GetId(); bsp.currentSlot = -1; bsp.started = true; + bsp.hasMwait = Hal::HasMwait(); // BSP uses the global TSS (already set up in PrepareGDT) bsp.tss = &Hal::g_tss; + // Allocate IST1 stack for the BSP (for Double Fault / NMI) + void* bspIstPage = Memory::g_pfa->AllocateZeroed(); + if (bspIstPage) { + Hal::g_tss.ist1 = (uint64_t)bspIstPage + 0x1000; + } + // Set GS base for BSP SetGSBase(&bsp); @@ -187,14 +203,25 @@ namespace Smp { // --- Calibrate and start APIC timer --- Timekeeping::ApicTimerInitializeAP(); + // --- Check MWAIT support --- + cpu->hasMwait = Hal::HasMwait(); + // --- Signal that we are online --- cpu->started = true; // --- Enable interrupts and enter idle loop --- asm volatile("sti"); - for (;;) { - asm volatile("hlt"); + // Use MWAIT for deeper C-states if available, otherwise HLT. + static volatile uint64_t s_idleMonitor = 0; + if (cpu->hasMwait) { + for (;;) { + Hal::IdleWait(&s_idleMonitor); + } + } else { + for (;;) { + asm volatile("hlt"); + } } } diff --git a/kernel/src/Hal/SmpBoot.hpp b/kernel/src/Hal/SmpBoot.hpp index 661c0c6..f3b5591 100644 --- a/kernel/src/Hal/SmpBoot.hpp +++ b/kernel/src/Hal/SmpBoot.hpp @@ -37,6 +37,7 @@ namespace Smp { volatile bool started; // set by AP after init is complete Hal::TSS64* tss; // pointer to this CPU's TSS + bool hasMwait; // CPU supports MONITOR/MWAIT // Per-CPU GDT and TSS (APs use these; BSP uses globals) Hal::BasicGDT cpuGdt __attribute__((aligned(16))); diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index 81f7431..65e7739 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -241,8 +241,17 @@ extern "C" void kmain() { // Enable preemptive scheduling via the APIC timer Timekeeping::EnableSchedulerTick(); - // Main loop: halt until next interrupt - for (;;) { - asm volatile ("hlt"); + // Main loop: idle until next interrupt. + // Use MWAIT for deeper C-states if available, otherwise HLT. + auto* bspCpu = Smp::GetCpuData(0); + if (bspCpu && bspCpu->hasMwait) { + static volatile uint64_t s_bspIdleMonitor = 0; + for (;;) { + Hal::IdleWait(&s_bspIdleMonitor); + } + } else { + for (;;) { + asm volatile("hlt"); + } } } diff --git a/kernel/src/Memory/Heap.cpp b/kernel/src/Memory/Heap.cpp index 3c8345a..7ac29e2 100644 --- a/kernel/src/Memory/Heap.cpp +++ b/kernel/src/Memory/Heap.cpp @@ -56,11 +56,12 @@ namespace Memory void* HeapAllocator::Request(size_t size) { Lock.Acquire(); + size_t sizeNeeded = size + sizeof(Header); + + retry: Node* current = head.next; Node* prev = &head; - size_t sizeNeeded = size + sizeof(Header); - while (current != nullptr) { if (current->size >= sizeNeeded) { // Unlink the node @@ -89,13 +90,11 @@ namespace Memory current = current->next; } - Lock.Release(); - - // First pass allocation failed -- grow the heap + // No suitable block found -- grow the heap under the lock so + // InsertToFreelist is protected from concurrent modification. size_t pagesNeeded = (sizeNeeded + 0xFFF) / 0x1000; InsertPagesToFreelist(pagesNeeded); - - return Request(size); + goto retry; } void* HeapAllocator::Realloc(void* ptr, size_t size) { diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index 497ef48..3ab9cfc 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -37,6 +37,11 @@ namespace Sched { // The resumed process releases it. static kcp::Spinlock schedLock; + // Approximate count of Ready processes. Incremented/decremented + // under schedLock. Idle CPUs check this to avoid scanning all 256 + // process slots on every timer tick. + static volatile int readyCount = 0; + // The idle loop runs in the kernel PML4 static uint64_t GetKernelCR3() { return (uint64_t)Memory::VMM::g_paging->PML4; @@ -86,6 +91,7 @@ namespace Sched { processTable[i].heapNext = 0; processTable[i].args[0] = '\0'; processTable[i].runningOnCpu = -1; + processTable[i].killPending = false; processTable[i].waitingForPid = -1; processTable[i].sleepUntilTick = 0; processTable[i].redirected = false; @@ -248,6 +254,7 @@ namespace Sched { Process& proc = processTable[slot]; proc.pid = nextPid++; proc.state = ProcessState::Ready; + readyCount++; { int i = 0; for (; i < 63 && vfsPath[i]; i++) proc.name[i] = vfsPath[i]; @@ -262,6 +269,7 @@ namespace Sched { proc.userStackTop = UserStackTop - 8; proc.heapNext = UserHeapBase; proc.runningOnCpu = -1; + proc.killPending = false; proc.waitingForPid = -1; proc.sleepUntilTick = 0; @@ -361,6 +369,7 @@ namespace Sched { if (cpu->currentSlot >= 0) { int oldSlot = cpu->currentSlot; processTable[oldSlot].state = ProcessState::Ready; + readyCount++; processTable[oldSlot].runningOnCpu = -1; cpu->currentSlot = -1; @@ -394,6 +403,7 @@ namespace Sched { if (oldSlot >= 0) { processTable[oldSlot].state = ProcessState::Ready; + readyCount++; processTable[oldSlot].runningOnCpu = -1; oldRspPtr = &processTable[oldSlot].savedRsp; } else { @@ -402,6 +412,7 @@ namespace Sched { cpu->currentSlot = next; processTable[next].state = ProcessState::Running; + readyCount--; processTable[next].runningOnCpu = cpu->cpuIndex; processTable[next].sliceRemaining = TimeSliceMs; @@ -430,6 +441,7 @@ namespace Sched { // BSP: wake sleeping processes and reclaim terminated slots if (cpu->cpuIndex == 0) { + schedLock.Acquire(); uint64_t now = Timekeeping::GetTicks(); for (int i = 0; i < MaxProcesses; i++) { if (processTable[i].state == ProcessState::Blocked && @@ -437,8 +449,10 @@ namespace Sched { now >= processTable[i].sleepUntilTick) { processTable[i].sleepUntilTick = 0; processTable[i].state = ProcessState::Ready; + readyCount++; } } + schedLock.Release(); // Reclaim terminated process memory (BSP only, once per tick) ReclaimTerminated(); @@ -447,17 +461,21 @@ namespace Sched { int slot = cpu->currentSlot; if (slot < 0) { - // Idle CPU. Do a quick lockless scan before taking the - // expensive schedLock path. On a 32-core system with 5 - // active processes, 27 CPUs are idle -- without this check, - // they'd each acquire schedLock 1000x/sec to find nothing. - for (int i = 0; i < MaxProcesses; i++) { - if (processTable[i].state == ProcessState::Ready) { - Schedule(); - return; - } + // Idle CPU. Check the approximate ready count to avoid + // scanning 256 process slots on every tick. On a 32-core + // system with 27 idle CPUs, this avoids ~7M cache-line + // reads/sec from the process table. + if (readyCount > 0) { + Schedule(); } - // Nothing ready -- stay halted, don't touch schedLock + return; + } + + // Check if another CPU requested this process be killed. + // We are on the CPU running it, so ExitProcess is safe here. + if (processTable[slot].killPending) { + processTable[slot].killPending = false; + ExitProcess(); return; } @@ -492,9 +510,11 @@ namespace Sched { } Process& proc = processTable[slot]; + proc.killPending = false; + int exitingPid = proc.pid; // Clean up any windows owned by this process - WinServer::CleanupProcess(proc.pid); + WinServer::CleanupProcess(exitingPid); // Free I/O redirect buffers if (proc.outBuf) { @@ -511,7 +531,6 @@ namespace Sched { schedLock.Acquire(); - int exitingPid = proc.pid; proc.state = ProcessState::Terminated; proc.runningOnCpu = -1; @@ -520,6 +539,7 @@ namespace Sched { if (processTable[i].state == ProcessState::Blocked && processTable[i].waitingForPid == exitingPid) { processTable[i].state = ProcessState::Ready; + readyCount++; processTable[i].waitingForPid = -1; } } @@ -536,6 +556,7 @@ namespace Sched { if (next >= 0) { cpu->currentSlot = next; processTable[next].state = ProcessState::Running; + readyCount--; processTable[next].runningOnCpu = cpu->cpuIndex; processTable[next].sliceRemaining = TimeSliceMs; @@ -561,6 +582,81 @@ namespace Sched { } } + int KillProcess(int pid) { + // Refuse to kill PID 0 (init) or caller's own process + if (pid == 0) return -1; + if (pid == GetCurrentPid()) return -1; + + schedLock.Acquire(); + + // Find the process by PID + int slot = -1; + for (int i = 0; i < MaxProcesses; i++) { + if (processTable[i].pid == pid) { + auto s = processTable[i].state; + if (s == ProcessState::Ready || s == ProcessState::Running || + s == ProcessState::Blocked) { + slot = i; + } + break; + } + } + + if (slot < 0) { + schedLock.Release(); + return -1; + } + + Process& proc = processTable[slot]; + + if (proc.runningOnCpu >= 0) { + // Process is currently running on another CPU. We cannot + // safely free its resources (kernel stack, PML4, user pages) + // because that CPU is actively using them. Set a kill-pending + // flag; the target CPU's Tick() will call ExitProcess(). + proc.killPending = true; + schedLock.Release(); + return 0; + } + + // Process is Ready or Blocked (not running on any CPU). + // Mark it Terminated so the scheduler won't pick it up. + int killedPid = proc.pid; + if (proc.state == ProcessState::Ready) + readyCount--; + proc.state = ProcessState::Terminated; + proc.killPending = false; + + // Wake any processes blocked on this PID + for (int i = 0; i < MaxProcesses; i++) { + if (processTable[i].state == ProcessState::Blocked && + processTable[i].waitingForPid == killedPid) { + processTable[i].state = ProcessState::Ready; + readyCount++; + processTable[i].waitingForPid = -1; + } + } + + schedLock.Release(); + + // Safe to clean up resources now -- process is not running anywhere. + WinServer::CleanupProcess(killedPid); + + if (proc.outBuf) { + Memory::g_pfa->Free(proc.outBuf); + proc.outBuf = nullptr; + } + if (proc.inBuf) { + Memory::g_pfa->Free(proc.inBuf); + proc.inBuf = nullptr; + } + + Memory::VMM::Paging::FreeUserHalf(proc.pml4Phys); + + // Kernel stack and PML4 freed by ReclaimTerminated on BSP tick. + return 0; + } + void BlockOnPid(int pid) { // If the target is already dead, return immediately if (!IsAlive(pid)) return; @@ -607,6 +703,7 @@ namespace Sched { if (next >= 0) { cpu->currentSlot = next; processTable[next].state = ProcessState::Running; + readyCount--; processTable[next].runningOnCpu = cpu->cpuIndex; processTable[next].sliceRemaining = TimeSliceMs; @@ -651,6 +748,7 @@ namespace Sched { if (next >= 0) { cpu->currentSlot = next; processTable[next].state = ProcessState::Running; + readyCount--; processTable[next].runningOnCpu = cpu->cpuIndex; processTable[next].sliceRemaining = TimeSliceMs; diff --git a/kernel/src/Sched/Scheduler.hpp b/kernel/src/Sched/Scheduler.hpp index bae61d5..2849795 100644 --- a/kernel/src/Sched/Scheduler.hpp +++ b/kernel/src/Sched/Scheduler.hpp @@ -45,19 +45,20 @@ namespace Sched { char args[256]; // Command-line arguments (set by parent via Spawn) int runningOnCpu; // CPU index running this process (-1 if not running) + bool killPending = false; // Set by Sys_Kill when target is running on another CPU // I/O redirection for GUI terminal bool redirected = false; int parentPid = -1; uint8_t* outBuf = nullptr; // 4KB ring: child writes (print/putchar), parent reads - uint32_t outHead = 0; - uint32_t outTail = 0; + volatile uint32_t outHead = 0; + volatile uint32_t outTail = 0; uint8_t* inBuf = nullptr; // 4KB ring: parent writes, child reads (getchar) - uint32_t inHead = 0; - uint32_t inTail = 0; + volatile uint32_t inHead = 0; + volatile uint32_t inTail = 0; Montauk::KeyEvent keyBuf[64]; // parent injects, child reads (getkey/iskeyavailable) - uint32_t keyHead = 0; - uint32_t keyTail = 0; + volatile uint32_t keyHead = 0; + volatile uint32_t keyTail = 0; static constexpr uint32_t IoBufSize = 4096; // GUI terminal dimensions (set by desktop, read by SYS_TERMSIZE) @@ -93,6 +94,11 @@ namespace Sched { // Block the current process for the given number of milliseconds. void BlockForSleep(uint64_t ms); + // Kill a process by PID. If the process is running on another CPU, + // sets a kill-pending flag checked on the next timer tick. + // Returns 0 on success, -1 on failure. + int KillProcess(int pid); + // Find a process by PID (returns nullptr if not found or not alive) Process* GetProcessByPid(int pid); diff --git a/kernel/src/Timekeeping/ApicTimer.cpp b/kernel/src/Timekeeping/ApicTimer.cpp index 2003c1d..b056478 100644 --- a/kernel/src/Timekeeping/ApicTimer.cpp +++ b/kernel/src/Timekeeping/ApicTimer.cpp @@ -5,6 +5,7 @@ */ #include "ApicTimer.hpp" +#include #include #include #include @@ -36,7 +37,7 @@ namespace Timekeeping { static constexpr uint32_t TIMER_HZ = 1000; // Global state - static volatile uint64_t g_tickCount = 0; + static std::atomic g_tickCount{0}; static uint32_t g_ticksPerMs = 0; static bool g_schedEnabled = false; @@ -47,7 +48,7 @@ namespace Timekeeping { if (cpu->cpuIndex == 0) { // BSP: increment global tick count and poll devices - g_tickCount = g_tickCount + 1; + g_tickCount.fetch_add(1, std::memory_order_relaxed); Drivers::Net::E1000E::Poll(); Drivers::USB::Xhci::ProcessDeferredWork(); @@ -158,11 +159,11 @@ namespace Timekeeping { } uint64_t GetTicks() { - return g_tickCount; + return g_tickCount.load(std::memory_order_relaxed); } uint64_t GetMilliseconds() { - return g_tickCount; // 1 tick = 1 ms at 1000 Hz + return g_tickCount.load(std::memory_order_relaxed); // 1 tick = 1 ms at 1000 Hz } void EnableSchedulerTick() { @@ -183,13 +184,10 @@ namespace Timekeeping { } void Sleep(uint64_t ms) { - uint64_t target = g_tickCount + ms; - while (g_tickCount < target) { - // Yield to other processes instead of hlt. Using hlt here causes - // a deadlock: if the timer ISR preempts us during hlt and context- - // switches away (via Tick -> Schedule), EOI is never sent, so no - // more timer interrupts fire and any process doing hlt freezes. - Sched::Schedule(); - } + if (ms == 0) return; + // Use BlockForSleep to properly deschedule the process instead + // of busy-waiting with Schedule(). This frees the CPU for real + // work and avoids unnecessary scheduling overhead. + Sched::BlockForSleep(ms); } };