feat: scheduling, usermode, shell
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
;
|
||||
; Context.asm
|
||||
; Context switch: save/restore callee-saved registers, stack pointer, and CR3
|
||||
; Copyright (c) 2025 Daniel Hammer
|
||||
;
|
||||
|
||||
[bits 64]
|
||||
section .text
|
||||
|
||||
; void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3)
|
||||
; rdi = pointer to save old RSP
|
||||
; rsi = new RSP to restore
|
||||
; rdx = new PML4 physical address (for CR3)
|
||||
global SchedContextSwitch
|
||||
SchedContextSwitch:
|
||||
; Save callee-saved registers on the current stack
|
||||
push rbp
|
||||
push rbx
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
|
||||
; Save current RSP into *oldRsp
|
||||
mov [rdi], rsp
|
||||
|
||||
; Load new RSP
|
||||
mov rsp, rsi
|
||||
|
||||
; Switch address space if CR3 differs (avoid unnecessary TLB flush)
|
||||
mov rax, cr3
|
||||
cmp rax, rdx
|
||||
je .skip_cr3
|
||||
mov cr3, rdx
|
||||
.skip_cr3:
|
||||
|
||||
; Restore callee-saved registers from the new stack
|
||||
pop r15
|
||||
pop r14
|
||||
pop r13
|
||||
pop r12
|
||||
pop rbx
|
||||
pop rbp
|
||||
|
||||
ret
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* ElfLoader.cpp
|
||||
* ELF64 binary loader for user-mode processes
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "ElfLoader.hpp"
|
||||
#include <Fs/Vfs.hpp>
|
||||
#include <Memory/Heap.hpp>
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
|
||||
namespace Sched {
|
||||
|
||||
static bool ValidateElfHeader(const Elf64Header* hdr) {
|
||||
// Check ELF magic: 0x7f 'E' 'L' 'F'
|
||||
if (hdr->e_ident[0] != 0x7f ||
|
||||
hdr->e_ident[1] != 'E' ||
|
||||
hdr->e_ident[2] != 'L' ||
|
||||
hdr->e_ident[3] != 'F') {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Invalid ELF magic";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Class must be ELFCLASS64 (2)
|
||||
if (hdr->e_ident[4] != 2) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Not a 64-bit ELF";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Data encoding must be ELFDATA2LSB (1) - little endian
|
||||
if (hdr->e_ident[5] != 1) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Not little-endian";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hdr->e_type != ET_EXEC) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Not an executable (type=" << (uint64_t)hdr->e_type << ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hdr->e_machine != EM_X86_64) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Not x86_64 (machine=" << (uint64_t)hdr->e_machine << ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys) {
|
||||
Kt::KernelLogStream(Kt::INFO, "ELF") << "Loading " << vfsPath;
|
||||
|
||||
int handle = Fs::Vfs::VfsOpen(vfsPath);
|
||||
if (handle < 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to open " << vfsPath;
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t fileSize = Fs::Vfs::VfsGetSize(handle);
|
||||
if (fileSize < sizeof(Elf64Header)) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "File too small (" << fileSize << " bytes)";
|
||||
Fs::Vfs::VfsClose(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Read entire file into a heap buffer
|
||||
uint8_t* fileData = (uint8_t*)Memory::g_heap->Request(fileSize);
|
||||
if (fileData == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Failed to allocate " << fileSize << " bytes for file";
|
||||
Fs::Vfs::VfsClose(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Fs::Vfs::VfsRead(handle, fileData, 0, fileSize);
|
||||
Fs::Vfs::VfsClose(handle);
|
||||
|
||||
// Validate ELF header
|
||||
Elf64Header* hdr = (Elf64Header*)fileData;
|
||||
if (!ValidateElfHeader(hdr)) {
|
||||
Memory::g_heap->Free(fileData);
|
||||
return 0;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "ELF") << "Entry point: " << kcp::hex << hdr->e_entry << kcp::dec
|
||||
<< ", " << (uint64_t)hdr->e_phnum << " program header(s)";
|
||||
|
||||
// Process program headers
|
||||
for (uint16_t i = 0; i < hdr->e_phnum; i++) {
|
||||
Elf64ProgramHeader* phdr = (Elf64ProgramHeader*)(fileData + hdr->e_phoff + i * hdr->e_phentsize);
|
||||
|
||||
if (phdr->p_type != PT_LOAD) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (phdr->p_memsz == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::INFO, "ELF") << "PT_LOAD: vaddr=" << kcp::hex << phdr->p_vaddr
|
||||
<< " filesz=" << phdr->p_filesz << " memsz=" << phdr->p_memsz << kcp::dec;
|
||||
|
||||
// Allocate pages and map them in the process PML4 with User bit
|
||||
uint64_t segBase = phdr->p_vaddr & ~0xFFFULL;
|
||||
uint64_t segEnd = (phdr->p_vaddr + phdr->p_memsz + 0xFFF) & ~0xFFFULL;
|
||||
uint64_t numPages = (segEnd - segBase) / 0x1000;
|
||||
|
||||
for (uint64_t p = 0; p < numPages; p++) {
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "ELF") << "Out of physical pages";
|
||||
Memory::g_heap->Free(fileData);
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
uint64_t virtAddr = segBase + p * 0x1000;
|
||||
|
||||
// Map into the process's PML4 with User bit set
|
||||
Memory::VMM::Paging::MapUserIn(pml4Phys, physAddr, virtAddr);
|
||||
|
||||
// Copy file data that overlaps this page (via HHDM)
|
||||
uint64_t pageStart = virtAddr;
|
||||
uint64_t pageEnd = virtAddr + 0x1000;
|
||||
|
||||
uint64_t segFileStart = phdr->p_vaddr;
|
||||
uint64_t segFileEnd = phdr->p_vaddr + phdr->p_filesz;
|
||||
|
||||
uint64_t copyStart = (pageStart > segFileStart) ? pageStart : segFileStart;
|
||||
uint64_t copyEnd = (pageEnd < segFileEnd) ? pageEnd : segFileEnd;
|
||||
|
||||
if (copyStart < copyEnd) {
|
||||
uint64_t dstOffset = copyStart - pageStart;
|
||||
uint64_t srcOffset = copyStart - phdr->p_vaddr + phdr->p_offset;
|
||||
uint64_t copySize = copyEnd - copyStart;
|
||||
|
||||
uint8_t* dst = (uint8_t*)Memory::HHDM(physAddr) + dstOffset;
|
||||
uint8_t* src = fileData + srcOffset;
|
||||
memcpy(dst, src, copySize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t entryPoint = hdr->e_entry;
|
||||
Memory::g_heap->Free(fileData);
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "ELF") << "Loaded successfully, entry=" << kcp::hex << entryPoint << kcp::dec;
|
||||
return entryPoint;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* ElfLoader.hpp
|
||||
* ELF64 binary loader for user-mode processes
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Sched {
|
||||
|
||||
struct Elf64Header {
|
||||
uint8_t e_ident[16];
|
||||
uint16_t e_type;
|
||||
uint16_t e_machine;
|
||||
uint32_t e_version;
|
||||
uint64_t e_entry;
|
||||
uint64_t e_phoff;
|
||||
uint64_t e_shoff;
|
||||
uint32_t e_flags;
|
||||
uint16_t e_ehsize;
|
||||
uint16_t e_phentsize;
|
||||
uint16_t e_phnum;
|
||||
uint16_t e_shentsize;
|
||||
uint16_t e_shnum;
|
||||
uint16_t e_shstrndx;
|
||||
};
|
||||
|
||||
struct Elf64ProgramHeader {
|
||||
uint32_t p_type;
|
||||
uint32_t p_flags;
|
||||
uint64_t p_offset;
|
||||
uint64_t p_vaddr;
|
||||
uint64_t p_paddr;
|
||||
uint64_t p_filesz;
|
||||
uint64_t p_memsz;
|
||||
uint64_t p_align;
|
||||
};
|
||||
|
||||
static constexpr uint32_t PT_LOAD = 1;
|
||||
static constexpr uint16_t ET_EXEC = 2;
|
||||
static constexpr uint16_t EM_X86_64 = 62;
|
||||
|
||||
// Load an ELF64 binary into a per-process address space.
|
||||
// pml4Phys = physical address of the process's PML4.
|
||||
// Returns the entry point address, or 0 on failure.
|
||||
uint64_t ElfLoad(const char* vfsPath, uint64_t pml4Phys);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
/*
|
||||
* Scheduler.cpp
|
||||
* Preemptive process scheduler with user-mode support
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#include "Scheduler.hpp"
|
||||
#include "ElfLoader.hpp"
|
||||
#include <Memory/PageFrameAllocator.hpp>
|
||||
#include <Memory/Paging.hpp>
|
||||
#include <Memory/HHDM.hpp>
|
||||
#include <Libraries/Memory.hpp>
|
||||
#include <Terminal/Terminal.hpp>
|
||||
#include <CppLib/Stream.hpp>
|
||||
#include <Hal/Apic/Apic.hpp>
|
||||
#include <Hal/GDT.hpp>
|
||||
|
||||
// Assembly: context switch with CR3 parameter
|
||||
extern "C" void SchedContextSwitch(uint64_t* oldRsp, uint64_t newRsp, uint64_t newCR3);
|
||||
|
||||
// Assembly: jump to user mode via IRETQ
|
||||
extern "C" void JumpToUserMode(uint64_t rip, uint64_t rsp);
|
||||
|
||||
// Global kernel RSP for SYSCALL entry (written by scheduler, read by SyscallEntry.asm)
|
||||
extern "C" uint64_t g_kernelRsp;
|
||||
uint64_t g_kernelRsp = 0;
|
||||
|
||||
namespace Sched {
|
||||
|
||||
static Process processTable[MaxProcesses];
|
||||
static int currentPid = -1; // -1 = idle (kernel main loop)
|
||||
static int nextPid = 0;
|
||||
static uint64_t idleSavedRsp = 0;
|
||||
|
||||
// The idle loop runs in the kernel PML4
|
||||
static uint64_t GetKernelCR3() {
|
||||
return (uint64_t)Memory::VMM::g_paging->PML4;
|
||||
}
|
||||
|
||||
// Startup function for newly spawned processes.
|
||||
// SchedContextSwitch "returns" here on first schedule.
|
||||
static void ProcessStartup() {
|
||||
// Send EOI for the timer IRQ that triggered the context switch
|
||||
Hal::LocalApic::SendEOI();
|
||||
|
||||
if (currentPid >= 0) {
|
||||
Process& proc = processTable[currentPid];
|
||||
|
||||
// Set up kernel RSP for SYSCALL entry
|
||||
g_kernelRsp = proc.kernelStackTop;
|
||||
|
||||
// Set up TSS RSP0 for hardware interrupts from ring 3
|
||||
Hal::g_tss.rsp0 = proc.kernelStackTop;
|
||||
|
||||
// Jump to user mode (never returns)
|
||||
JumpToUserMode(proc.entryPoint, proc.userStackTop);
|
||||
}
|
||||
|
||||
ExitProcess();
|
||||
for (;;) {
|
||||
asm volatile("hlt");
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize() {
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
processTable[i].pid = i;
|
||||
processTable[i].state = ProcessState::Free;
|
||||
processTable[i].name = nullptr;
|
||||
processTable[i].savedRsp = 0;
|
||||
processTable[i].stackBase = 0;
|
||||
processTable[i].entryPoint = 0;
|
||||
processTable[i].sliceRemaining = 0;
|
||||
processTable[i].pml4Phys = 0;
|
||||
processTable[i].kernelStackTop = 0;
|
||||
processTable[i].userStackTop = 0;
|
||||
processTable[i].heapNext = 0;
|
||||
}
|
||||
|
||||
currentPid = -1;
|
||||
nextPid = 0;
|
||||
idleSavedRsp = 0;
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Initialized (" << MaxProcesses
|
||||
<< " process slots, " << (uint64_t)TimeSliceMs << " ms time slice)";
|
||||
}
|
||||
|
||||
void Spawn(const char* vfsPath) {
|
||||
int slot = -1;
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].state == ProcessState::Free) {
|
||||
slot = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (slot < 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "No free process slots";
|
||||
return;
|
||||
}
|
||||
|
||||
// Create per-process PML4 with kernel-half copied
|
||||
uint64_t pml4Phys = Memory::VMM::Paging::CreateUserPML4();
|
||||
|
||||
// Load ELF into the process's address space
|
||||
uint64_t entry = ElfLoad(vfsPath, pml4Phys);
|
||||
if (entry == 0) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to load ELF: " << vfsPath;
|
||||
return;
|
||||
}
|
||||
|
||||
// Allocate kernel stack (used during syscalls and interrupts)
|
||||
void* firstPage = Memory::g_pfa->AllocateZeroed();
|
||||
if (firstPage == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for kernel stack";
|
||||
return;
|
||||
}
|
||||
void* stackMem = Memory::g_pfa->ReallocConsecutive(firstPage, StackPages);
|
||||
if (stackMem == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Failed to allocate contiguous kernel stack";
|
||||
Memory::g_pfa->Free(firstPage);
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t* kernelStackBase = (uint8_t*)stackMem;
|
||||
uint64_t kernelStackTop = (uint64_t)kernelStackBase + StackSize;
|
||||
|
||||
// Allocate user stack pages and map them in the process PML4
|
||||
uint64_t userStackBase = UserStackTop - UserStackSize;
|
||||
uint64_t topStackPagePhys = 0;
|
||||
for (uint64_t i = 0; i < UserStackPages; i++) {
|
||||
void* page = Memory::g_pfa->AllocateZeroed();
|
||||
if (page == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for user stack";
|
||||
return;
|
||||
}
|
||||
uint64_t physAddr = Memory::SubHHDM((uint64_t)page);
|
||||
Memory::VMM::Paging::MapUserIn(pml4Phys, physAddr, userStackBase + i * 0x1000);
|
||||
if (i == UserStackPages - 1) topStackPagePhys = physAddr;
|
||||
}
|
||||
|
||||
// Allocate and map a user-space exit stub page.
|
||||
// When _start() returns, it jumps here and calls SYS_EXIT(0).
|
||||
{
|
||||
void* stubPage = Memory::g_pfa->AllocateZeroed();
|
||||
if (stubPage == nullptr) {
|
||||
Kt::KernelLogStream(Kt::ERROR, "Sched") << "Out of memory for exit stub";
|
||||
return;
|
||||
}
|
||||
uint64_t stubPhys = Memory::SubHHDM((uint64_t)stubPage);
|
||||
Memory::VMM::Paging::MapUserIn(pml4Phys, stubPhys, ExitStubAddr);
|
||||
|
||||
// Write: xor edi, edi; xor eax, eax; syscall
|
||||
uint8_t* stub = (uint8_t*)stubPage;
|
||||
stub[0] = 0x31; stub[1] = 0xFF; // xor edi, edi (exit code 0)
|
||||
stub[2] = 0x31; stub[3] = 0xC0; // xor eax, eax (SYS_EXIT = 0)
|
||||
stub[4] = 0x0F; stub[5] = 0x05; // syscall
|
||||
}
|
||||
|
||||
// Push exit stub address as the return address on the user stack.
|
||||
// UserStackTop - 8 falls at offset 0xFF8 within the top stack page.
|
||||
{
|
||||
uint8_t* topPage = (uint8_t*)Memory::HHDM(topStackPagePhys);
|
||||
*(uint64_t*)(topPage + 0xFF8) = ExitStubAddr;
|
||||
}
|
||||
|
||||
// Set up the initial kernel stack frame so that SchedContextSwitch
|
||||
// "returns" into ProcessStartup
|
||||
uint64_t* sp = (uint64_t*)kernelStackTop;
|
||||
|
||||
*(--sp) = (uint64_t)ProcessStartup; // return addr
|
||||
*(--sp) = 0; // rbp
|
||||
*(--sp) = 0; // rbx
|
||||
*(--sp) = 0; // r12
|
||||
*(--sp) = 0; // r13
|
||||
*(--sp) = 0; // r14
|
||||
*(--sp) = 0; // r15
|
||||
|
||||
Process& proc = processTable[slot];
|
||||
proc.pid = nextPid++;
|
||||
proc.state = ProcessState::Ready;
|
||||
proc.name = vfsPath;
|
||||
proc.savedRsp = (uint64_t)sp;
|
||||
proc.stackBase = (uint64_t)kernelStackBase;
|
||||
proc.entryPoint = entry;
|
||||
proc.sliceRemaining = TimeSliceMs;
|
||||
proc.pml4Phys = pml4Phys;
|
||||
proc.kernelStackTop = kernelStackTop;
|
||||
proc.userStackTop = UserStackTop - 8; // account for pushed exit stub return address
|
||||
proc.heapNext = UserHeapBase;
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Spawned process " << (uint64_t)proc.pid
|
||||
<< " (" << vfsPath << ") entry=" << kcp::hex << entry
|
||||
<< " kstack=" << (uint64_t)kernelStackBase << "-" << kernelStackTop
|
||||
<< " ustack=" << userStackBase << "-" << UserStackTop
|
||||
<< " pml4=" << pml4Phys << kcp::dec;
|
||||
}
|
||||
|
||||
void Schedule() {
|
||||
int next = -1;
|
||||
int start = (currentPid >= 0) ? currentPid + 1 : 0;
|
||||
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
int idx = (start + i) % MaxProcesses;
|
||||
if (processTable[idx].state == ProcessState::Ready) {
|
||||
next = idx;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (next < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (next == currentPid) {
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t* oldRspPtr;
|
||||
uint64_t oldCR3;
|
||||
|
||||
if (currentPid >= 0) {
|
||||
processTable[currentPid].state = ProcessState::Ready;
|
||||
oldRspPtr = &processTable[currentPid].savedRsp;
|
||||
} else {
|
||||
oldRspPtr = &idleSavedRsp;
|
||||
}
|
||||
|
||||
currentPid = next;
|
||||
processTable[next].state = ProcessState::Running;
|
||||
processTable[next].sliceRemaining = TimeSliceMs;
|
||||
|
||||
uint64_t newCR3 = processTable[next].pml4Phys;
|
||||
|
||||
// Update kernel RSP for SYSCALL entry
|
||||
g_kernelRsp = processTable[next].kernelStackTop;
|
||||
|
||||
// Update TSS RSP0 for hardware interrupts from ring 3
|
||||
Hal::g_tss.rsp0 = processTable[next].kernelStackTop;
|
||||
|
||||
SchedContextSwitch(oldRspPtr, processTable[next].savedRsp, newCR3);
|
||||
}
|
||||
|
||||
void Tick() {
|
||||
if (currentPid < 0) {
|
||||
// Idle — check if any process became ready
|
||||
Schedule();
|
||||
return;
|
||||
}
|
||||
|
||||
if (processTable[currentPid].sliceRemaining > 0) {
|
||||
processTable[currentPid].sliceRemaining--;
|
||||
}
|
||||
|
||||
if (processTable[currentPid].sliceRemaining == 0) {
|
||||
Schedule();
|
||||
}
|
||||
}
|
||||
|
||||
int GetCurrentPid() {
|
||||
return (currentPid >= 0) ? processTable[currentPid].pid : -1;
|
||||
}
|
||||
|
||||
Process* GetCurrentProcessPtr() {
|
||||
if (currentPid < 0) return nullptr;
|
||||
return &processTable[currentPid];
|
||||
}
|
||||
|
||||
void ExitProcess() {
|
||||
if (currentPid < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
Kt::KernelLogStream(Kt::OK, "Sched") << "Process " << (uint64_t)processTable[currentPid].pid << " terminated";
|
||||
|
||||
processTable[currentPid].state = ProcessState::Terminated;
|
||||
|
||||
int next = -1;
|
||||
for (int i = 0; i < MaxProcesses; i++) {
|
||||
if (processTable[i].state == ProcessState::Ready) {
|
||||
next = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (next >= 0) {
|
||||
int old = currentPid;
|
||||
currentPid = next;
|
||||
processTable[next].state = ProcessState::Running;
|
||||
processTable[next].sliceRemaining = TimeSliceMs;
|
||||
|
||||
uint64_t newCR3 = processTable[next].pml4Phys;
|
||||
g_kernelRsp = processTable[next].kernelStackTop;
|
||||
Hal::g_tss.rsp0 = processTable[next].kernelStackTop;
|
||||
|
||||
SchedContextSwitch(&processTable[old].savedRsp, processTable[next].savedRsp, newCR3);
|
||||
} else {
|
||||
int old = currentPid;
|
||||
currentPid = -1;
|
||||
SchedContextSwitch(&processTable[old].savedRsp, idleSavedRsp, GetKernelCR3());
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
asm volatile("hlt");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Scheduler.hpp
|
||||
* Preemptive process scheduler with user-mode support
|
||||
* Copyright (c) 2025 Daniel Hammer
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
namespace Sched {
|
||||
|
||||
static constexpr int MaxProcesses = 16;
|
||||
static constexpr uint64_t StackPages = 4; // 16 KiB kernel stack per process
|
||||
static constexpr uint64_t StackSize = StackPages * 0x1000;
|
||||
static constexpr uint64_t UserStackPages = 4; // 16 KiB user stack
|
||||
static constexpr uint64_t UserStackSize = UserStackPages * 0x1000;
|
||||
static constexpr uint64_t UserStackTop = 0x7FFFFFF000ULL; // User stack top VA
|
||||
static constexpr uint64_t UserHeapBase = 0x40000000ULL; // User heap start VA
|
||||
static constexpr uint64_t ExitStubAddr = 0x3FF000ULL; // User-space exit stub page
|
||||
static constexpr uint64_t TimeSliceMs = 10; // 10 ms time slice
|
||||
|
||||
enum class ProcessState {
|
||||
Free,
|
||||
Ready,
|
||||
Running,
|
||||
Terminated
|
||||
};
|
||||
|
||||
struct Process {
|
||||
int pid;
|
||||
ProcessState state;
|
||||
const char* name;
|
||||
uint64_t savedRsp;
|
||||
uint64_t stackBase; // Bottom of allocated kernel stack (lowest address)
|
||||
uint64_t entryPoint;
|
||||
uint64_t sliceRemaining; // Ticks left in current time slice
|
||||
uint64_t pml4Phys; // Physical address of per-process PML4
|
||||
uint64_t kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL)
|
||||
uint64_t userStackTop; // User-space stack top
|
||||
uint64_t heapNext; // Simple bump allocator for user heap
|
||||
};
|
||||
|
||||
void Initialize();
|
||||
void Spawn(const char* vfsPath);
|
||||
void Schedule();
|
||||
|
||||
// Called from the APIC timer handler on every tick.
|
||||
void Tick();
|
||||
|
||||
// Get the PID of the currently running process (-1 if idle)
|
||||
int GetCurrentPid();
|
||||
|
||||
// Get a pointer to the currently running process (nullptr if idle)
|
||||
Process* GetCurrentProcessPtr();
|
||||
|
||||
// Called by terminated processes to mark themselves done
|
||||
void ExitProcess();
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user