Implement paging subsystem with identity mapping and kernel heap (AI)

- Created two-level x86 paging (page directory + page tables) with 4 KiB pages.
- Identity maps all detected physical memory in two phases:
  1) Static: first 16 MiB using 4 BSS-allocated page tables (avoids
     chicken-and-egg with PMM bitmap in BSS).
  2) Dynamic: memory above 16 MiB using PMM-allocated page tables,
     created before paging is enabled so physical addresses still work.
- Provides kernel heap at 0xD0000000–0xF0000000 for virtual page allocation.
- API: paging_map_page, paging_unmap_page, paging_alloc_page, paging_free_page,
  paging_get_physical.
- Added pmm_get_memory_size() to expose detected RAM for paging init.
- Kernel tests paging by allocating a virtual page, writing 0xDEADBEEF, and
  reading it back, then freeing it.
- Added documentation in docs/paging.md.

Tested: boots and passes paging test with both 4 MiB and 128 MiB RAM in QEMU.
This commit is contained in:
AI
2026-02-23 11:03:27 +00:00
parent f2e7d6c5d7
commit fb61ab7c15
8 changed files with 467 additions and 9 deletions

75
docs/paging.md Normal file
View File

@@ -0,0 +1,75 @@
# Paging Subsystem
## Overview
The paging subsystem manages virtual memory using the x86 two-level paging scheme (no PAE). It provides identity mapping for all physical memory and a kernel heap region for dynamic virtual page allocation.
## Architecture
### Page Table Structure
x86 32-bit paging uses two levels:
| Level | Entries | Each Entry Maps | Total Coverage |
|---|---|---|---|
| Page Directory | 1024 | 4 MiB (one page table) | 4 GiB |
| Page Table | 1024 | 4 KiB (one page) | 4 MiB |
Each entry is a 32-bit value containing a 20-bit physical page frame number and 12 bits of flags.
### Identity Mapping
During initialization, all detected physical memory is identity-mapped (virtual address = physical address). This is done in two phases:
1. **Static mapping (first 16 MiB):** Four page tables are statically allocated in BSS. This avoids a chicken-and-egg problem since the PMM bitmap itself resides in this region.
2. **Dynamic mapping (above 16 MiB):** Additional page tables are allocated from the PMM *before* paging is enabled (so physical addresses are still directly accessible). These cover all remaining detected physical memory.
### Kernel Heap
The kernel heap region occupies virtual addresses `0xD0000000` through `0xF0000000` (768 MiB).
When `paging_alloc_page()` is called:
1. A physical page is allocated from the PMM.
2. A page table entry is created mapping the next free virtual address to the physical page.
3. The virtual address is returned.
When `paging_free_page()` is called:
1. The physical address is looked up via the page table entry.
2. The virtual mapping is removed.
3. The physical page is returned to the PMM.
### TLB Management
- Single-page invalidations use `invlpg`.
- Full TLB flushes use CR3 reload.
## API
```c
void init_paging(void);
void paging_map_page(uint32_t vaddr, uint32_t paddr, uint32_t flags);
void paging_unmap_page(uint32_t vaddr);
void *paging_alloc_page(void);
void paging_free_page(void *vaddr);
uint32_t paging_get_physical(uint32_t vaddr);
```
### Flags
| Flag | Value | Meaning |
|---|---|---|
| `PAGE_PRESENT` | 0x001 | Page is present in memory |
| `PAGE_WRITE` | 0x002 | Page is writable |
| `PAGE_USER` | 0x004 | Page is user-accessible (ring 3) |
## Key Files
- `src/paging.c` / `src/paging.h` — Implementation and API.
- `src/pmm.c` / `src/pmm.h` — Physical page allocation backing.
## Design Decisions
- **No higher-half kernel yet:** The kernel runs at its physical load address (1 MiB) with identity mapping. Higher-half mapping (0xC0000000) can be added later without changing the paging API.
- **Static + dynamic page tables:** The first 16 MiB uses BSS-allocated tables to bootstrap, while memory above 16 MiB uses PMM-allocated tables. This keeps BSS usage bounded at ~16 KiB regardless of total RAM.
- **Sequential heap allocation:** The heap grows upward linearly. No free-list reuse of freed virtual addresses is implemented yet.