Skip to content

Memory Management

NtinosTheGamer2324 edited this page Dec 11, 2025 · 1 revision

Memory Management

ModuOS implements a three-tier memory management system: physical memory allocation, virtual memory (paging), and kernel heap management.

Overview

┌─────────────────────────────────────────────┐
│         Kernel Heap (kmalloc/kfree)         │
│              kheap.c                        │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│      Virtual Memory (Paging)                │
│      4-Level Page Tables (PML4)             │
│              paging.c                       │
└──────────────────┬──────────────────────────┘
                   │
┌──────────────────▼──────────────────────────┐
│      Physical Memory Allocator              │
│      Bitmap-based Page Allocator            │
│              phys.c                         │
└──────────────────┬──────────────────────────┘
                   │
              Physical RAM

Physical Memory Management

File: src/kernel/memory/phys.c

Architecture

The physical memory allocator manages RAM at 4KB page granularity using a bitmap.

Data Structures:

static uint64_t *phys_bitmap = NULL;    // Bitmap: 1 bit per page
static uint64_t phys_total_pages = 0;   // Total pages in system
static uint64_t phys_used_pages = 0;    // Currently allocated pages
static uint64_t phys_total_memory = 0;  // Total memory in bytes

Initialization

void phys_init(uint64_t total_mem, 
               const void *usable_regions, 
               size_t region_count);

Steps:

  1. Parse Multiboot2 memory map
  2. Calculate total pages
  3. Allocate bitmap (1 bit per 4KB page)
  4. Mark all memory as used
  5. Mark usable regions as free

Memory Map Types (from Multiboot2):

  • MULTIBOOT_MEMORY_AVAILABLE (1): Usable RAM
  • MULTIBOOT_MEMORY_RESERVED (2): Reserved by BIOS
  • MULTIBOOT_MEMORY_ACPI_RECLAIMABLE (3): ACPI tables
  • MULTIBOOT_MEMORY_NVS (4): Non-volatile storage
  • MULTIBOOT_MEMORY_BADRAM (5): Defective RAM

Page Allocation

void* phys_alloc_page(void);        // Allocate single 4KB page
void* phys_alloc_pages(size_t n);   // Allocate n contiguous pages
void phys_free_page(void* addr);    // Free single page
void phys_free_pages(void* addr, size_t n);  // Free n pages

Algorithm (First-Fit):

// Find first free page
for (uint64_t i = 0; i < phys_total_pages; i++) {
    uint64_t bitmap_index = i / 64;
    uint64_t bit_index = i % 64;
    
    if (!(phys_bitmap[bitmap_index] & (1ULL << bit_index))) {
        // Page is free
        phys_bitmap[bitmap_index] |= (1ULL << bit_index);  // Mark used
        return (void*)(i * PAGE_SIZE);
    }
}

Memory Statistics

uint64_t phys_get_total_memory(void);    // Total RAM
uint64_t phys_get_used_memory(void);     // Used RAM
uint64_t phys_get_free_memory(void);     // Available RAM

Virtual Memory (Paging)

File: src/kernel/memory/paging.c

4-Level Paging Structure

ModuOS uses the x86-64 4-level paging hierarchy:

Virtual Address (48-bit):
┌──────┬──────┬──────┬──────┬──────────────┐
│ PML4 │ PDPT │  PD  │  PT  │    Offset    │
│ 9bit │ 9bit │ 9bit │ 9bit │    12bit     │
└──────┴──────┴──────┴──────┴──────────────┘
  47:39  38:30  29:21  20:12     11:0

PML4 Entry → PDPT (512 entries × 512GB = 256TB)
PDPT Entry → PD   (512 entries × 1GB)
PD Entry   → PT   (512 entries × 2MB)
PT Entry   → Page (512 entries × 4KB)

Entry Format (64-bit):

