diff --git a/kernel/src/Api/Heap.hpp b/kernel/src/Api/Heap.hpp index 6078c3f..986e96b 100644 --- a/kernel/src/Api/Heap.hpp +++ b/kernel/src/Api/Heap.hpp @@ -61,22 +61,10 @@ namespace Zenith { return userVa; } - // Free all heap allocations for a process slot (called on process exit) - static void CleanupHeapForSlot(int slot, uint64_t pml4Phys) { + // Reset heap allocation tracking for a process slot. + // The actual physical pages are freed by Paging::FreeUserHalf() during process cleanup. + static void CleanupHeapForSlot(int slot, uint64_t /*pml4Phys*/) { if (slot < 0 || slot >= Sched::MaxProcesses) return; - - for (int i = 0; i < g_heapAllocCount[slot]; i++) { - uint64_t va = g_heapAllocs[slot][i].va; - uint64_t numPages = g_heapAllocs[slot][i].numPages; - for (uint64_t p = 0; p < numPages; p++) { - uint64_t pageVa = va + p * 0x1000; - uint64_t physAddr = Memory::VMM::Paging::GetPhysAddr(pml4Phys, pageVa); - if (physAddr != 0) { - Memory::g_pfa->Free((void*)Memory::HHDM(physAddr)); - } - Memory::VMM::Paging::UnmapUserIn(pml4Phys, pageVa); - } - } g_heapAllocCount[slot] = 0; } diff --git a/kernel/src/Api/Process.hpp b/kernel/src/Api/Process.hpp index f595fba..36ddf1d 100644 --- a/kernel/src/Api/Process.hpp +++ b/kernel/src/Api/Process.hpp @@ -8,6 +8,9 @@ #pragma once #include #include +#include +#include +#include #include "Syscall.hpp" #include "WinServer.hpp" @@ -105,9 +108,34 @@ namespace Zenith { auto* proc = Sched::GetProcessByPid(pid); if (!proc) return -1; - // Clean up any windows owned by this process + // 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; } diff --git a/kernel/src/Api/WinServer.cpp b/kernel/src/Api/WinServer.cpp index 116ecf6..f5aed90 100644 --- a/kernel/src/Api/WinServer.cpp +++ b/kernel/src/Api/WinServer.cpp @@ -10,6 +10,7 @@ #include #include #include +#include namespace WinServer { @@ -229,6 +230,19 @@ namespace WinServer { if (g_slots[i].used && g_slots[i].ownerPid == pid) { Kt::KernelLogStream(Kt::INFO, "WinServer") << "Cleaning up window " << i << " for exited PID " << pid; + + // Unmap pixel pages from desktop's address space to prevent stale access + if (g_slots[i].desktopVa != 0 && g_slots[i].desktopPid != 0) { + auto* desktopProc = Sched::GetProcessByPid(g_slots[i].desktopPid); + if (desktopProc) { + for (int p = 0; p < g_slots[i].pixelNumPages; p++) { + Memory::VMM::Paging::UnmapUserIn( + desktopProc->pml4Phys, + g_slots[i].desktopVa + (uint64_t)p * 0x1000); + } + } + } + g_slots[i].used = false; } } diff --git a/kernel/src/Memory/PageFrameAllocator.cpp b/kernel/src/Memory/PageFrameAllocator.cpp index 2aa86d4..e547da5 100644 --- a/kernel/src/Memory/PageFrameAllocator.cpp +++ b/kernel/src/Memory/PageFrameAllocator.cpp @@ -69,25 +69,43 @@ namespace Memory { } void* PageFrameAllocator::ReallocConsecutive(void* ptr, int n) { - auto first = Allocate(); + Lock.Acquire(); - for (int i = 0; i < n - 1; i++) { - if (Allocate() == nullptr) { - Panic("PageFrameAllocator Reallocation failed", nullptr); - }; + // Search the free list for a single contiguous region >= n pages. + // The old implementation assumed N consecutive Allocate() calls gave + // adjacent pages, which breaks when individual pages are freed back. + size_t needed = (size_t)n * 0x1000; + Page* current = head.next; + Page* prev = &head; + + while (current != nullptr) { + if (current->size >= needed) { + // Carve from the top of this contiguous region + current->size -= needed; + void* base; + if (current->size == 0) { + base = (void*)current; + prev->next = current->next; + } else { + base = (void*)((uint64_t)current + current->size); + } + + Lock.Release(); + + if (ptr != nullptr) { + memcpy(base, ptr, 0x1000); // copy one page (ptr is always a single page) + Free(ptr); + } + + return base; + } + prev = current; + current = current->next; } - // Allocate() returns pages from the top of a free region in descending - // order, so 'first' is the highest address. The contiguous block - // actually starts (n-1) pages below 'first'. - void* base = (void*)((uint64_t)first - (uint64_t)(n - 1) * 0x1000); - - if (ptr != nullptr) { - memcpy(base, ptr, (uint64_t)n * 0x1000); - Free(ptr); - } - - return base; + Lock.Release(); + Panic("PageFrameAllocator: no contiguous region available", nullptr); + return nullptr; } void PageFrameAllocator::Free(void* ptr) { @@ -102,7 +120,7 @@ namespace Memory { void PageFrameAllocator::Free(void* ptr, int n) { for (int i = 0; i < n; i++) { - Free((void*)(uint64_t)ptr + (0x1000 * n)); + Free((void*)((uint64_t)ptr + 0x1000 * i)); } } diff --git a/kernel/src/Memory/Paging.cpp b/kernel/src/Memory/Paging.cpp index 2520a0b..c62c2b3 100644 --- a/kernel/src/Memory/Paging.cpp +++ b/kernel/src/Memory/Paging.cpp @@ -268,6 +268,60 @@ namespace Memory::VMM { asm volatile("invlpg (%0)" :: "r"(virtualAddress) : "memory"); } + void Paging::FreeUserHalf(std::uint64_t pml4Phys) { + // PageTable* pointers store PHYSICAL addresses (same convention as MapUserIn/UnmapUserIn). + // Each individual entry access goes through HHDM() to get a valid virtual pointer. + PageTable* pml4 = (PageTable*)pml4Phys; + + // Walk user-half entries (0-255); kernel-half (256-511) is shared and must not be touched. + for (int i4 = 0; i4 < 256; i4++) { + PageTableEntry* pml4e = (PageTableEntry*)Memory::HHDM(&pml4->entries[i4]); + if (!pml4e->Present) continue; + + uint64_t pdptPhys = (uint64_t)pml4e->Address << 12; + PageTable* pdpt = (PageTable*)pdptPhys; + + for (int i3 = 0; i3 < 512; i3++) { + PageTableEntry* pdpte = (PageTableEntry*)Memory::HHDM(&pdpt->entries[i3]); + if (!pdpte->Present) continue; + + uint64_t pdPhys = (uint64_t)pdpte->Address << 12; + PageTable* pd = (PageTable*)pdPhys; + + for (int i2 = 0; i2 < 512; i2++) { + PageTableEntry* pde = (PageTableEntry*)Memory::HHDM(&pd->entries[i2]); + if (!pde->Present) continue; + + uint64_t ptPhys = (uint64_t)pde->Address << 12; + PageTable* pt = (PageTable*)ptPhys; + + // Free all leaf physical pages + for (int i1 = 0; i1 < 512; i1++) { + PageTableEntry* pte = (PageTableEntry*)Memory::HHDM(&pt->entries[i1]); + if (!pte->Present) continue; + + // Skip MMIO/WC pages (not PFA-managed) + if (pte->WriteThrough || pte->CacheDisabled) continue; + + uint64_t pagePhys = (uint64_t)pte->Address << 12; + if (pagePhys != 0) { + Memory::g_pfa->Free((void*)Memory::HHDM(pagePhys)); + } + } + + // Free the PT page itself + Memory::g_pfa->Free((void*)Memory::HHDM(ptPhys)); + } + + // Free the PD page + Memory::g_pfa->Free((void*)Memory::HHDM(pdPhys)); + } + + // Free the PDPT page + Memory::g_pfa->Free((void*)Memory::HHDM(pdptPhys)); + } + } + std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) { VirtualAddress virtualAddressObj(virtualAddress); diff --git a/kernel/src/Memory/Paging.hpp b/kernel/src/Memory/Paging.hpp index 73b33b3..0b17f0b 100644 --- a/kernel/src/Memory/Paging.hpp +++ b/kernel/src/Memory/Paging.hpp @@ -112,6 +112,11 @@ public: // Unmap a single page from an arbitrary PML4 (clears PTE + invalidates TLB). static void UnmapUserIn(std::uint64_t pml4Phys, std::uint64_t virtualAddress); + // Free all user-half page table structures and physical pages (PML4 entries 0-255). + // Does NOT free the PML4 page itself (caller handles that). + // Skips MMIO/WC pages (WriteThrough or CacheDisabled set in PTE). + static void FreeUserHalf(std::uint64_t pml4Phys); + // Identity-map EFI runtime service regions so firmware code can // reference its own data at physical addresses. void MapEfiRuntime(limine_efi_memmap_response* efiMemmap); diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index a7574a7..f4a83be 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -239,9 +239,19 @@ namespace Sched { } void Schedule() { - // Reclaim terminated process slots so they can be reused + // Reclaim terminated process slots — free deferred resources first for (int i = 0; i < MaxProcesses; i++) { if (processTable[i].state == ProcessState::Terminated) { + // Free kernel stack (deferred from ExitProcess — can't free while running on it) + if (processTable[i].stackBase != 0) { + Memory::g_pfa->Free((void*)processTable[i].stackBase, StackPages); + processTable[i].stackBase = 0; + } + // Free the PML4 page (deferred from ExitProcess — can't free while it's CR3) + if (processTable[i].pml4Phys != 0) { + Memory::g_pfa->Free((void*)Memory::HHDM(processTable[i].pml4Phys)); + processTable[i].pml4Phys = 0; + } processTable[i].state = ProcessState::Free; } } @@ -322,10 +332,27 @@ namespace Sched { return; } - // Clean up any windows owned by this process - WinServer::CleanupProcess(processTable[currentPid].pid); + Process& proc = processTable[currentPid]; - processTable[currentPid].state = ProcessState::Terminated; + // Clean up any windows owned by this process (unmaps pixel pages from desktop) + WinServer::CleanupProcess(proc.pid); + + // Free I/O redirect buffers (kernel-allocated pages) + 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 physical pages and page table structures (entries 0-255). + // This covers ELF code/data, user stack, exit stub, heap, and window pixel buffers. + // The PML4 page itself is freed later in Schedule() (can't free while it's CR3). + Memory::VMM::Paging::FreeUserHalf(proc.pml4Phys); + + proc.state = ProcessState::Terminated; int next = -1; for (int i = 0; i < MaxProcesses; i++) { diff --git a/programs/src/fontpreview/main.cpp b/programs/src/fontpreview/main.cpp index e8650b4..ba40a8e 100644 --- a/programs/src/fontpreview/main.cpp +++ b/programs/src/fontpreview/main.cpp @@ -1,6 +1,6 @@ /* * main.cpp - * ZenithOS Font Preview - standalone Window Server process + * ZenithOS Font Preview app * Displays TTF font samples at multiple sizes with vertical scrolling * Copyright (c) 2026 Daniel Hammer */ @@ -14,7 +14,7 @@ using namespace gui; // ============================================================================ -// Constants — light theme matching ZenithOS desktop conventions +// Constants // ============================================================================ static constexpr int INIT_W = 800; @@ -40,7 +40,7 @@ static const char* UPPER = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; static const char* LOWER_NUM = "abcdefghijklmnopqrstuvwxyz 0123456789"; // ============================================================================ -// Pre-rendered glyph cache — rasterised once at startup for all 7 sizes +// Pre-rendered glyph cache // ============================================================================ struct PreviewGlyph { diff --git a/ramdisk.tar b/ramdisk.tar index 824c7e6..bd13476 100644 Binary files a/ramdisk.tar and b/ramdisk.tar differ