better scheduler time allocation, proper quota tracking
This commit is contained in:
1 parent
03bf767ac2
commit
b3569a2b90
6 files changed
+362
-235
No files matched your search
Binary file not shown.
+218
-218
@@ -1,218 +1,218 @@
|
||||
// hal_timer.c - Local APIC timer
|
||||
#include <stdint.h>
|
||||
#include <intrin.h>
|
||||
#include <hal_api.h>
|
||||
#include <hal_apic.h>
|
||||
#include <hal_debug.h>
|
||||
|
||||
#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 <stdint.h>
|
||||
#include <intrin.h>
|
||||
#include <hal_api.h>
|
||||
#include <hal_apic.h>
|
||||
#include <hal_debug.h>
|
||||
|
||||
#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;
|
||||
}
|
||||
+103
-12
@@ -10,8 +10,6 @@
|
||||
#include <ke_list.h>
|
||||
#include <mm_virt.h>
|
||||
|
||||
#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) {
|
||||
|
||||
+15
-3
@@ -3,7 +3,12 @@
|
||||
// Three run queues (Low / Normal / High). IRQ 0 drives KeSchedulerTick.
|
||||
#include <ke_thread.h>
|
||||
|
||||
#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);
|
||||
|
||||
+16
-1
@@ -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);
|
||||
|
||||
+10
-1
@@ -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
|
||||
|
||||
Reference in new issue
Block a user