Bit 0:    Present (P)
Bit 1:    Read/Write (R/W)
Bit 2:    User/Supervisor (U/S)
Bit 3:    Page-level Write-Through (PWT)
Bit 4:    Page-level Cache Disable (PCD)
Bit 5:    Accessed (A)
Bit 6:    Dirty (D)
Bit 7:    Page Size (PS) - 1=2MB/1GB page
Bit 8:    Global (G)
Bits 9-11: Available for OS
Bits 12-51: Physical address (4KB aligned)
Bit 63:   Execute Disable (XD/NX)

Initialization

void paging_init(void);

Steps:

  1. Allocate PML4, PDPT, PD tables
  2. Identity map first 512MB (kernel code/data)
  3. Set up recursive mapping (optional)
  4. Load CR3 with PML4 address
  5. Enable paging (already enabled from boot)

Identity Mapping

Maps virtual address = physical address for kernel region:

void early_identity_map(void);        // Map first 512MB
void early_identity_map_all(void);    // Map all available RAM

Usage: Kernel can directly access physical addresses without translation during early boot.

Page Table Operations

// Map virtual address to physical frame
int paging_map_page(uint64_t virt_addr, uint64_t phys_addr, uint64_t flags);

// Unmap virtual address
void paging_unmap_page(uint64_t virt_addr);

// Get physical address from virtual
uint64_t paging_get_physical(uint64_t virt_addr);

// Create new page directory
uint64_t* paging_create_directory(void);

// Switch page directory
void paging_switch_directory(uint64_t* pml4);

Page Flags

#define PAGE_PRESENT    (1 << 0)
#define PAGE_WRITE      (1 << 1)
#define PAGE_USER       (1 << 2)
#define PAGE_WRITETHROUGH (1 << 3)
#define PAGE_CACHE_DISABLE (1 << 4)
#define PAGE_ACCESSED   (1 << 5)
#define PAGE_DIRTY      (1 << 6)
#define PAGE_HUGE       (1 << 7)  // 2MB or 1GB page
#define PAGE_GLOBAL     (1 << 8)
#define PAGE_NO_EXECUTE (1ULL << 63)

TLB Management

static inline void tlb_flush_all(void) {
    uint64_t cr3;
    __asm__ volatile("mov %%cr3, %0" : "=r"(cr3));
    __asm__ volatile("mov %0, %%cr3" : : "r"(cr3) : "memory");
}

static inline void tlb_flush_page(uint64_t virt_addr) {
    __asm__ volatile("invlpg (%0)" : : "r"(virt_addr) : "memory");
}

Kernel Heap

File: src/kernel/memory/kheap.c

Dynamic Memory Allocation

The kernel heap provides malloc-like functionality for kernel code.

API:

void* kmalloc(size_t size);                    // Allocate memory
void* kmalloc_aligned(size_t size, size_t alignment);  // Aligned allocation
void* kzalloc(size_t size);                    // Zero-initialized allocation
void kfree(void* ptr);                         // Free memory
void kheap_stats(void);                        // Print heap statistics

Heap Structure

Implementation: Simple linked-list allocator

typedef struct heap_block {
    size_t size;              // Block size (excluding header)
    int used;                 // 1 if allocated, 0 if free
    struct heap_block *next;  // Next block in list
} heap_block_t;

Memory Layout:

Heap Start
  ↓
┌──────────────┬─────────────┬──────────────┬─────────────┐
│ Block Header │    Data     │ Block Header │    Data     │ ...
│   used=1     │   (alloc)   │   used=0     │   (free)    │
└──────────────┴─────────────┴──────────────┴─────────────┘

Allocation Algorithm

First-Fit Strategy:

  1. Traverse linked list
  2. Find first free block ≥ requested size
  3. Split block if too large
  4. Mark block as used
  5. Return pointer to data area (after header)

Coalescing

Free Block Merging:

void kfree(void* ptr) {
    // Mark block as free
    block->used = 0;
    
    // Merge with next block if free
    if (block->next && !block->next->used) {
        block->size += sizeof(heap_block_t) + block->next->size;
        block->next = block->next->next;
    }
}

