Skip to content

Memory Management in SlayerOS

SlayerOS implements a comprehensive memory management system with four key components: frame allocation, paging, heap management, and memory mapping. This document explains how these components work together to provide efficient memory management.

Memory Layout

SlayerOS uses a higher-half kernel design where the kernel occupies the upper portion of the virtual address space. This provides:

  • Clear separation between user and kernel memory
  • Protection against unauthorized access to kernel memory
  • Ability to remap user memory independently

Frame Allocator

The frame allocator manages physical memory at the granularity of 4KB frames.

Implementation

  • Initialization: Frame::init() parses the Limine-provided memory map to identify usable memory regions
  • Tracking: Uses a bitmap where each bit represents one physical frame (1=used, 0=free)
  • API:
    cpp
    void* Frame::alloc();       // Allocates a single frame
    void Frame::free(void* ptr); // Releases a previously allocated frame

The allocator carefully avoids regions marked as reserved, bootloader data, or hardware-mapped areas in the memory map.

Paging System

The paging system implements virtual memory using the x86_64 four-level paging structure.

Implementation

  • Page Tables: Uses PML4, PML3, PML2, and PML1 tables (also known as Page Map Level 4, PDPT, PD, and PT)
  • API:
    cpp
    void Paging::init();                                  // Initializes paging
    void Paging::map(uintptr_t phys, uintptr_t virt, uint64_t flags); // Maps physical to virtual
    void Paging::unmap(uintptr_t virt);                   // Removes mapping

Page Flags

  • PAGE_PRESENT: Page is in memory
  • PAGE_WRITABLE: Page can be written to
  • PAGE_USER: Page is accessible from user mode
  • PAGE_WRITETHROUGH: Write-through caching
  • PAGE_NOCACHE: Disable caching
  • PAGE_NX: No-execute (prevents code execution)

Heap Allocator

The heap allocator provides dynamic memory allocation (similar to malloc/free).

Implementation

  • Structure: Divides memory into pages, each containing segments
  • Tracking: Uses linked lists of free and used segments
  • API:
    cpp
    void* kmalloc(size_t size);  // Allocates memory
    void kfree(void* ptr);       // Frees allocated memory

The allocator implements basic coalescing to prevent fragmentation by merging adjacent free segments.

Memory Mapper

The memory mapper creates the initial virtual memory layout during kernel initialization.

Implementation

  • Initialization: Mapper::full_map() sets up all required mappings
  • Key Mappings:
    • Kernel code and data sections
    • Higher Half Direct Map (HHDM) for accessing physical memory
    • Framebuffer for graphics output
    • Bootloader data structures

Address Translation

cpp
// Convert physical address to virtual
#define PHYS2VIRT(addr) ((void*)((uintptr_t)(addr) + hhdm_offset))

// Convert virtual address to physical
#define VIRT2PHYS(addr) ((void*)((uintptr_t)(addr) - hhdm_offset))

Memory Management Flow

  1. Boot: Limine provides memory map and HHDM information
  2. Initialization: Frame allocator initializes using memory map
  3. Paging Setup: Kernel creates initial page tables
  4. Mapping: Memory mapper creates standard mappings
  5. Heap Setup: Heap allocator initializes for dynamic allocation
  6. Runtime: Kernel uses kmalloc/kfree for memory management

Best Practices

  • Always check allocation results for NULL
  • Free all allocated memory to prevent leaks
  • Use appropriate page flags for security (e.g., NX for data)
  • Be aware of alignment requirements for hardware operations

```markdown:site/docs/guide/architecture/bootloader.md
# Bootloader Integration in SlayerOS

SlayerOS uses the Limine bootloader to initialize hardware and load the kernel. This document explains the boot process and how the kernel interacts with Limine.

## Limine Bootloader

[Limine](https://github.com/limine-bootloader/limine) is a modern bootloader supporting both BIOS and UEFI systems. It provides a clean protocol for retrieving system information and resources.

### Key Features

- Multiboot2 and Limine protocol support
- BIOS and UEFI compatibility
- Detailed memory map information
- Framebuffer initialization
- Module loading capability
- SMP initialization

## Boot Process

1. Firmware (BIOS/UEFI) loads Limine
2. Limine reads its configuration (`limine.conf`)
3. Limine loads the kernel into memory
4. Limine prepares boot information structures
5. Control transfers to kernel entry point (`_start`)

## Limine Protocol

The Limine protocol uses a request/response mechanism for communication between the bootloader and kernel.

### Request Structure

```cpp
struct limine_request {
    uint64_t id[4];         // Unique identifier
    uint64_t revision;      // Protocol revision
    void *response;         // Filled by bootloader
};

Key Requests Used by SlayerOS

  • Memory Map Request: limine_memmap_request

    • Provides information about available memory regions
    • Identifies usable, reserved, ACPI, and other memory types
  • Framebuffer Request: limine_framebuffer_request

    • Provides initialized graphics framebuffer
    • Includes resolution, pixel format, and memory address
  • HHDM Request: limine_hhdm_request

    • Provides Higher Half Direct Map offset
    • Enables easy physical memory access from kernel space
  • Kernel File Request: limine_kernel_file_request

    • Provides information about the kernel ELF file
    • Useful for accessing embedded data

Boot Context

SlayerOS stores bootloader information in a central structure:

cpp
struct BootloaderCtx {
    struct limine_memmap_response *memmap;
    struct limine_framebuffer_response *fb_info;
    struct limine_hhdm_response *hhdm;
    struct limine_kernel_file_response *kernel_file;
};

extern BootloaderCtx boot_ctx;

This structure is populated in bootloader/limine.cxx and accessed throughout the kernel.

Kernel Initialization Sequence

  1. Entry Point: _start (assembly)

    • Sets up stack
    • Calls _kernel_start
  2. Early Initialization: _kernel_start

    • Initializes serial port for debugging
    • Retrieves bootloader information
    • Sets up essential services
  3. Memory Setup:

    • Initializes frame allocator using memory map
    • Sets up paging system
    • Creates memory mappings
  4. Driver Initialization:

    • Initializes framebuffer driver
    • Sets up other hardware drivers

Limine Configuration

SlayerOS uses a minimal limine.conf:

TIMEOUT=0
SERIAL=yes

:SlayerOS
PROTOCOL=limine
KERNEL_PATH=boot/slay.kernel

Building a Bootable Image

The build process:

  1. Compile kernel into ELF binary
  2. Create ISO directory structure
  3. Copy kernel and Limine files
  4. Generate ISO with xorriso
  5. Install Limine bootloader to ISO
bash
# Example build command (from Makefile)
xorriso -as mkisofs -b boot/limine/limine-bios-cd.bin \
    -no-emul-boot -boot-load-size 4 -boot-info-table \
    --efi-boot boot/limine/limine-uefi-cd.bin \
    -efi-boot-part --efi-boot-image --protective-msdos-label \
    $(ISO_DIR) -o $(ISO_FILE)

Debugging Boot Issues

  • Enable serial output with SERIAL=yes in limine.conf
  • Use QEMU with -serial stdio to view serial output
  • Check early initialization code for errors
  • Verify memory map is being correctly interpreted
  • Ensure kernel entry point is properly aligned

Best Practices

  • Always validate bootloader responses before use
  • Don't rely on bootloader-reclaimable memory after initialization
  • Use the provided memory map to avoid overwriting critical regions
  • Initialize serial port early for debugging capability

SlayerOS