From c13dd991502b4636d16ab9fae0294d90f7c9f299 Mon Sep 17 00:00:00 2001 From: Maksymilian Wilczynski Date: Sat, 2 May 2026 15:25:39 +0200 Subject: [PATCH] KernelMain reports more details about memory usage, LdrResolveImports catches kernel32, user32, ole32 and winmm and redirects them to win32. --- KERNEL_DESIGN.md | 1182 --------------------------------------- OVMF/OVMF_VARS_4M.fd | Bin 540672 -> 540672 bytes code/kernel/kmain.c | 20 +- code/kernel/ldr_image.c | 5 +- code/kernel/mm_virt.c | 10 + code/kernel/mm_virt.h | 3 + readme.md | 86 +++ 7 files changed, 116 insertions(+), 1190 deletions(-) delete mode 100644 KERNEL_DESIGN.md create mode 100644 readme.md diff --git a/KERNEL_DESIGN.md b/KERNEL_DESIGN.md deleted file mode 100644 index 41171a4..0000000 --- a/KERNEL_DESIGN.md +++ /dev/null @@ -1,1182 +0,0 @@ -# Hybrid Kernel Architecture Design Specification - ---- - -## 1. Overview & Philosophy - -The kernel manages memory, schedules threads, routes interrupts, and brokers messages. -No filesystem, no network stack, no GUI. Everything else lives in user-mode code or -drivers loaded at boot. - -Three governing rules: - -- **Kernel is unreachable from ring 3.** Supervisor pages, full stop. -- **User space is one flat shared heap.** There is no per-process address space. - All user code lives in the same virtual range and can reach any of it. - No code signing certificates, no TPM, just the checksum already in the format. - -Key influences: - -- Executive objects - typed, reference-counted kernel objects for everything the - kernel tracks. -- A HAL sitting between the kernel and hardware so drivers never touch ports directly. -- A clean separation between the object manager, the scheduler, and the memory manager. - -Key influences rejected: - -- UNIX naming (no `pmm_alloc_frame`, no `ke_spin_lock`, no snake_case in kernel code). -- UTF-16 anywhere. The kernel is UTF-8/ASCII. `char *` throughout. -- SYSCALL/SYSRET complexity. Interrupts are simpler and sufficient: `INT 0x2E`. -- GCC-isms. This is MSVC. - ---- - -## 2. Component Map - -``` -bootx64.efi -│ -├── reads boot.ini (debug flag, future knobs) -├── reads drivers/drivers.ini -├── loads: -│ kernel.exe -│ hal.dll -│ syscall.dll -│ win32.dll -│ drivers/*.sys -│ -├── converts EFI memory map -> MEMORY_MAP -└── jumps to kernel entry point in ring 0 - - ┌──────────────────────────────────┐ -ring 0 │ kernel.exe │ -(upper half) │ Mm │ Ke │ Ob │ Io │ scheduler │ - ├──────────────────────────────────┤ - │ hal.dll │ - │ APIC │ HPET │ GOP │ PS/2 │ - │ PCI │ disk dispatch │ - │ DbgPrint │ - └──────────────────────────────────┘ - ┌──────────────────────────────────┐ -ring 0 (loadable) │ ide.sys ahci.sys │ - └──────────────────────────────────┘ - ┌──────────────────────────────────┐ -ring 3 │ win32.dll │ -(lower half) │ kernel32 │ user32 │ ole32 │ - ├──────────────────────────────────┤ - │ syscall.dll │ - │ INT 0x2E stubs │ - └──────────────────────────────────┘ -``` - ---- - -## 3. Naming Convention - -All kernel-visible identifiers follow **PrefixActionObject**: - -``` - -``` - -### Subsystem prefixes - -| Prefix | Subsystem | -|--------|------------------------------------| -| `Mm` | Memory manager | -| `Ke` | Kernel executive (sched, sync) | -| `Ob` | Object manager | -| `Io` | I/O manager / driver interface | -| `Hal` | Hardware abstraction layer | -| `Ldr` | Loader (PE image loading) | -| `Dbg` | Debug | - -### Examples - -```c -MmAllocatePool(size) -MmFreePool(ptr) -MmMapPage(virt, phys, flags) - -KeAcquireMutex(mutex) -KeReleaseMutex(mutex) -KeSignalEvent(event) -KeWaitForObject(object) -KeYield() - -ObReferenceObject(object) -ObDereferenceObject(object) -ObAllocateObject(type, size) - -IoRegisterDisk(index, driver) -IoReadSectors(drive, lba, count, buf) - -HalQueryTicks() -HalStall(us) -HalInitialize(bootInfo) - -LdrLoadImage(buffer, size, baseOut) -LdrResolveImport(dllName, functionName) - -DbgPrint(Format, ...) -``` - -### Types and structs - -Structs are named `NOUN` in all-caps with no prefix. The prefix appears only on functions -and global variables, not on type names: - -```c -typedef struct PROCESS PROCESS; -typedef struct THREAD THREAD; -typedef struct MUTEX MUTEX; -typedef struct SEMAPHORE SEMAPHORE; -typedef struct EVENT EVENT; -typedef struct QUEUE QUEUE; -typedef struct OBJECT OBJECT; -``` - -### Return type - -All fallible kernel functions return `KSTATUS`. Infallible helpers (queries, ref bumps) may -return their value directly. - ---- - -## 4. Memory Architecture - -### 4.1 Virtual Address Space - -``` -0x0000000000000000 ┌──────────────────────────────────┐ - │ │ - │ User Space (lower half) │ - │ │ - │ One flat shared heap. │ - │ All processes and all their │ - │ allocations live here. │ - │ Ring 3 readable and writable. │ - │ │ -0x00007FFFFFFFFFFF └──────────────────────────────────┘ - ... (non-canonical hole) -0xFFFF800000000000 ┌──────────────────────────────────┐ - │ kernel.exe [+0 MB .. +128 MB]│ - ├──────────────────────────────────┤ - │ hal.dll, syscall.dll, │ - │ win32.dll, *.sys │ - │ (packed above kernel.exe) │ - ├──────────────────────────────────┤ - │ Direct physical window │ - │ 0xFFFF880000000000 + │ - ├──────────────────────────────────┤ - │ Kernel pool (kmalloc) │ - │ 0xFFFFC00000000000 + │ - └──────────────────────────────────┘ -``` - -**Invariant:** Every PML4 entry covering the upper half has U/S = 0. A ring-3 access to any -upper-half address is always a hard fault. No fixups, no guard pages, no recovery. - -There is one PML4 shared by all processes. The lower-half entries point to the shared user -heap pages. The upper-half entries are identical in every CR3. - -### 4.2 Custom Memory Map - -The bootloader converts the EFI memory map into a `MEMORY_MAP` before calling `ExitBootServices`. -The kernel never sees EFI types. - -```c -// memory_map.h - shared between bootx64.efi and kernel.exe - -#pragma once -#include - -#define MEMORY_MAP_MAGIC 0x4D454D4D41500000ULL /* 'MEMMAP\0\0' */ -#define MEMORY_MAX_REGIONS 256 - -typedef enum MEMORY_TYPE { - MemoryUsable = 0, /* free RAM, available to the PMM */ - MemoryReserved = 1, /* firmware-reserved, do not touch */ - MemoryMappedIO = 2, /* device MMIO regions */ - MemoryFramebuffer = 3, /* GOP framebuffer */ - MemoryBootloader = 4, /* bootloader code/data (reclaimable) */ - MemoryKernel = 5, /* kernel + HAL + drivers image pages */ -} MEMORY_TYPE; - -typedef struct MEMORY_REGION { - uint64_t Base; - uint64_t Length; /* in bytes, always page-aligned */ - MEMORY_TYPE Type; - uint32_t _pad; -} MEMORY_REGION; - -typedef struct MEMORY_MAP { - uint64_t Magic; - uint32_t RegionCount; - uint32_t _pad; - MEMORY_REGION Regions[MEMORY_MAX_REGIONS]; -} MEMORY_MAP; -``` - -### 4.3 Physical Memory Manager - -Stack allocator over 4 KB frames, seeded from all `MemoryUsable` regions in `MEMORY_MAP`. - -```c -// mm_phys.h - -#pragma once -#include -#include - -#define PAGE_SIZE 4096ULL - -typedef uint64_t PHYSICAL_ADDRESS; - -void MmInitializePhysical(const MEMORY_MAP* map); -PHYSICAL_ADDRESS MmAllocateFrame(void); -PHYSICAL_ADDRESS MmAllocateContiguous(size_t count); -void MmFreeFrame(PHYSICAL_ADDRESS frame); -size_t MmFreeFrameCount(void); -size_t MmTotalFrameCount(void); -``` - -Internals: a stack of free `PHYSICAL_ADDRESS` values in a region carved out before any -allocation is served. Lock is a spinlock; the stack is trivially O(1) for both push and pop. - -### 4.4 Virtual Memory Manager - -```c -// mm_virt.h - -#pragma once -#include -#include - -/* Page flags */ -#define PAGE_PRESENT (1ULL << 0) -#define PAGE_WRITE (1ULL << 1) -#define PAGE_USER (1ULL << 2) -#define PAGE_NOCACHE (1ULL << 4) -#define PAGE_NX (1ULL << 63) - -KSTATUS MmMapPage(uintptr_t virt, PHYSICAL_ADDRESS phys, uint64_t flags); -KSTATUS MmUnmapPage(uintptr_t virt); - -/* Kernel pool - ring 0 only, never PAGE_USER. */ -void* MmAllocatePool(size_t size); -void MmFreePool(void* ptr); - -/* User heap - shared flat space, ring 3 accessible. */ -void* MmAllocateUserMemory(size_t size); -void MmFreeUserMemory(void* ptr, size_t size); - -/* Load the shared PML4 into CR3. Called once at boot. */ -void MmActivate(void); -``` - -Kernel pool uses a best-fit free list allocator. - -User memory is a single global best-fit free list allocator backed by the PMM. All user allocations are -mapped `PAGE_PRESENT | PAGE_WRITE | PAGE_USER`. There are no per-process heaps, no virtual -address descriptors, no separate regions per process - one heap, everyone shares it. - ---- - -## 5. Boot Sequence - bootx64.efi - -### 5.1 boot.ini - -Sits at the root of the ESP. Read by the bootloader before anything else. - -```ini -[kernel] -debug = 1 -``` - -The `debug` value is passed through `BOOT_INFO` to the kernel, which hands it to -`HalInitialize`. The HAL sets an internal `HalDebugEnabled` flag from it. No other mechanism -controls `DbgPrint` output at this time. - -Future knobs (serial port, memory cap, etc.) belong here, not in `drivers.ini`. - -### 5.2 Responsibilities - -1. Parse `boot.ini`. -2. Read `drivers/drivers.ini` to build the driver load list. -3. Load each PE image (`kernel.exe`, `hal.dll`, `syscall.dll`, `win32.dll`, all listed `.sys` - files) from the ESP using UEFI file protocol. -4. Validate each image: the PE32+ loader computes the PE checksum and compares it to - `IMAGE_OPTIONAL_HEADER64.CheckSum`. Mismatch → print error via `ConOut` → halt. -5. Map kernel images into the upper half of a freshly constructed PML4. Map all physical RAM - as a direct window at `0xFFFF880000000000`. -6. Convert the EFI memory map to `MEMORY_MAP`. -7. Call `ExitBootServices`. -8. Set up a minimal GDT (ring 0 code/data, ring 3 code/data), a minimal IDT (fault stubs + - `INT 0x2E` handler vector), enable NXE in EFER, set CR3. -9. Fill `BOOT_INFO` and jump to `kernel.exe` entry point. - -### 5.3 PE Checksum Validation - -The checksum algorithm is the standard PE checksum (16-bit word accumulation with carry folding, -then the file size is added). The bootloader rejects any image whose computed checksum does not -match the value stored in the optional header. This is enforced on every image, every boot. -There is no override, no skip flag. - -### 5.4 Boot Information Block - -```c -// boot_info.h - shared between bootx64.efi and kernel.exe - -#pragma once -#include -#include "memory_map.h" - -#define BOOT_INFO_MAGIC 0x424F4F54494E464FULL /* 'BOOTINFO' */ -#define MAX_BOOT_MODULES 32 - -typedef struct BOOT_MODULE { - char Name[32]; /* e.g. "hal.dll", "ide.sys" */ - uintptr_t VirtBase; /* where it was mapped */ - size_t ImageSize; -} BOOT_MODULE; - -typedef struct BOOT_INFO { - uint64_t Magic; - - /* Physical memory (our format, not EFI's). */ - MEMORY_MAP MemoryMap; - - /* GOP framebuffer. */ - uintptr_t FramebufferBase; - uint32_t FramebufferWidth; - uint32_t FramebufferHeight; - uint32_t FramebufferStride; /* pixels per row */ - - /* Loaded modules. */ - BOOT_MODULE Modules[MAX_BOOT_MODULES]; - uint32_t ModuleCount; - - /* ACPI RSDP physical address (for APIC, HPET). */ - uintptr_t RsdpPhysical; - - /* Direct physical window base. */ - uintptr_t PhysicalMapBase; /* = 0xFFFF880000000000 */ - - /* From boot.ini */ - uint32_t DebugEnabled; -} BOOT_INFO; -``` - ---- - -## 6. Kernel - kernel.exe - -### 6.1 Status Codes - KSTATUS - -All fallible kernel functions return `KSTATUS`. Success is always zero. Failures are positive -named constants. No errno negatives, no HRESULT sign games. - -```c -// ke_status.h - -#pragma once -#include - -typedef uint32_t KSTATUS; - -#define STATUS_SUCCESS ((KSTATUS)0x00000000) -#define STATUS_FAILURE ((KSTATUS)0x00000001) -#define STATUS_INVALID_PARAMETER ((KSTATUS)0x00000002) -#define STATUS_NO_MEMORY ((KSTATUS)0x00000003) -#define STATUS_NOT_FOUND ((KSTATUS)0x00000004) -#define STATUS_ACCESS_DENIED ((KSTATUS)0x00000005) -#define STATUS_ALREADY_EXISTS ((KSTATUS)0x00000006) -#define STATUS_NOT_SUPPORTED ((KSTATUS)0x00000007) -#define STATUS_TIMEOUT ((KSTATUS)0x00000008) -#define STATUS_BUFFER_TOO_SMALL ((KSTATUS)0x00000009) -#define STATUS_DEVICE_ERROR ((KSTATUS)0x0000000A) -#define STATUS_OBJECT_DEAD ((KSTATUS)0x0000000B) -#define STATUS_QUEUE_FULL ((KSTATUS)0x0000000C) -#define STATUS_QUEUE_EMPTY ((KSTATUS)0x0000000D) -#define STATUS_CHECKSUM_MISMATCH ((KSTATUS)0x0000000E) - -#define KSUCCESS(s) ((s) == STATUS_SUCCESS) -``` - -### 6.2 Object Model - -Every resource the kernel tracks is an **executive object**: a typed, reference-counted blob -allocated from the kernel pool. The object header is always the first member of any object -struct - this allows the object manager to operate on any object through a pointer to `OBJECT`. - -```c -// ob_object.h - -#pragma once -#include -#include "ke_status.h" - -typedef enum OBJECT_TYPE { - ObjectTypeProcess = 1, - ObjectTypeThread = 2, - ObjectTypeMutex = 3, - ObjectTypeSemaphore = 4, - ObjectTypeEvent = 5, - ObjectTypeQueue = 6, -} OBJECT_TYPE; - -typedef struct OBJECT { - OBJECT_TYPE Type; - volatile long RefCount; /* interlocked */ - void (*Destroy)(struct OBJECT* self); -} OBJECT; - -/* Increment the reference count. Call whenever ownership is shared. */ -void ObReferenceObject(OBJECT* object); - -/* Decrement. Calls Destroy() and frees the object when count reaches zero. */ -void ObDereferenceObject(OBJECT* object); - -/* Allocate a zeroed object of the given size from kernel pool. - Caller fills Type and Destroy before publishing the pointer. */ -OBJECT* ObAllocateObject(OBJECT_TYPE type, size_t size); -``` - -### 6.3 Process Model - -A process owns a handle table and a list of threads. It does not own an address space - -there is one shared address space and it belongs to everyone. - -```c -// ke_process.h - -#pragma once -#include "ob_object.h" -#include "ke_list.h" -#include "ke_spinlock.h" - -#define PROCESS_NAME_MAX 64 -#define HANDLE_TABLE_SIZE 256 - -typedef struct PROCESS { - OBJECT Header; /* must be first */ - - uint32_t ProcessId; - char Name[PROCESS_NAME_MAX]; /* UTF-8 */ - - /* Thread list. */ - LIST_ENTRY ThreadList; /* links THREAD.ProcessLink */ - uint32_t ThreadCount; - - /* Handle table. - Index 0 is always NULL (the invalid handle). - Entries are OBJECT pointers, each holding a reference. */ - OBJECT* Handles[HANDLE_TABLE_SIZE]; - SPINLOCK HandleLock; - - int ExitCode; - LIST_ENTRY GlobalLink; /* node in the global process list */ -} PROCESS; - -KSTATUS KeCreateProcess(const char* name, PROCESS **out); -void KeExitProcess(PROCESS* process, int exitCode); -KSTATUS KeAllocateHandle(PROCESS* process, OBJECT* object, uint32_t* handleOut); -void KeCloseHandle(PROCESS* process, uint32_t handle); -OBJECT* KeLookupHandle(PROCESS* process, uint32_t handle); -``` - -### 6.4 Thread Model - -A thread is the unit of scheduling. Every thread belongs to exactly one process. - -```c -// ke_thread.h - -#pragma once -#include "ob_object.h" -#include "ke_list.h" -#include - -#define KSTACK_SIZE (64 * 1024) /* 64 KB kernel stack */ -#define USTACK_SIZE (512 * 1024) /* 512 KB user stack */ - -typedef enum THREAD_PRIORITY { - PriorityLow = 0, - PriorityNormal = 1, - PriorityHigh = 2, -} THREAD_PRIORITY; - -typedef enum THREAD_STATE { - ThreadReady = 0, - ThreadRunning = 1, - ThreadWaiting = 2, - ThreadDead = 3, -} THREAD_STATE; - -/* - * Saved register context - matches the layout pushed by KeSwitchContext - * in ke_switch.asm. The saved RSP points to this structure on the - * kernel stack; restoring RSP and popping is the entire switch. - */ -typedef struct KCONTEXT { - uint64_t R15, R14, R13, R12; - uint64_t Rbp, Rbx; - uint64_t Rip; /* return address within KeSwitchContext */ -} KCONTEXT; - -typedef struct THREAD { - OBJECT Header; - - uint32_t ThreadId; - THREAD_STATE State; - THREAD_PRIORITY Priority; - - /* Scheduler feedback: quanta consumed without blocking. - Used by the MLFQ to decide demotion. */ - uint32_t QuantaConsumed; - - struct PROCESS* Owner; - LIST_ENTRY ProcessLink; /* node in Owner->ThreadList */ - LIST_ENTRY SchedulerLink; /* node in run queue or wait list */ - - /* Kernel stack - never PAGE_USER. */ - void* KernelStackBase; - uintptr_t KernelStackTop; - - /* Saved context pointer (set by KeSwitchContext). */ - KCONTEXT* SavedContext; - - /* Non-NULL when State == ThreadWaiting. */ - OBJECT* WaitObject; - - /* TSS RSP0 value for this thread, updated on every context switch. */ - uintptr_t TssRsp0; - - /* Ring-3 entry point and argument for new threads. */ - uintptr_t UserEntry; - uintptr_t UserArg; -} THREAD; - -KSTATUS KeCreateThread(PROCESS* owner, uintptr_t entry, uintptr_t arg, - THREAD_PRIORITY priority, THREAD **out); -void KeExitThread(THREAD* thread, int code); -``` - -**Kernel vs. user stacks:** -The kernel stack is allocated from kernel pool, mapped without `PAGE_USER`, and pointed to by -`TssRsp0`. On `INT 0x2E` or any hardware interrupt the CPU automatically loads this address -from the TSS, so ring-3 code never touches the kernel stack. The user stack lives in the shared -user heap, allocated by `MmAllocateUserMemory` when the thread is created. - -### 6.5 Scheduler - Multi-Level Feedback Queue - -Three run queues, one per priority level. A thread starts at `PriorityNormal` unless specified -otherwise. The scheduler moves threads between levels based on behaviour: - -- **Demotion:** a thread that exhausts its full quantum without blocking is moved down one - level (floor: `PriorityLow`). -- **Promotion:** a thread that blocks voluntarily before its quantum expires is moved up one - level (ceiling: `PriorityHigh`). -- **Dispatch:** always pick from the highest non-empty queue. Within a queue, round-robin. - -This rewards I/O-bound and interactive threads (which block often) with higher priority, and -keeps CPU-bound threads from starving other work without dropping them entirely. - -```c -// ke_sched.h - -#pragma once -#include "ke_thread.h" - -#define QUANTUM_MS 10 /* base time slice in milliseconds */ - -void KeInitializeScheduler(void); -void KeReadyThread(THREAD* thread); -void KeUnreadyThread(THREAD* thread); -void KeSchedulerTick(void); /* called from timer ISR every QUANTUM_MS */ -void KeYield(void); -THREAD* KeCurrentThread(void); -``` - -**`KeSchedulerTick` logic:** - -1. Increment `CurrentThread->QuantaConsumed`. -2. If the thread has blocked since last tick, promote it (already off the run queue - apply - the priority change before it is re-readied). -3. If `QuantaConsumed` exceeds the threshold for the current priority level, demote and reset. -4. Find the highest non-empty queue. If it is higher priority than the current thread, - preempt immediately; otherwise let the current thread finish its quantum. -5. Call `KeSwitchContext(current, next)`. - -**Context switch - `ke_switch.asm` (MASM64):** - -```asm -; void KeSwitchContext(THREAD* from, THREAD* to) -; RCX = from, RDX = to (Microsoft x64 ABI) - -KeSwitchContext PROC - push rbx - push rbp - push r12 - push r13 - push r14 - push r15 - mov [rcx + THREAD_SavedContext], rsp ; from->SavedContext = rsp - mov rsp, [rdx + THREAD_SavedContext] ; rsp = to->SavedContext - ; Update TSS.RSP0 so the next interrupt uses the incoming thread's kernel stack - mov rax, [rdx + THREAD_TssRsp0] - mov qword ptr [tss + TSS_RSP0_OFFSET], rax - pop r15 - pop r14 - pop r13 - pop r12 - pop rbp - pop rbx - ret -KeSwitchContext ENDP -``` - -`THREAD_SavedContext`, `THREAD_TssRsp0`, and `TSS_RSP0_OFFSET` are numeric offsets defined in -a shared `offsets.inc` generated by the build system from the C struct definitions. - -`KeCurrentThread()` reads from `gs:[0]`, a per-CPU slot written by the bootstrap code. -Single-core for now; SMP is a matter of setting up per-core GS bases. - -### 6.6 Synchronization Primitives - -#### Spinlock - -Not an executive object. Used inside the kernel for short critical sections that must not -sleep and may run in interrupt context. Never hold a spinlock across a scheduling boundary. - -Implemented in `ke_spinlock.h` as an inline interlocked CAS loop using MSVC intrinsics -(`_InterlockedCompareExchange`) with a `_mm_pause()` spin body. No assembly file needed. - -```c -// ke_spinlock.h - -#pragma once -#include -#include - -typedef struct SPINLOCK { - volatile long Locked; -} SPINLOCK; - -#define SPINLOCK_INIT { 0 } - -static __forceinline void KeAcquireSpinlock(SPINLOCK* sl) { - while (_InterlockedCompareExchange(&sl->Locked, 1, 0) != 0) - _mm_pause(); -} - -static __forceinline void KeReleaseSpinlock(SPINLOCK* sl) { - _InterlockedExchange(&sl->Locked, 0); -} -``` - -#### Mutex - -Binary, ownership-based. Only the acquiring thread may release it. -Blocking threads sleep on a wait list; the scheduler wakes exactly one on release. - -```c -typedef struct MUTEX { - OBJECT Header; - SPINLOCK Lock; - THREAD* Owner; /* NULL if unowned */ - LIST_ENTRY WaitList; -} MUTEX; - -KSTATUS MutexCreate(MUTEX **out); -KSTATUS KeAcquireMutex(MUTEX* mutex); -KSTATUS KeTryAcquireMutex(MUTEX* mutex); -void KeReleaseMutex(MUTEX* mutex); -``` - -#### Semaphore - -Counter-based. `KeWaitSemaphore` decrements (blocks at zero); -`KeSignalSemaphore` increments and wakes one waiter per call. - -```c -typedef struct SEMAPHORE { - OBJECT Header; - SPINLOCK Lock; - int32_t Count; - int32_t MaxCount; - LIST_ENTRY WaitList; -} SEMAPHORE; - -KSTATUS SemaphoreCreate(int32_t initial, int32_t max, SEMAPHORE **out); -KSTATUS KeWaitSemaphore(SEMAPHORE* semaphore); -KSTATUS KeTrySemaphore(SEMAPHORE* semaphore); -void KeSignalSemaphore(SEMAPHORE* semaphore); -``` - -#### Event - -Two modes: auto-reset and manual-reset. - -```c -typedef enum EVENT_TYPE { EventAutoReset, EventManualReset } EVENT_TYPE; - -typedef struct EVENT { - OBJECT Header; - SPINLOCK Lock; - EVENT_TYPE Type; - uint32_t Signaled; - LIST_ENTRY WaitList; -} EVENT; - -KSTATUS KeEventCreate(EVENT_TYPE type, uint32_t initialState, EVENT **out); -void KeSignalEvent(EVENT* event); -void KeClearEvent(EVENT* event); -KSTATUS KeWaitForEvent(EVENT* event); -``` - -**Common wait mechanism:** -`KeBlockThread(thread, object)` - marks the thread `ThreadWaiting`, records `WaitObject`, -removes it from the run queue, and calls `KeYield()`. The waking side calls -`KeUnblockThread(thread)` which clears `WaitObject`, promotes the thread, and calls -`KeReadyThread`. This path is shared by all three blocking primitives. - -### 6.7 IPC - Message Queues - -Processes share memory, so queues are a synchronization and ordering mechanism, not a -data-transfer one. A pointer in the payload is valid on both sides. - -```c -// ke_queue.h - -#pragma once -#include "ob_object.h" -#include "ke_spinlock.h" -#include -#include - -#define QUEUE_MESSAGE_SIZE 64 /* bytes, fixed */ -#define QUEUE_DEPTH 256 - -typedef struct QUEUE_MESSAGE { - uint32_t Type; /* caller-defined message class */ - uint32_t Flags; - uint64_t Param[6]; /* 48 bytes of payload */ -} QUEUE_MESSAGE; /* total: 64 bytes */ - -typedef struct QUEUE { - OBJECT Header; - SPINLOCK Lock; - QUEUE_MESSAGE Ring[QUEUE_DEPTH]; - uint32_t Head, Tail, Count; - LIST_ENTRY SendWaiters; - LIST_ENTRY RecvWaiters; -} QUEUE; - -KSTATUS QueueCreate(QUEUE **out); -KSTATUS KeSendMessage(QUEUE* queue, const QUEUE_MESSAGE* msg, bool blocking); -KSTATUS KeReceiveMessage(QUEUE* queue, QUEUE_MESSAGE* msg, bool blocking); -``` - ---- - -## 7. Hardware Abstraction Layer - hal.dll - -`hal.dll` is loaded into ring 0 alongside the kernel. The kernel retrieves its interface once -at boot via `LdrGetExport("hal.dll", "HalGetApi")`. Drivers receive the same pointer through -their `DriverEntry` call. - -### 7.1 HAL API Table - -```c -// hal_api.h - -#pragma once -#include -#include -#include -#include "ke_status.h" - -/* Screen - GOP framebuffer */ -typedef struct HAL_SCREEN { - void (*Clear)(uint32_t color); - void (*PutPixel)(int x, int y, uint32_t color); - void (*Blit)(int x, int y, int w, int h, const uint32_t* pixels); - void (*GetSize)(int* width, int* height); - void (*Scroll)(int lines); - void (*PutChar)(char c, int x, int y, uint32_t fg, uint32_t bg); -} HAL_SCREEN; - -/* Keyboard - PS/2, interrupt-driven ring buffer */ -typedef struct HAL_KEYBOARD { - bool (*Poll)(void); /* true if a key is available */ - uint8_t (*ReadScancode)(void); - char (*ReadChar)(void); /* 0 if nothing pending */ - void (*SetCallback)(void (*cb)(uint8_t scancode)); -} HAL_KEYBOARD; - -/* Disk - abstract 512-byte sector interface */ -typedef struct HAL_DISK { - KSTATUS (*ReadSectors)(uint8_t drive, uint64_t lba, - uint32_t count, void* buf); - KSTATUS (*WriteSectors)(uint8_t drive, uint64_t lba, - uint32_t count, const void* buf); - bool (*DrivePresent)(uint8_t drive); - uint64_t (*DriveSectorCount)(uint8_t drive); -} HAL_DISK; - -/* Timer - HPET preferred, APIC timer fallback */ -typedef struct HAL_TIMER { - uint64_t (*QueryTicks)(void); /* milliseconds since boot */ - void (*Stall)(uint32_t us); -} HAL_TIMER; - -/* PCI - config space via port I/O */ -typedef struct HAL_PCI { - uint32_t (*ConfigRead32)(uint8_t bus, uint8_t dev, - uint8_t fn, uint8_t reg); - void (*ConfigWrite32)(uint8_t bus, uint8_t dev, - uint8_t fn, uint8_t reg, uint32_t val); - bool (*FindDevice)(uint16_t vendor, uint16_t device, - uint8_t* bus, uint8_t* dev, uint8_t* fn); - bool (*FindClass)(uint8_t classCode, uint8_t subclass, - uint8_t* bus, uint8_t* dev, uint8_t* fn); -} HAL_PCI; - -/* Interrupt controller - local APIC + I/O APIC */ -typedef struct HAL_INTC { - void (*RegisterIrq)(uint8_t irq, void (*handler)(void)); - void (*MaskIrq)(uint8_t irq); - void (*UnmaskIrq)(uint8_t irq); - void (*SendEoi)(void); -} HAL_INTC; - -typedef struct HAL_API { - uint32_t Version; /* HAL_API_VERSION */ - HAL_SCREEN Screen; - HAL_KEYBOARD Keyboard; - HAL_DISK Disk; - HAL_TIMER Timer; - HAL_PCI Pci; - HAL_INTC Intc; -} HAL_API; - -#define HAL_API_VERSION 1 - -/* Exported by hal.dll. */ -const HAL_API* HalGetApi(void); -KSTATUS HalInitialize(const BOOT_INFO* bootInfo); -``` - -### 7.2 DbgPrint - -```c -/* Declared in hal_api.h, implemented in hal.dll, ring 0 only. - Not exported to ring 3. No KSTATUS - void, always. */ - -void DbgPrint(const char* fmt, ...); -``` - -`DbgPrint` is always compiled in. On entry it checks the internal `HalDebugEnabled` flag -(set by `HalInitialize` from `BOOT_INFO.DebugEnabled`) and returns immediately if clear. -When enabled, it formats the string and writes to the console. The implementation is not -specified here - just noted as writing to console output. - -Kernel code, HAL internals, and drivers all call `DbgPrint` freely. There is no ring-3 -equivalent and no syscall wrapper for it. - -### 7.3 HAL Internals Summary - -**Screen:** Direct writes to the GOP framebuffer from `BOOT_INFO`. An embedded 8×16 bitmap font -covers printable ASCII. `DbgPrint` uses this for output. - -**Keyboard:** IRQ 1 via I/O APIC. PS/2 controller on ports `0x60`/`0x64`. Scancodes collected -into a 256-entry ring buffer; `ReadChar` translates set-2 scancodes to ASCII. - -**Disk:** Dispatch table of up to 8 logical drives. Populated by drivers calling -`IoRegisterDisk` during `DriverEntry`. `HAL_DISK` functions index into this table. - -**Timer:** HPET comparator fires every 1 ms, increments a `uint64_t` tick counter. Falls back -to the APIC timer calibrated against the PIT if no HPET is found. - -**PCI:** Mechanism 1 port I/O (`0xCF8`/`0xCFC`). No PCIe extended config space for now. - -**APIC:** Local APIC MMIO base discovered from the MADT. I/O APIC IRQ routing from MADT. -IRQ 0 drives the scheduler tick. IRQ 1 is the keyboard. - ---- - -## 8. System Call Interface - syscall.dll - -### 8.1 Why INT 0x2E - -`SYSCALL`/`SYSRET` requires programming `MSR_STAR`, `MSR_LSTAR`, `MSR_SFMASK`, and careful -handling of the segment registers on return. `INT 0x2E` uses the IDT we already set up for -hardware interrupts - one IDT gate, one handler, done. The performance difference does not -matter at this scale. - -### 8.2 IDT Entry and Kernel Handler - -Vector `0x2E` is installed as an interrupt gate (IF cleared on entry, ring-3 callable - DPL=3 -in the gate descriptor). - -The handler is a MASM64 stub: - -```asm -; ke_syscall_entry in ke_syscall.asm -; On entry: RAX = syscall number, RCX/RDX/R8/R9/R10/R11 = args -; (Microsoft x64 ABI: first four args in RCX, RDX, R8, R9; -; we repurpose R10 and R11 for args 5 and 6) - -KeSyscallEntry PROC - ; The CPU has already pushed SS, RSP, RFLAGS, CS, RIP - ; and switched to the kernel stack via TSS.RSP0. - sub rsp, 8 ; align to 16 bytes - push r11 - push r10 - push r9 - push r8 - push rdx - push rcx - push rax - call KeSyscallDispatch ; C function, args already on stack - add rsp, 7 * 8 ; clean args - iretq -KeSyscallEntry ENDP -``` - -`KeSyscallDispatch` is a plain C function in `ke_syscall.c` that dispatches on `nr` through -a function pointer table indexed by `SYS_*` constants. - -### 8.3 Syscall Numbers - -```c -// syscall_nr.h - shared between kernel and syscall.dll - -#pragma once - -#define SYS_EXIT_THREAD 0 -#define SYS_EXIT_PROCESS 1 -#define SYS_SPAWN 2 -#define SYS_SLEEP 3 -#define SYS_YIELD 4 -#define SYS_ALLOC 5 -#define SYS_FREE 6 -#define SYS_CREATE_THREAD 7 -#define SYS_OPEN_HANDLE 8 -#define SYS_CLOSE_HANDLE 9 -#define SYS_MUTEX_CREATE 10 -#define SYS_MUTEX_ACQUIRE 11 -#define SYS_MUTEX_RELEASE 12 -#define SYS_SEM_CREATE 13 -#define SYS_SEM_WAIT 14 -#define SYS_SEM_SIGNAL 15 -#define SYS_EVENT_CREATE 16 -#define SYS_EVENT_SET 17 -#define SYS_EVENT_WAIT 18 -#define SYS_QUEUE_CREATE 19 -#define SYS_QUEUE_SEND 20 -#define SYS_QUEUE_RECV 21 -#define SYS_SCREEN_BLIT 22 -#define SYS_GET_TICK 23 - -#define SYS_MAX 24 -``` - -### 8.4 User-Side Stubs - syscall.dll - -Each syscall is a MASM64 procedure. `syscall.dll` exports them by name. User code links -against `syscall.dll` and calls them as normal functions. - -```asm -; syscall_stubs.asm -; Microsoft x64 ABI: args in RCX, RDX, R8, R9, then stack. -; We move arg 5 and 6 into R10 and R11 before INT. - -SysYield PROC - mov rax, SYS_YIELD - int 02Eh - ret -SysYield ENDP - -SysAlloc PROC - ; RCX = size (already in place) - mov rax, SYS_ALLOC - int 02Eh - ret -SysAlloc ENDP - -SysFree PROC - ; RCX = ptr, RDX = size - mov rax, SYS_FREE - int 02Eh - ret -SysFree ENDP - -; ... one PROC per SYS_* constant -``` - ---- - -## 9. Driver Model - -### 9.1 drivers/drivers.ini - -```ini -[drivers] -ide.sys -ahci.sys -``` - -All listed drivers are loaded unconditionally. Each one's `DriverEntry` is called after the -HAL is initialised. A driver that finds no matching hardware returns `STATUS_NOT_FOUND` - this -is logged via `DbgPrint` and the kernel continues. Nothing is skipped based on load order. - -### 9.2 Driver Interface - -```c -// io_driver.h - -#pragma once -#include "hal_api.h" -#include "ke_status.h" - -/* - * Every .sys exports these two symbols. - * - * DriverEntry - probe for hardware, register services, return status. - * DriverUnload - cleanup (reserved for future use). - */ -typedef KSTATUS (*DRIVER_ENTRY_FN)(const HAL_API* hal); -typedef void (*DRIVER_UNLOAD_FN)(void); - -/* Disk driver registration - called by a driver inside DriverEntry - when it has found and initialised a disk controller. - Exported by hal.dll. */ -typedef struct DISK_DRIVER { - KSTATUS (*ReadSectors)(uint64_t lba, uint32_t count, void* buf); - KSTATUS (*WriteSectors)(uint64_t lba, uint32_t count, const void* buf); - uint64_t SectorCount; -} DISK_DRIVER; - -KSTATUS IoRegisterDisk(uint8_t driveIndex, const DISK_DRIVER* driver); -``` - ---- - -## 10. Win32 Personality - win32.dll - -The Win32 personality is implemented as a DLL (`win32.dll`) that provides a user-mode API compatible with Windows. It translates Win32 API calls into equivalent system calls to the kernel. -It does not implement the full Win32 API, but provides a subset sufficient for basic applications. -The API includes functions for process and thread management, synchronization primitives, file I/O, and basic GUI operations (stubs for now, no graphics yet). - ---- - -## 11. Data Structures Reference - -### Intrusive Doubly-Linked List - -```c -// ke_list.h - -#pragma once -#include - -typedef struct LIST_ENTRY { - struct LIST_ENTRY* Fwd; - struct LIST_ENTRY* Bwd; -} LIST_ENTRY; - -#define LIST_INIT(head) ((head)->Fwd = (head)->Bwd = (head)) -#define LIST_IS_EMPTY(head) ((head)->Fwd == (head)) - -/* Recover the containing struct pointer from an embedded LIST_ENTRY. */ -#define CONTAINING_RECORD(ptr, type, member) \ - ((type *)((char *)(ptr) - offsetof(type, member))) - -static inline void ListInsertTail(LIST_ENTRY* head, LIST_ENTRY* node) { - node->Bwd = head->Bwd; - node->Fwd = head; - head->Bwd->Fwd = node; - head->Bwd = node; -} - -static inline void ListRemove(LIST_ENTRY* node) { - node->Bwd->Fwd = node->Fwd; - node->Fwd->Bwd = node->Bwd; - node->Fwd = node->Bwd = node; /* self-loop: poison against double-remove */ -} -``` - ---- - -## 12. File Layout - -``` -ESP/ -├── boot.ini -├── bootx64.efi -├── kernel.exe -├── hal.dll -├── syscall.dll -├── win32.dll -└── drivers/ - ├── drivers.ini - ├── ide.sys - └── ahci.sys - └── fat32.sys - -src/ -├── boot/ -│ ├── main.c UefiMain, image loading, PML4 setup, MEMORY_MAP conversion -│ ├── ini_parser.c Minimal boot.ini / drivers.ini reader -│ └── ldr_image.c symlink to kernel's loader. -│ -├── kernel/ -│ ├── kmain.c KernelMain(BOOT_INFO *) -│ ├── boot_info.h BOOT_INFO structure -│ ├── ke_status.h KSTATUS codes -│ ├── ke_list.h LIST_ENTRY, ListInsertTail, ListRemove -│ ├── ke_spinlock.h SPINLOCK, KeAcquireSpinlock, KeReleaseSpinlock -│ ├── ob_object.c/h OBJECT, ObAllocateObject, ObReferenceObject, ObDereferenceObject -│ ├── mm_phys.c/h PMM: MmAllocateFrame, MmFreeFrame -│ ├── mm_virt.c/h VMM: MmMapPage, MmAllocatePool, MmAllocateUserMemory -│ ├── ke_process.c/h PROCESS, KeCreateProcess, KeExitProcess -│ ├── ke_thread.c/h THREAD, KeCreateThread, KeExitThread -│ ├── ke_sched.c/h MLFQ scheduler, KeSchedulerTick, KeYield -│ ├── ke_switch.asm KeSwitchContext (MASM64) -│ ├── ke_mutex.c/h MUTEX, KeAcquireMutex, KeReleaseMutex -│ ├── ke_semaphore.c/h SEMAPHORE, KeWaitSemaphore, KeSignalSemaphore -│ ├── ke_event.c/h EVENT, KeSignalEvent, KeWaitForEvent -│ ├── ke_queue.c/h QUEUE, KeSendMessage, KeReceiveMessage -│ ├── ke_syscall.c KeSyscallDispatch, syscall table -│ ├── ke_syscall.asm KeSyscallEntry INT 0x2E stub (MASM64) -│ ├── ldr_image.c/h LdrLoadImage, LdrResolveImport, LdrGetExport -│ └── memory_map.h MEMORY_MAP, MEMORY_REGION, MEMORY_TYPE -│ -├── hal/ -│ ├── hal_main.c HalInitialize, HalGetApi, HalDebugEnabled -│ ├── hal_debug.c DbgPrint (printf-style, runtime-gated) -│ ├── hal_screen.c GOP framebuffer, 8x16 ASCII font -│ ├── hal_keyboard.c PS/2 + IRQ 1 -│ ├── hal_timer.c HPET / APIC timer fallback -│ ├── hal_pci.c Mechanism-1 config space -│ ├── hal_disk.c Drive registry, IoRegisterDisk -│ └── hal_apic.c Local APIC + I/O APIC, MADT parsing -│ -├── syscall/ -│ └── syscall_stubs.asm SysYield, SysAlloc, SysFree, ... (MASM64) -│ -├── win32/ -│ ├── kernel32.c -│ ├── user32.c -│ ├── ole32.c -│ └── winmm.c -│ -└── drivers/ - ├── ide/ - │ └── ide.c - └── ahci/ - └── ahci.c -``` diff --git a/OVMF/OVMF_VARS_4M.fd b/OVMF/OVMF_VARS_4M.fd index d1474c3f3c273e7b0c86a5d3f34a6368319d26b8..d0452e99442a9ba0b394aba6996ff68c8a2afad2 100644 GIT binary patch delta 441 zcmZo@P;6*W+~D_Ma@1<4%@ZD6;@a%zXU8&m!TVd2?}hkHws{}~l41P2dE+c|ejt}` z^0oI%P^A9hmimuViiv@FbDc9EKe|Osj5x%ZaELSG5NE+5&Wb~v4Tm^84si|~;+#0d vxp0VcV~B?^@qolGa5X2rZclp6xIO7L(>>`n0hTra);0mQHUai!0vrYaLJ-0{ delta 59 zcmZo@P;6*W+~D_MI^Ro1r_B={T;gh1`oajrOxu;dFyE7I-|&-h`-Y!P2RPaUSlR?w P+XUF!1lX4ea2Nmp8V47y diff --git a/code/kernel/kmain.c b/code/kernel/kmain.c index b7fc887..07b08fe 100644 --- a/code/kernel/kmain.c +++ b/code/kernel/kmain.c @@ -78,9 +78,8 @@ typedef KSTATUS (*DRIVER_ENTRY_FN)(const HAL_API *); // Phase Helper: Resolve Imports for all registered modules // --------------------------------------------------------------------------- static void ResolveSystemImports(BOOT_INFO* bootInfo) { - // Resolve imports for all loaded modules (drivers may import from hal.dll, - // win32.dll imports from syscall.dll, etc). - // Starting at 2 to skip the kernel and HAL base images, as per your design. + // Resolve imports for all loaded modules (drivers may import from hal.dll, win32.dll imports from syscall.dll, etc). + // Starting at 2 to skip the kernel and HAL base images. for (uint32_t i = 2; i < bootInfo->ModuleCount; i++) { LdrResolveImports(bootInfo->Modules[i].VirtBase); } @@ -112,7 +111,7 @@ static void InitializeDrivers(BOOT_INFO* bootInfo, const HAL_API* hal) { // Identify .sys files by extension size_t len = strlen(name); - if (len < 4 || strcmp(name + len - 4, ".sys") != 0) continue; + if (len < 4 || strcmp(name + len - 4, ".sys")) continue; DbgPrint("KernelMain: Executing DriverEntry for %s\n", name); @@ -235,7 +234,7 @@ static void LoadAndRegisterDll(const char* filename) { IoCloseFile(f); // Map the DLL into memory - // Passing 'true' for user-mode so init.exe can access its code + // Passing 'true' for user-mode uintptr_t mappedBase = 0; s = LdrLoadImage(mem, size, &mappedBase, true); if (!KSUCCESS(s)) { @@ -384,8 +383,6 @@ extern void KernelMain(BOOT_INFO* bootInfo) { // KeInitializeScheduler(); // hal->Screen.Clear(0x000000); - DbgPrint("KernelMain: initialisation complete. %.2f MB free.\n", - (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f)); DbgPrint("KernelMain: Loading system DLLs...\n"); LoadAndRegisterDll("syscall.dll"); @@ -406,6 +403,15 @@ extern void KernelMain(BOOT_INFO* bootInfo) { DbgPrint("KernelMain: Starting scheduler... Handing over control.\n"); _enable(); + float freeMB = (float)MmFreeFrameCount() * PAGE_SIZE / (1024.0f * 1024.0f); + float kernelMB = (float)MmGetKernelPoolUsage() / (1024.0f * 1024.0f); + float userMB = (float)MmGetUserHeapUsage() / (1024.0f * 1024.0f); + + DbgPrint("Memory Status:\n"); + DbgPrint(" -> Free (PMM): %.2f MB\n", freeMB); + DbgPrint(" -> Kernel Pool: %.2f MB\n", kernelMB); + DbgPrint(" -> User Heap: %.2f MB\n", userMB); + while (1) { //DbgPrint("KernelMain: Idle loop. Free memory: %.2f MB.\n", //(float)(MmFreeFrameCount() * PAGE_SIZE) / (1024.0f * 1024.0f)); diff --git a/code/kernel/ldr_image.c b/code/kernel/ldr_image.c index 8437ae8..888dde7 100644 --- a/code/kernel/ldr_image.c +++ b/code/kernel/ldr_image.c @@ -225,13 +225,16 @@ KSTATUS LdrResolveImports(uintptr_t imageBase) { PE_HEADER* pe = (PE_HEADER *)(imageBase + peOffset); OPT_HEADER64* opt = &pe->OptionalHeader; - if (opt->NumberOfRvaAndSizes < 2) return STATUS_SUCCESS; // no imports + if (opt->NumberOfRvaAndSizes < 2) return STATUS_SUCCESS; // no imports uint32_t impRva = opt->DataDirectory[1].VirtualAddress; if (!impRva) return STATUS_SUCCESS; IMPORT_DESC* imp = (IMPORT_DESC *)(imageBase + impRva); while (imp->Name) { const char* dllName = (const char *)(imageBase + imp->Name); + if (!_stricmp(dllName, "kernel32.dll") || !_stricmp(dllName, "user32.dll") + || !_stricmp(dllName, "ole32.dll") || !_stricmp(dllName, "winmm.dll")) + dllName = "win32.dll"; uint64_t* thunk = (uint64_t *)(imageBase + imp->FirstThunk); uint64_t* origThunk = (uint64_t *)(imageBase + (imp->OriginalFirstThunk ? imp->OriginalFirstThunk diff --git a/code/kernel/mm_virt.c b/code/kernel/mm_virt.c index e80801e..8d89564 100644 --- a/code/kernel/mm_virt.c +++ b/code/kernel/mm_virt.c @@ -443,3 +443,13 @@ void* MmReallocatePool(void* ptr, size_t newSize) { return HeapRealloc(&gPoolHea void* MmAllocateUserMemory(size_t size) { return HeapAlloc(&gUserHeap, size); } void MmFreeUserMemory(void* ptr, size_t size) { (void)size; HeapFree(&gUserHeap, ptr); } void* MmReallocateUserMemory(void* ptr, size_t newSize) { return HeapRealloc(&gUserHeap, ptr, newSize); } + +size_t MmGetKernelPoolUsage(void) { + // Returns bytes committed to the kernel pool + return (size_t)(gPoolHeap.Brk - gPoolHeap.Start); +} + +size_t MmGetUserHeapUsage(void) { + // Returns bytes committed to the user heap + return (size_t)(gUserHeap.Brk - gUserHeap.Start); +} diff --git a/code/kernel/mm_virt.h b/code/kernel/mm_virt.h index 9bda186..2bc0ff0 100644 --- a/code/kernel/mm_virt.h +++ b/code/kernel/mm_virt.h @@ -68,3 +68,6 @@ void* MmReallocatePool(void* ptr, size_t newSize); void* MmAllocateUserMemory(size_t size); void MmFreeUserMemory(void* ptr, size_t size); void* MmReallocateUserMemory(void* ptr, size_t newSize); + +size_t MmGetKernelPoolUsage(void); +size_t MmGetUserHeapUsage(void); diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..9a2fc42 --- /dev/null +++ b/readme.md @@ -0,0 +1,86 @@ +# an x86-64 kernel + +boots. runs processes. doesn't crash (usually). + +## what is it? + +a 64-bit operating system kernel written from scratch. +UEFI bootloader, physical and virtual memory managers, preemptive scheduler, HAL, VFS, PE loader, partial Win32 layer. +it boots into a shell. +~20k lines of code. + +## features + +- UEFI bootloader with PE loading and base relocation +- physical memory manager (frame stack, O(1) alloc/free) +- virtual memory manager with kernel pool +- HAL (screen, keyboard, timer, disk, PCI, APIC) +- preemptive scheduler with priority levels +- processes and threads +- VFS with FAT32 driver (read-only) +- drive-letter paths (`0:\apps\shell.exe`) +- INI-based boot and driver configuration +- partial Win32 layer (kernel32 and user32, far from finished) +- PE import resolution and dynamic driver loading + +## how 2 build + +* open vs x64 build tools +* cd into this directory +* `build.bat` +* open `run.bat`/`rund.bat` +* replace `\\.\PhysicalDrive7` with your thumbdrive partition +* `run.bat`/`rund.bat` +* enjoy + +## driver model + +drivers are PE32+ `.sys` files with an entry point preferably named DriverEntry accepting a constant pointer to HAL_API. + +drivers to be loaded have their path stored in `drivers.ini`, split into essential for boot (loaded by bootloader) and not required for boot (loaded by kernel). + +a disk driver fills in a `DISK_DRIVER` vtable and calls `IoRegisterDisk(index, &driver)`. +a filesystem driver fills in an `IO_FILESYSTEM` vtable and calls `IoRegisterFilesystem(index, &fs, &vol)`. + +## how it boots + +1. UEFI loads `boot.ini`, reads `drivers.ini` +2. bootloader maps kernel, HAL, and boot drivers into the upper half +3. direct physical window at `0xFFFF880000000000` +4. jumps to `KernelMain` +5. PMM, VMM, GDT, IDT, HAL, IRQs, scheduler +6. loads runtime drivers from disk +7. launches `shell.exe` + +## paths + +``` +"apps\shell.exe" -> drive 0 +"0:\apps\shell.exe" -> drive 0 +"2:\data\config.ini" -> drive 2 +``` + +drives 0–7. a drive only works if a filesystem registered on it. + +## faq + +Q: Why? +A: Why not. + +Q: Does it run Doom? +A: Not yet. + +Q: Is the Win32 layer real? +A: Real enough to load binaries that expect it. Not real enough to run Notepad. + +Q: Is the scheduler preemptive? +A: Yes. + +Q: Will it corrupt my data? +A: How would it? It's read-only. + +Q: Is this finished? +A: Not in the slightest. + +Q: Can I use this in production? +A: Don't.