Heap Growth

Currently fixed-size heap. Future versions may expand dynamically:

#define HEAP_INITIAL_SIZE  (1 * 1024 * 1024)  // 1MB
#define HEAP_MAX_SIZE      (16 * 1024 * 1024) // 16MB

Memory Layout

Physical Memory Map

0x00000000 - 0x000FFFFF : Real Mode region (1MB)
  0x00000000 - 0x000003FF : Real Mode IVT
  0x00000400 - 0x000004FF : BIOS Data Area
  0x00000500 - 0x00007BFF : Free conventional memory
  0x00007C00 - 0x00007DFF : Boot sector (GRUB)
  0x00007E00 - 0x0009FFFF : Free conventional memory
  0x000A0000 - 0x000BFFFF : Video memory
  0x000C0000 - 0x000FFFFF : BIOS ROM

0x00100000 - 0x???????? : Kernel (loaded by GRUB)
0x???????? - 0x???????? : Kernel heap
0x???????? - 0x???????? : Available RAM

Virtual Memory Layout

Current (Identity Mapped):

0x00000000 - 0x1FFFFFFF : Identity mapped (512MB)
  0x00100000 - 0x00?????? : Kernel code/data
  0x00?????? - 0x00?????? : Kernel heap
  0x00?????? - 0x1FFFFFFF : Available

0xFFFFFFFF80000000 - ... : Higher-half kernel (future)

Future (Higher-Half Kernel):

0x00000000 - 0x00007FFFFFFFFFFF : User space (128TB)
0x0000800000000000 - 0xFFFF7FFFFFFFFFFF : Non-canonical (unusable)
0xFFFF800000000000 - 0xFFFFFFFFFFFFFFFF : Kernel space (128TB)
  0xFFFFFFFF80000000 - 0xFFFFFFFFFFFFFFFF : Kernel code/data
  0xFFFFFFFFC0000000 - 0xFFFFFFFFFFFFFFFF : Kernel heap

Memory Protection

Kernel vs. User Space

Ring Levels:

  • Ring 0 (Supervisor): Kernel code, full access
  • Ring 3 (User): User programs, restricted access

Page Protection:

// Kernel page (Ring 0 only)
flags = PAGE_PRESENT | PAGE_WRITE;

// User page (Ring 3 accessible)
flags = PAGE_PRESENT | PAGE_WRITE | PAGE_USER;

No Execute (NX)

Prevent code execution from data pages:

// Data page (no execute)
flags = PAGE_PRESENT | PAGE_WRITE | PAGE_NO_EXECUTE;

// Code page (executable)
flags = PAGE_PRESENT;  // No WRITE, no NO_EXECUTE

Performance Considerations

TLB (Translation Lookaside Buffer)

Optimization: Use global pages for kernel

// Mark kernel pages as global (not flushed on CR3 reload)
flags |= PAGE_GLOBAL;

Huge Pages

2MB Pages: Reduce TLB pressure for large mappings

// Map 2MB page instead of 512 × 4KB pages
pde = phys_addr | PAGE_PRESENT | PAGE_WRITE | PAGE_HUGE;

Page Coloring

Future optimization: Reduce cache conflicts by aligning allocations

Debugging

Memory Leak Detection

void kheap_stats(void) {
    // Print total allocated blocks
    // Print total free blocks
    // Print fragmentation level
}

Page Fault Handler

File: src/kernel/fault.c

void page_fault_handler(registers_t *regs) {
    uint64_t faulting_address;
    __asm__ volatile("mov %%cr2, %0" : "=r"(faulting_address));
    
    uint64_t error_code = regs->err_code;
    
    // Parse error code
    int present = !(error_code & 0x1);
    int write = error_code & 0x2;
    int user = error_code & 0x4;
    int reserved = error_code & 0x8;
    int instruction = error_code & 0x10;
    
    panic("Page Fault at 0x%llx", faulting_address);
}

Next Steps

Clone this wiki locally