diff --git a/OVMF/OVMF_VARS_4M_d.fd b/OVMF/OVMF_VARS_4M_d.fd index 2e3b119..8d8bdfe 100644 Binary files a/OVMF/OVMF_VARS_4M_d.fd and b/OVMF/OVMF_VARS_4M_d.fd differ diff --git a/code/hal/hal_timer.c b/code/hal/hal_timer.c index 90482d2..2945b1d 100644 --- a/code/hal/hal_timer.c +++ b/code/hal/hal_timer.c @@ -1,218 +1,218 @@ -// hal_timer.c - Local APIC timer -#include -#include -#include -#include -#include - -#define LAPIC_TIMER_LVT 0x320 -#define LAPIC_TIMER_INIT 0x380 -#define LAPIC_TIMER_CUR 0x390 -#define LAPIC_TIMER_DIV 0x3E0 - -#define TIMER_VECTOR 0x20 // vector 0x20 = IRQ 0 slot in IDT -#define TIMER_PERIODIC (1 << 17) -#define TIMER_MASKED (1 << 16) -#define TIMER_DIV16 0x3 - -#define QUANTUM_MS 10 -#define PIT_HZ 1193182UL - -extern HAL_API gApi; -static volatile uint32_t* gLapic = NULL; -static uint32_t gApicTicksPerUs = 0; -static uint32_t gApicTicksPerQuantum = 0; -static uint64_t gHpetTicksPerUs = 0; -static uint64_t gHpetHz = 0; -static volatile uint64_t* gHpet = NULL; -#define LAPIC_REG(off) (*(volatile uint32_t *)((uint8_t *)gLapic + (off))) - -static volatile uint64_t gTicks = 0; -void (* volatile gTimerCallback)(void) = NULL; - -static inline void outb(uint16_t port, uint8_t val) { __outbyte(port, val); } -static inline uint8_t inb(uint16_t port) { return __inbyte(port); } - -static void TimerIsr(void) { - gTicks++; - if (gTimerCallback) gTimerCallback(); - // EOI is sent by KeIrqDispatch after this returns -} - -static uint64_t QueryTicks(void) { - // Prefer HPET when present: it is already a monotonically increasing main - // counter and avoids races between the LAPIC periodic tick and the current - // countdown register. - if (gHpet && gHpetHz) { - uint64_t counter = gHpet[30]; // offset 0xF0: main counter - uint64_t seconds = counter / gHpetHz; - uint64_t rem = counter % gHpetHz; - return seconds * 1000000ull + (rem * 1000000ull) / gHpetHz; - } - - // Fallback: combine the coarse periodic tick with the current LAPIC - // countdown. Read the coarse tick twice so an interrupt between gTicks and - // LAPIC_TIMER_CUR cannot make time jump backwards by almost one quantum. - uint64_t baseTicks1; - uint64_t baseTicks2; - uint32_t current; - do { - baseTicks1 = gTicks; - current = LAPIC_REG(LAPIC_TIMER_CUR); - baseTicks2 = gTicks; - } while (baseTicks1 != baseTicks2); - - uint32_t initial = gApicTicksPerQuantum; - - uint32_t elapsedApicTicks = 0; - if (current < initial) { - elapsedApicTicks = initial - current; - } - - uint64_t subQuantumUs = 0; - if (gApicTicksPerUs) - subQuantumUs = elapsedApicTicks / gApicTicksPerUs; - - return (baseTicks1 * QUANTUM_MS * 1000ull) + subQuantumUs; -} - -static void Stall(uint32_t us) { - // Priority 1: Use HPET (Highest Precision) - if (gHpet && gHpetTicksPerUs > 0) { - uint64_t start = gHpet[30]; // Offset 0xF0 is the Main Counter - uint64_t delta = (uint64_t)us * gHpetTicksPerUs; - while ((gHpet[30] - start) < delta) { - _mm_pause(); // Hint to CPU we are in a spin-loop - } - return; - } - - // Priority 2: Use APIC Timer (High Precision, but handles wrap-around) - if (gApicTicksPerUs > 0) { - uint32_t total_ticks = us * gApicTicksPerUs; - uint32_t last_val = LAPIC_REG(LAPIC_TIMER_CUR); - uint32_t elapsed = 0; - - while (elapsed < total_ticks) { - uint32_t current = LAPIC_REG(LAPIC_TIMER_CUR); - if (current < last_val) { - elapsed += (last_val - current); - } else if (current > last_val) { - // Handled the reset/wrap at the end of QUANTUM_MS - uint32_t init_val = LAPIC_REG(LAPIC_TIMER_INIT); - elapsed += (last_val + (init_val - current)); - } - last_val = current; - _mm_pause(); - } - return; - } - - // Priority 3: Fallback to Port 0x80 - for (uint32_t i = 0; i < us; i++) { - __outbyte(0x80, 0); - } -} - -// Returns HPET ticks/second, or 0 if HPET is unavailable. -static uint64_t HpetInit(uintptr_t hpetPhys) { - // HPET register block, accessed through physical window - volatile uint64_t *hpet = (volatile uint64_t *)gApi.PhysToVirt(hpetPhys); - - // General Capabilities register (offset 0): - // Bits 63:32 = counter clock period in femtoseconds - uint64_t caps = hpet[0]; - uint32_t periodFs = (uint32_t)(caps >> 32); - if (!periodFs) return 0; - - // Enable the HPET main counter (General Configuration, offset 0x10, bit 0) - hpet[2] |= 1; // offset 0x10 / 8 = index 2 - - return (uint64_t)1e15 / periodFs; // ticks per second -} - -// Use PIT channel 2 one-shot to measure how many APIC ticks elapse in 10 ms, then return ticks/ms. -static uint32_t CalibrateApicTimerViaPit(void) { - // Divide by 16 - LAPIC_REG(LAPIC_TIMER_DIV) = TIMER_DIV16; - // Mask, set vector - leave masked during calibration - LAPIC_REG(LAPIC_TIMER_LVT) = TIMER_MASKED | TIMER_VECTOR; - // Load max count - LAPIC_REG(LAPIC_TIMER_INIT) = 0xFFFFFFFF; - - // PIT channel 2: one-shot, ~10 ms - uint16_t pit_count = (uint16_t)(PIT_HZ / 100); // 11931 ≈ 10 ms - outb(0x61, (inb(0x61) & ~0x02) | 0x01); // gate on, speaker off - outb(0x43, 0xB0); // ch2, lo/hi, mode 0, binary - outb(0x42, (uint8_t)(pit_count & 0xFF)); - outb(0x42, (uint8_t)(pit_count >> 8)); - - // Wait for OUT2 (bit 5 of port 0x61) to go high - while (!(inb(0x61) & 0x20)) _mm_pause(); - - uint32_t elapsed = 0xFFFFFFFF - LAPIC_REG(LAPIC_TIMER_CUR); - return elapsed / 10; // ticks per ms -} - -// Use HPET main counter to measure how many APIC ticks elapse in 10 ms, then return ticks/ms. -static uint32_t CalibrateApicTimerViaHpet(uintptr_t hpetPhys) { - volatile uint64_t *hpet = (volatile uint64_t *)gApi.PhysToVirt(hpetPhys); - uint64_t hpetHz = HpetInit(hpetPhys); - if (!hpetHz) return 0; - - // Ticks in 10 ms = hpetHz / 100 - uint64_t ticksFor10ms = hpetHz / 100; - - LAPIC_REG(LAPIC_TIMER_DIV) = TIMER_DIV16; - LAPIC_REG(LAPIC_TIMER_LVT) = TIMER_MASKED | TIMER_VECTOR; - LAPIC_REG(LAPIC_TIMER_INIT) = 0xFFFFFFFF; - - // HPET main counter is at offset 0xF0, index 0xF0/8 = 30 - uint64_t hpetStart = hpet[30]; - while ((hpet[30] - hpetStart) < ticksFor10ms) _mm_pause(); - - uint32_t elapsed = 0xFFFFFFFF - LAPIC_REG(LAPIC_TIMER_CUR); - return elapsed / 10; // APIC ticks per ms -} - -KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *out) { - gLapic = (volatile uint32_t *)gApi.PhysToVirt(lapicPhys); - HalDbgPrint("HAL: lapicPhys=0x%llX, lapicVirt=0x%llX, hpetPhys=0x%llX\n", lapicPhys, (uintptr_t)gLapic, hpetPhys); - - uint32_t ticksPerMs = 0; - if (hpetPhys) { - HalDbgPrint("HAL: Attempting to calibrate APIC timer via HPET...\n"); - ticksPerMs = CalibrateApicTimerViaHpet(hpetPhys); - } - if (!ticksPerMs) { - HalDbgPrint("HAL: HPET unavailable or calibration failed. Falling back to PIT...\n"); - ticksPerMs = CalibrateApicTimerViaPit(); - } - if (!ticksPerMs) { - HalDbgPrint("HAL: APIC calibration failed. How?\n"); - return STATUS_DEVICE_ERROR; - } - - gApicTicksPerUs = ticksPerMs / 1000; - gApicTicksPerQuantum = ticksPerMs * QUANTUM_MS; - - if (hpetPhys) { - gHpet = (volatile uint64_t*)gApi.PhysToVirt(hpetPhys); - uint64_t hpetHz = HpetInit(hpetPhys); - gHpetHz = hpetHz; - gHpetTicksPerUs = hpetHz / 1000000; - } - - HalDbgPrint("HAL: APIC timer: %u ticks/ms\n", ticksPerMs); - - // Configure LAPIC timer in periodic mode - LAPIC_REG(LAPIC_TIMER_DIV) = TIMER_DIV16; - LAPIC_REG(LAPIC_TIMER_LVT) = TIMER_PERIODIC | TIMER_VECTOR; - LAPIC_REG(LAPIC_TIMER_INIT) = gApicTicksPerQuantum; - - out->QueryTicks = QueryTicks; - out->Stall = Stall; - out->Isr = TimerIsr; - out->IrqNumber = 0; - return STATUS_SUCCESS; -} +// hal_timer.c - Local APIC timer +#include +#include +#include +#include +#include + +#define LAPIC_TIMER_LVT 0x320 +#define LAPIC_TIMER_INIT 0x380 +#define LAPIC_TIMER_CUR 0x390 +#define LAPIC_TIMER_DIV 0x3E0 + +#define TIMER_VECTOR 0x20 // vector 0x20 = IRQ 0 slot in IDT +#define TIMER_PERIODIC (1 << 17) +#define TIMER_MASKED (1 << 16) +#define TIMER_DIV16 0x3 + +#define TIMER_PERIOD_MS 10 +#define PIT_HZ 1193182UL + +extern HAL_API gApi; +static volatile uint32_t* gLapic = NULL; +static uint32_t gApicTicksPerUs = 0; +static uint32_t gApicTicksPerQuantum = 0; +static uint64_t gHpetTicksPerUs = 0; +static uint64_t gHpetHz = 0; +static volatile uint64_t* gHpet = NULL; +#define LAPIC_REG(off) (*(volatile uint32_t *)((uint8_t *)gLapic + (off))) + +static volatile uint64_t gTicks = 0; +void (* volatile gTimerCallback)(void) = NULL; + +static inline void outb(uint16_t port, uint8_t val) { __outbyte(port, val); } +static inline uint8_t inb(uint16_t port) { return __inbyte(port); } + +static void TimerIsr(void) { + gTicks++; + if (gTimerCallback) gTimerCallback(); + // EOI is sent by KeIrqDispatch after this returns +} + +static uint64_t QueryTicks(void) { + // Prefer HPET when present: it is already a monotonically increasing main + // counter and avoids races between the LAPIC periodic tick and the current + // countdown register. + if (gHpet && gHpetHz) { + uint64_t counter = gHpet[30]; // offset 0xF0: main counter + uint64_t seconds = counter / gHpetHz; + uint64_t rem = counter % gHpetHz; + return seconds * 1000000ull + (rem * 1000000ull) / gHpetHz; + } + + // Fallback: combine the coarse periodic tick with the current LAPIC + // countdown. Read the coarse tick twice so an interrupt between gTicks and + // LAPIC_TIMER_CUR cannot make time jump backwards by almost one quantum. + uint64_t baseTicks1; + uint64_t baseTicks2; + uint32_t current; + do { + baseTicks1 = gTicks; + current = LAPIC_REG(LAPIC_TIMER_CUR); + baseTicks2 = gTicks; + } while (baseTicks1 != baseTicks2); + + uint32_t initial = gApicTicksPerQuantum; + + uint32_t elapsedApicTicks = 0; + if (current < initial) { + elapsedApicTicks = initial - current; + } + + uint64_t subQuantumUs = 0; + if (gApicTicksPerUs) + subQuantumUs = elapsedApicTicks / gApicTicksPerUs; + + return (baseTicks1 * TIMER_PERIOD_MS * 1000ull) + subQuantumUs; +} + +static void Stall(uint32_t us) { + // Priority 1: Use HPET (Highest Precision) + if (gHpet && gHpetTicksPerUs > 0) { + uint64_t start = gHpet[30]; // Offset 0xF0 is the Main Counter + uint64_t delta = (uint64_t)us * gHpetTicksPerUs; + while ((gHpet[30] - start) < delta) { + _mm_pause(); // Hint to CPU we are in a spin-loop + } + return; + } + + // Priority 2: Use APIC Timer (High Precision, but handles wrap-around) + if (gApicTicksPerUs > 0) { + uint32_t total_ticks = us * gApicTicksPerUs; + uint32_t last_val = LAPIC_REG(LAPIC_TIMER_CUR); + uint32_t elapsed = 0; + + while (elapsed < total_ticks) { + uint32_t current = LAPIC_REG(LAPIC_TIMER_CUR); + if (current < last_val) { + elapsed += (last_val - current); + } else if (current > last_val) { + // Handled the reset/wrap at the end of TIMER_PERIOD_MS + uint32_t init_val = LAPIC_REG(LAPIC_TIMER_INIT); + elapsed += (last_val + (init_val - current)); + } + last_val = current; + _mm_pause(); + } + return; + } + + // Priority 3: Fallback to Port 0x80 + for (uint32_t i = 0; i < us; i++) { + __outbyte(0x80, 0); + } +} + +// Returns HPET ticks/second, or 0 if HPET is unavailable. +static uint64_t HpetInit(uintptr_t hpetPhys) { + // HPET register block, accessed through physical window + volatile uint64_t *hpet = (volatile uint64_t *)gApi.PhysToVirt(hpetPhys); + + // General Capabilities register (offset 0): + // Bits 63:32 = counter clock period in femtoseconds + uint64_t caps = hpet[0]; + uint32_t periodFs = (uint32_t)(caps >> 32); + if (!periodFs) return 0; + + // Enable the HPET main counter (General Configuration, offset 0x10, bit 0) + hpet[2] |= 1; // offset 0x10 / 8 = index 2 + + return (uint64_t)1e15 / periodFs; // ticks per second +} + +// Use PIT channel 2 one-shot to measure how many APIC ticks elapse in 10 ms, then return ticks/ms. +static uint32_t CalibrateApicTimerViaPit(void) { + // Divide by 16 + LAPIC_REG(LAPIC_TIMER_DIV) = TIMER_DIV16; + // Mask, set vector - leave masked during calibration + LAPIC_REG(LAPIC_TIMER_LVT) = TIMER_MASKED | TIMER_VECTOR; + // Load max count + LAPIC_REG(LAPIC_TIMER_INIT) = 0xFFFFFFFF; + + // PIT channel 2: one-shot, ~10 ms + uint16_t pit_count = (uint16_t)(PIT_HZ / 100); // 11931 ≈ 10 ms + outb(0x61, (inb(0x61) & ~0x02) | 0x01); // gate on, speaker off + outb(0x43, 0xB0); // ch2, lo/hi, mode 0, binary + outb(0x42, (uint8_t)(pit_count & 0xFF)); + outb(0x42, (uint8_t)(pit_count >> 8)); + + // Wait for OUT2 (bit 5 of port 0x61) to go high + while (!(inb(0x61) & 0x20)) _mm_pause(); + + uint32_t elapsed = 0xFFFFFFFF - LAPIC_REG(LAPIC_TIMER_CUR); + return elapsed / 10; // ticks per ms +} + +// Use HPET main counter to measure how many APIC ticks elapse in 10 ms, then return ticks/ms. +static uint32_t CalibrateApicTimerViaHpet(uintptr_t hpetPhys) { + volatile uint64_t *hpet = (volatile uint64_t *)gApi.PhysToVirt(hpetPhys); + uint64_t hpetHz = HpetInit(hpetPhys); + if (!hpetHz) return 0; + + // Ticks in 10 ms = hpetHz / 100 + uint64_t ticksFor10ms = hpetHz / 100; + + LAPIC_REG(LAPIC_TIMER_DIV) = TIMER_DIV16; + LAPIC_REG(LAPIC_TIMER_LVT) = TIMER_MASKED | TIMER_VECTOR; + LAPIC_REG(LAPIC_TIMER_INIT) = 0xFFFFFFFF; + + // HPET main counter is at offset 0xF0, index 0xF0/8 = 30 + uint64_t hpetStart = hpet[30]; + while ((hpet[30] - hpetStart) < ticksFor10ms) _mm_pause(); + + uint32_t elapsed = 0xFFFFFFFF - LAPIC_REG(LAPIC_TIMER_CUR); + return elapsed / 10; // APIC ticks per ms +} + +KSTATUS HalTimerInitialize(uintptr_t lapicPhys, uintptr_t hpetPhys, HAL_TIMER *out) { + gLapic = (volatile uint32_t *)gApi.PhysToVirt(lapicPhys); + HalDbgPrint("HAL: lapicPhys=0x%llX, lapicVirt=0x%llX, hpetPhys=0x%llX\n", lapicPhys, (uintptr_t)gLapic, hpetPhys); + + uint32_t ticksPerMs = 0; + if (hpetPhys) { + HalDbgPrint("HAL: Attempting to calibrate APIC timer via HPET...\n"); + ticksPerMs = CalibrateApicTimerViaHpet(hpetPhys); + } + if (!ticksPerMs) { + HalDbgPrint("HAL: HPET unavailable or calibration failed. Falling back to PIT...\n"); + ticksPerMs = CalibrateApicTimerViaPit(); + } + if (!ticksPerMs) { + HalDbgPrint("HAL: APIC calibration failed. How?\n"); + return STATUS_DEVICE_ERROR; + } + + gApicTicksPerUs = ticksPerMs / 1000; + gApicTicksPerQuantum = ticksPerMs * TIMER_PERIOD_MS; + + if (hpetPhys) { + gHpet = (volatile uint64_t*)gApi.PhysToVirt(hpetPhys); + uint64_t hpetHz = HpetInit(hpetPhys); + gHpetHz = hpetHz; + gHpetTicksPerUs = hpetHz / 1000000; + } + + HalDbgPrint("HAL: APIC timer: %u ticks/ms\n", ticksPerMs); + + // Configure LAPIC timer in periodic mode + LAPIC_REG(LAPIC_TIMER_DIV) = TIMER_DIV16; + LAPIC_REG(LAPIC_TIMER_LVT) = TIMER_PERIODIC | TIMER_VECTOR; + LAPIC_REG(LAPIC_TIMER_INIT) = gApicTicksPerQuantum; + + out->QueryTicks = QueryTicks; + out->Stall = Stall; + out->Isr = TimerIsr; + out->IrqNumber = 0; + return STATUS_SUCCESS; +} diff --git a/code/kernel/ke_sched.c b/code/kernel/ke_sched.c index a334060..2fb5d07 100644 --- a/code/kernel/ke_sched.c +++ b/code/kernel/ke_sched.c @@ -10,8 +10,6 @@ #include #include -#define DEMOTION_QUANTA 4 /* ticks at a level before demotion */ - static LIST_ENTRY gRunQueue[PriorityCount]; static SPINLOCK gSchedLock; @@ -22,6 +20,39 @@ static uint32_t gAgingCounter = 0; static volatile long gReschedulePending = 0; static KE_SCHED_STATS gSchedStats; +static uint32_t KiBaseQuantumForPriority(THREAD_PRIORITY priority) { + switch (priority) { + case PriorityHigh: return SCHED_HIGH_QUANTUM_TICKS; + case PriorityNormal: return SCHED_NORMAL_QUANTUM_TICKS; + case PriorityLow: return SCHED_LOW_QUANTUM_TICKS; + default: return SCHED_NORMAL_QUANTUM_TICKS; + } +} + +static uint32_t KiComputeQuantumLocked(THREAD* thread) { + if (!thread || thread == gIdleThread) + return 1; + + uint32_t quantum = KiBaseQuantumForPriority(thread->Priority); + + // Low-priority CPU-bound threads get longer batches once they have proven + // they mostly burn CPU. Higher-priority work still preempts them as soon + // as it becomes ready, so this reduces switch overhead without hurting + // interactive wake latency. + if (thread->Priority == PriorityLow && thread->CpuBurstTicks >= + (SCHED_DEMOTE_AFTER_BURST_TICKS * 2u)) { + quantum += 4; + } + + return quantum ? quantum : 1; +} + +static void KiRefillQuantumLocked(THREAD* thread) { + if (!thread) return; + thread->QuantumTicks = KiComputeQuantumLocked(thread); + thread->QuantumRemaining = thread->QuantumTicks; +} + THREAD* KeGetCurrentThread(void) { return gCurrentThread; } void KeSetCurrentThread(THREAD* t) { gCurrentThread = t; } @@ -55,6 +86,10 @@ void KeInitializeScheduler(void) { idle->State = ThreadReady; idle->Priority = PriorityLow; idle->QuantaConsumed = 0; + idle->QuantumTicks = 1; + idle->QuantumRemaining = 1; + idle->CpuBurstTicks = 0; + idle->LastRunTicks = 0; idle->Owner = idleProc; idle->WaitObject = (OBJECT *)0; idle->WaitStatus = STATUS_SUCCESS; @@ -119,6 +154,15 @@ void KeReadyThread(THREAD* thread) { uintptr_t flags = KeAcquireSpinlock(&gSchedLock); ListInsertTail(&gRunQueue[p], &thread->SchedulerLink); + + THREAD* cur = gCurrentThread; + if (!cur || cur == gIdleThread) { + KiRequestReschedule(); + } else if (cur->State == ThreadRunning && thread->Priority > cur->Priority) { + gSchedStats.PriorityPreemptions++; + KiRequestReschedule(); + } + KeReleaseSpinlock(&gSchedLock, flags); } @@ -170,6 +214,8 @@ static void KiSchedule(void) { // lone CPU-bound thread gets alternated with the idle thread on every timer // tick, effectively turning a periodic preemption check into a 50% throttle. if (cur != gIdleThread && cur->State == ThreadRunning && !KiAnyRunnableLocked()) { + if (cur->QuantumRemaining == 0) + KiRefillQuantumLocked(cur); gSchedStats.KeepCurrentDecisions++; KeReleaseSpinlock(&gSchedLock, flags); return; @@ -188,6 +234,7 @@ static void KiSchedule(void) { } next->State = ThreadRunning; + KiRefillQuantumLocked(next); gCurrentThread = next; // Only re-queue the outgoing thread if it is still in the running state. @@ -211,33 +258,76 @@ void KiScheduleFromInterrupt(void) { KiSchedule(); } +void KiSchedulerNoteVoluntaryBlock(THREAD* thread) { + if (!thread) return; + uintptr_t flags = KeAcquireSpinlock(&gSchedLock); + gSchedStats.VoluntaryBlocks++; + thread->QuantumRemaining = 0; + KeReleaseSpinlock(&gSchedLock, flags); +} + void KeSchedulerTick(void) { THREAD* cur = gCurrentThread; if (!cur) return; uintptr_t flags = KeAcquireSpinlock(&gSchedLock); - cur->SchedLockFlags = flags; // Issue 5 fix + cur->SchedLockFlags = flags; gSchedStats.Ticks++; - cur->QuantaConsumed++; - if (cur != gIdleThread && cur->QuantaConsumed >= DEMOTION_QUANTA) { - cur->QuantaConsumed = 0; - if (cur->Priority > PriorityLow) { - cur->Priority = (THREAD_PRIORITY)(cur->Priority - 1); - gSchedStats.Demotions++; + int requestReschedule = 0; + + if (cur == gIdleThread) { + if (KiAnyRunnableLocked()) + requestReschedule = 1; + } else { + cur->CpuBurstTicks++; + cur->QuantaConsumed = cur->CpuBurstTicks; + + if (cur->CpuBurstTicks >= SCHED_DEMOTE_AFTER_BURST_TICKS) { + cur->CpuBurstTicks = 0; + cur->QuantaConsumed = 0; + if (cur->Priority > PriorityLow) { + cur->Priority = (THREAD_PRIORITY)(cur->Priority - 1); + gSchedStats.Demotions++; + gSchedStats.CpuBurstDemotions++; + } + } + + if (cur->QuantumRemaining == 0) + KiRefillQuantumLocked(cur); + + if (cur->QuantumRemaining > 0) + cur->QuantumRemaining--; + + if (cur->QuantumRemaining == 0) { + if (KiAnyRunnableLocked()) { + gSchedStats.QuantumExpirations++; + requestReschedule = 1; + } else { + // No peer is ready, so keep the CPU hot and refill the budget. + // This avoids reintroducing the old every-other-tick idle dance + // as a pointless reschedule-pending chatter loop. + KiRefillQuantumLocked(cur); + gSchedStats.KeepCurrentDecisions++; + } } } - // Periodic aging pass to prevent indefinite starvation of PriorityLow threads + // Periodic aging pass to prevent indefinite starvation of PriorityLow and + // PriorityNormal threads. Ready threads promoted by aging get a fresh CPU + // burst history; they earned the boost by waiting, not by burning CPU. gAgingCounter++; - if (gAgingCounter >= 100) { + if (gAgingCounter >= SCHED_AGING_INTERVAL_TICKS) { gAgingCounter = 0; for (int p = 0; p < PriorityHigh; p++) { while (!LIST_IS_EMPTY(&gRunQueue[p])) { LIST_ENTRY* node = ListPopHead(&gRunQueue[p]); THREAD* t = CONTAINING_RECORD(node, THREAD, SchedulerLink); t->Priority = PriorityHigh; + t->CpuBurstTicks = 0; + t->QuantaConsumed = 0; + t->QuantumRemaining = 0; gSchedStats.AgingPromotions++; ListInsertTail(&gRunQueue[PriorityHigh], &t->SchedulerLink); } @@ -246,7 +336,8 @@ void KeSchedulerTick(void) { KeReleaseSpinlock(&gSchedLock, flags); - KiRequestReschedule(); + if (requestReschedule) + KiRequestReschedule(); } __declspec(dllexport) void KeYield(void) { diff --git a/code/kernel/ke_sched.h b/code/kernel/ke_sched.h index e3ef0bb..b23a0a1 100644 --- a/code/kernel/ke_sched.h +++ b/code/kernel/ke_sched.h @@ -3,7 +3,12 @@ // Three run queues (Low / Normal / High). IRQ 0 drives KeSchedulerTick. #include -#define QUANTUM_MS 10 // base time slice in milliseconds +#define SCHED_TICK_MS 10u +#define SCHED_HIGH_QUANTUM_TICKS 2u +#define SCHED_NORMAL_QUANTUM_TICKS 4u +#define SCHED_LOW_QUANTUM_TICKS 8u +#define SCHED_DEMOTE_AFTER_BURST_TICKS 16u +#define SCHED_AGING_INTERVAL_TICKS 100u typedef struct KE_SCHED_STATS { uint64_t Ticks; @@ -12,6 +17,10 @@ typedef struct KE_SCHED_STATS { uint64_t IdleSelections; uint64_t Demotions; uint64_t AgingPromotions; + uint64_t QuantumExpirations; + uint64_t PriorityPreemptions; + uint64_t VoluntaryBlocks; + uint64_t CpuBurstDemotions; } KE_SCHED_STATS; // Called once by KernelMain before any threads are created. @@ -21,8 +30,10 @@ void KeInitializeScheduler(void); void KeReadyThread(THREAD* thread); void KeUnreadyThread(THREAD* thread); -// Called from the timer ISR every QUANTUM_MS. This only performs timer -// accounting and requests a reschedule; the actual context switch happens from +// Called from the timer ISR every SCHED_TICK_MS. This performs timer +// accounting and requests a reschedule only when the current thread has +// exhausted its scheduler budget, or when idle work becomes available. The +// actual context switch happens from // KiExitInterrupt after the IRQ handler has returned and EOI has been sent. void KeSchedulerTick(void); @@ -32,6 +43,7 @@ void KeSchedulerTick(void); void KiRequestReschedule(void); int KiIsReschedulePending(void); void KiScheduleFromInterrupt(void); +void KiSchedulerNoteVoluntaryBlock(THREAD* thread); // Voluntarily yield the CPU. Picks the next runnable thread immediately. __declspec(dllexport) void KeYield(void); diff --git a/code/kernel/ke_thread.c b/code/kernel/ke_thread.c index 78f62f3..56dd527 100644 --- a/code/kernel/ke_thread.c +++ b/code/kernel/ke_thread.c @@ -87,6 +87,10 @@ __declspec(dllexport) KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, ui thread->State = ThreadReady; thread->Priority = priority; thread->QuantaConsumed = 0; + thread->QuantumTicks = 0; + thread->QuantumRemaining = 0; + thread->CpuBurstTicks = 0; + thread->LastRunTicks = 0; thread->WaitObject = (OBJECT *)0; thread->WaitStatus = STATUS_SUCCESS; thread->WaitListHead = (LIST_ENTRY*)0; @@ -241,6 +245,10 @@ __declspec(dllexport) KSTATUS KeCreateKernelThread(uintptr_t fn, uintptr_t arg, thread->State = ThreadReady; thread->Priority = priority; thread->QuantaConsumed = 0; + thread->QuantumTicks = 0; + thread->QuantumRemaining = 0; + thread->CpuBurstTicks = 0; + thread->LastRunTicks = 0; thread->WaitObject = (OBJECT*)0; thread->WaitStatus = STATUS_SUCCESS; thread->WaitListHead = (LIST_ENTRY*)0; @@ -344,6 +352,7 @@ KSTATUS KeCommitBlockThread(THREAD* thread, uint8_t alertable) { return STATUS_USER_APC; } + KiSchedulerNoteVoluntaryBlock(thread); KeYield(); // Kernel APCs are delivered before an alertable wait returns @@ -369,6 +378,9 @@ void KeUnblockThreadWithStatus(THREAD* thread, KSTATUS status) { // instead of inserting the running thread into the ready queue. if (thread == KeGetCurrentThread()) { KiFinishWait(thread, status); + thread->CpuBurstTicks = 0; + thread->QuantaConsumed = 0; + thread->QuantumRemaining = 0; if (thread->State == ThreadWaiting) thread->State = ThreadRunning; return; @@ -385,9 +397,12 @@ void KeUnblockThreadWithStatus(THREAD* thread, KSTATUS status) { KiFinishWait(thread, status); thread->State = ThreadReady; + thread->CpuBurstTicks = 0; + thread->QuantaConsumed = 0; + thread->QuantumRemaining = 0; + if (thread->Priority < PriorityHigh) { thread->Priority = (THREAD_PRIORITY)(thread->Priority + 1); - thread->QuantaConsumed = 0; } KeReadyThread(thread); diff --git a/code/kernel/ke_thread.h b/code/kernel/ke_thread.h index ad1adcc..0f82e4b 100644 --- a/code/kernel/ke_thread.h +++ b/code/kernel/ke_thread.h @@ -41,7 +41,8 @@ typedef struct THREAD { THREAD_STATE State; THREAD_PRIORITY Priority; - // Quanta consumed without blocking - used by the MLFQ for demotion. + // CPU-burst mirror kept here for the assembly-stable THREAD layout. + // Dynamic quantum state lives later in the structure. uint32_t QuantaConsumed; PROCESS* Owner; @@ -80,6 +81,14 @@ typedef struct THREAD { void* FpuContext; uint8_t FpuStorage[528]; + // Scheduler accounting. QuantaConsumed is kept in the assembly-stable + // portion of THREAD for layout compatibility; the dynamic scheduler uses + // these fields for per-dispatch budget and CPU-burst tracking. + uint32_t QuantumTicks; + uint32_t QuantumRemaining; + uint32_t CpuBurstTicks; + uint32_t LastRunTicks; + // Set by wait primitives before blocking and completed by the object // that wakes the thread. Keep this field after the assembly-visible // context-switch fields: ke_switch.asm hard-codes offsets through