fix: improve scheduling, memory, timer implementations
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
@@ -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() {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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<int I, int N>
|
||||
struct SetHandler {
|
||||
static void run() {
|
||||
IDTEncodeInterrupt(I, (void*)ExceptionHandler<I>, 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<I>, TrapGate, ist);
|
||||
SetHandler<I+1,N>::run();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <Hal/MSR.hpp>
|
||||
#include <Hal/Cpu.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)));
|
||||
|
||||
+12
-3
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
+110
-12
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
*/
|
||||
|
||||
#include "ApicTimer.hpp"
|
||||
#include <atomic>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/Apic/Interrupts.hpp>
|
||||
#include <Hal/SmpBoot.hpp>
|
||||
@@ -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<uint64_t> 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);
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user