Implemented 64-bit paging

This commit is contained in:
2026-03-22 14:27:48 +01:00
parent 74d1edbf9f
commit 8cbe050f35
4 changed files with 282 additions and 43 deletions
+63
View File
@@ -0,0 +1,63 @@
How paging works
================
NOTE: This is mainly so I can wrap my head around paging.
Table levels
------------
Since 2^64 is quite a large number, paging uses a bunch of tables to organize
& navigate pages. These are also called layers & 4-layer paging is most common.
* Level 1 - Page Table (PT)
* Level 2 - Page Directory (PD)
* Level 3 - Page Directory Pointer Table (PDPT)
* Level 4 - Page Map Layer 4 (PML4)
* Level 5 - Page Map Layer 5 (PML5) (optional)
Virtual address
---------------
A 64-bit virtual address has four parts:
* 47-39 - PML4 index
* 38-30 - PDPT index
* 29-21 - PD index
* 20-12 - PT index
* 11-0 - 4 kiB page offset
Note that the top 16 bits are unused. These must always be all 0s or all 1s.
Walking the tables
------------------
Here are the steps that the MMU will take to translate an address:
1. Find the physical address of the PML4 in the CR3 register
2. Index the PML4 with bits 47-39 to find the paddr of the PDPT
3. Index the PDPT with bits 38-30 to find the paddr of the PD
4. Index the PD with bits 29-21 to find the paddr of the PT
5. Index the PT with bits 20-12 to find the paddr of the Page
6. Offset the Page address by bits 11-0 to find the actual byte
The flags
---------
Since all physical addresses in these tables MUST be page (4 kiB) aligned, the
bottom 12 bits will always be zero. These bits are used to store Page Table
Entry (PTE) flags. Since the 64-bit address space is SO large, bits 52-63 are
also used for flags. Here's how a PTE entry looks:
* 0 - Present, 0 - page fault, 1 - page is in RAM
* 1 - Writable, 0 - read-only, 1 - read & write
* 2 - Userspace, 0 - kernel only, 1 - user & kernel
* 3 - Page-level write-through caching
* 4 - Page-level cache disable
* 5 - Accesed, set by CPU when page is read or written to
* 6 - Dirty, set by CPU when page is written to (level 1 only)
* 7 - Page size, 0 - 4 kiB, 1 - 2 MiB or 1 GiB
* 8-11 and 52-62 - Available to be used by kernel
* 63 - No execute, 0 - executable, 1 - not executable
There are some flags that only apply to certain levels and page sizes, Please
see the diagram at https://wiki.osdev.org/Paging for more info.
Large pages
-----------
I don't know man.
Caching
-------
I couldn't tell ya.
+35 -17
View File
@@ -1,37 +1,55 @@
#ifndef _PAGING_H
#define _PAGING_H
#include <bootloader.h>
#include <kernel.h>
#define PTE_FLAG_PRESENT (1ULL << 0) // Page is present in the table
#define PTE_FLAG_WRITE (1ULL << 1) // Read-write
#define PTE_FLAG_USER (1ULL << 2) // User-mode (DPL3) access allowed
#define PTE_FLAG_PWT (1ULL << 3) // Page write-thru
#define PTE_FLAG_NOCACHE (1ULL << 4) // Cache disable
#define PTE_FLAG_ACCESSED (1ULL << 5) // Indicates whether page was accessed
#define PTE_FLAG_DIRTY (1ULL << 6) // Page size (valid for PD and PDPT only)
#define PTE_FLAG_PAT (1ULL << 7) // Page Attribute Table (valid for PT only)
#define PTE_FLAG_GLOBAL (1ULL << 8) // Indicates the page is globally cached
#define PTE_FLAG_SHARED (1ULL << 9) // Userland page is shared
#define PTE_FLAG_NOEXEC (1ULL << 63) // No execute
#define PTE_FLAG_PRESENT (1UL << 0) // Page is present in the table
#define PTE_FLAG_WRITE (1UL << 1) // Read-write
#define PTE_FLAG_USER (1UL << 2) // User-mode (DPL3) access allowed
#define PTE_FLAG_PWT (1UL << 3) // Page write-thru
#define PTE_FLAG_NOCACHE (1UL << 4) // Cache disable
#define PTE_FLAG_ACCESSED (1UL << 5) // Indicates whether page was read/written
#define PTE_FLAG_DIRTY (1UL << 6) // Indicates whether page was written to (valid for PT only)
#define PTE_FLAG_PAT (1UL << 7) // Page Attribute Table (valid for PT only)
#define PTE_FLAG_PAGESIZE (1UL << 7) // Page size (valid for PD and PDPT only)
#define PTE_FLAG_RSVD (1UL << 7) // No idea (valid for PML4 and PML5 only)
#define PTE_FLAG_GLOBAL (1UL << 8) // Global caching or sum (valid for PT only)
#define PTE_FLAG_NOEXEC (1UL << 63) // No execute (the XD bit)
// Region caching according to the Limine protocol
#define PTE_FLAG_CACHE (PTE_FLAG_PWT | PTE_FLAG_PAT)
#define PTE_ADDR_MASK 0x000FFFFFFFFFFFFFF000ULL
#define PTE_ADDR(_pte) ((_pte) & PAGE_ADDR_MASK)
#define PTE_ADDR_MASK 0x000FFFFFFFFFF000UL
#define PTE_ADDR(_pte) ((_pte) & PTE_ADDR_MASK)
#define PML4_IDX(_ptr) (((_ptr) >> 39) & 0x1FFUL)
#define PDPT_IDX(_ptr) (((_ptr) >> 30) & 0x1FFUL)
#define PD_IDX(_ptr) (((_ptr) >> 21) & 0x1FFUL)
#define PT_IDX(_ptr) (((_ptr) >> 12) & 0x1FFUL)
#define TO_VIRT(_ptr) ((void*)((uintptr_t)(_ptr) + bootloader.hhdm_offset))
#define TO_PHYS(_ptr) ((void*)((uintptr_t)(_ptr) - bootloader.hhdm_offset))
#define PAGE_SIZE 0x1000
#define PAGE_SIZE_LARGE 0x200000
#define PAGE_SIZE_HUGE 0x40000000
// From the Linux kernel source
// WARN: This will only invalidate on one core
static inline void pag_invalidate(size_t vaddr) { __asm__ volatile("invlpg (%0)" :: "r" (vaddr) : "memory"); }
static inline void pag_invalidate(void* vaddr) { __asm__ volatile("invlpg (%0)" :: "r" (vaddr) : "memory"); }
static inline bool pag_is_user_addr(void* vaddr) { return (uintptr_t)vaddr < 0x0000800000000000UL; }
// NOTE: All uint64_t* are VIRTUAL addresses
int pag_init(void);
uint64_t* pag_get_pte(uint64_t* pml4, void* vaddr);
void pag_map(uint64_t* pml4, void* vaddr, void* paddr, uint64_t flags);
void pag_map_region(uint64_t* pml4, void* vaddr, void* paddr, size_t count, uint64_t flags);
void pag_unmap(uint64_t* pml4, void* vaddr);
void pag_switch(uint64_t* pml4);
uint64_t* pag_new_pml(void);
void pag_unmap_region(uint64_t* pml4, void* vaddr, size_t count);
void pag_protect(uint64_t* pml4, void* vaddr, uint64_t flags);
void* pag_get_phys(uint64_t* pml4, void* vaddr);
void pag_set_pml4(uint64_t* pml4);
uint64_t* pag_get_pml4(void);
uint64_t* pag_get_next_tab(uint64_t* tab, offset_t index);
uint64_t* pag_new_next_tab(uint64_t* tab, offset_t index);
#endif
+1 -4
View File
@@ -34,10 +34,7 @@ void _start(void) {
// Memory management
if (pmm_init() != 0) panic("Failed to initalize Physical Memory Manager.");
void* paddr = pmm_alloc();
pmm_free(paddr);
//if (pag_init() != 0) panic("Failed to initalize Paging.");
if (pag_init() != 0) panic("Failed to initalize Paging.");
//if (kmalloc_init() != 0) panic("Failed to initialize kernel heap.");
// Tasking
+180 -19
View File
@@ -1,35 +1,196 @@
#include "serial.h"
#include <bootloader.h>
#include <kernel.h>
#include <limine.h>
#include <string.h>
#include <serial.h>
#include <pmm.h>
#include <paging.h>
size_t* pag_globalpd = NULL;
// VIRTUAL address of the kernels PML4
uint64_t* pag_kernel_pml4 = NULL;
void pag_invalidate(size_t vaddr) { __asm__ volatile ("invlpg %0" ::"m"(vaddr)); }
int pag_init(void) {
pag_kernel_pml4 = TO_VIRT(pmm_alloc());
if (pag_kernel_pml4 == NULL) {
debugf("[pag] Failed to allocate kernel PML4. Not enough memory.\n");
return 1;
}
memset(pag_kernel_pml4, 0, PAGE_SIZE);
int pag_init() {
size_t pd_phys = 0;
__asm__ volatile ("movq %%cr3, %0" : "=r"(pd_phys));
if (!pd_phys) return 1;
pag_map_region(
pag_kernel_pml4,
(void*)bootloader.kernel_virt_base,
(void*)bootloader.kernel_phys_base,
dceil(bootloader.executable->size, PAGE_SIZE),
PTE_FLAG_WRITE | PTE_FLAG_PRESENT
);
size_t pd_virt = pd_phys + bootloader.hhdm_offset;
pag_globalpd = (size_t*)pd_virt;
for (size_t i = 0; i < bootloader.mm_entry_count; i++) {
struct limine_memmap_entry* entry = bootloader.mm_entries[i];
uint64_t flags = 0;
switch (entry->type) {
case LIMINE_MEMMAP_USABLE: flags = PTE_FLAG_WRITE; break;
case LIMINE_MEMMAP_RESERVED: continue;
case LIMINE_MEMMAP_ACPI_RECLAIMABLE: flags = PTE_FLAG_WRITE; break;
case LIMINE_MEMMAP_ACPI_NVS: break;
case LIMINE_MEMMAP_BAD_MEMORY: continue;
case LIMINE_MEMMAP_BOOTLOADER_RECLAIMABLE: flags = PTE_FLAG_WRITE; break;
case LIMINE_MEMMAP_EXECUTABLE_AND_MODULES: flags = PTE_FLAG_WRITE; break;
case LIMINE_MEMMAP_FRAMEBUFFER: flags = PTE_FLAG_WRITE | PTE_FLAG_PWT; break;
case LIMINE_MEMMAP_RESERVED_MAPPED: flags = 0; break;
default:
debugf(
"[pag] %p - %p: Unknown memmap type %d. Will remain unmapped.\n",
entry->base, entry->base + entry->length, entry->type
);
continue;
}
pag_map_region(
pag_kernel_pml4,
(void*)(entry->base + bootloader.hhdm_offset), (void*)entry->base,
dceil(entry->length, PAGE_SIZE),
flags | PTE_FLAG_PRESENT
);
}
debugf("[pag] Switching to kernel PML4 at %p.\n", pag_kernel_pml4);
pag_set_pml4(pag_kernel_pml4);
debugf("[pag] Paging initialized.");
return 0;
}
void pag_map(size_t vaddr, size_t paddr, size_t flags);
size_t pag_umap(size_t vaddr);
size_t pag_virt_to_phys(size_t vaddr);
size_t* pag_get_pagedir();
size_t* pag_get_task_pagedir(void* task);
void pag_set_pagedir(size_t* pagedir);
size_t* pag_alloc_pagedir();
void pag_free_pagedir(size_t* pagedir);
void pag_invalidate(size_t vaddr);
uint64_t* pag_get_pte(uint64_t* pml4, void* vaddr) {
uintptr_t v = (uintptr_t)vaddr;
if (v % PAGE_SIZE != 0) {
debugf("[pag] Could not get PTE from non-aligned virtual address %p.\n", vaddr);
return NULL;
}
uint64_t* pdpt = pag_get_next_tab(pml4, PML4_IDX(v));
if (pdpt == NULL) {
debugf("[pag] Could not get PTE. %p was not found in PML4.\n");
return NULL;
}
uint64_t* pd = pag_get_next_tab(pdpt, PDPT_IDX(v));
if (pd == NULL) {
debugf("[pag] Could not get PTE. %p was not found in PDPT.\n");
return NULL;
}
uint64_t* pt = pag_get_next_tab(pd, PD_IDX(v));
if (pt == NULL) {
debugf("[pag] Could not get PTE. %p was not found in PD.\n");
return NULL;
}
return &pt[PT_IDX(v)];
}
uint64_t* pag_new_pte(uint64_t* pml4, void* vaddr) {
uintptr_t v = (uintptr_t)vaddr;
if (v % PAGE_SIZE != 0) {
debugf("[pag] Could not create PTE for non-aligned virtual address %p.\n", vaddr);
return NULL;
}
uint64_t* pdpt = pag_new_next_tab(pml4, PML4_IDX(v));
if (pdpt == NULL) {
debugf("[pag] Failed to create PDPT. Not enough memory.\n");
return NULL;
}
uint64_t* pd = pag_new_next_tab(pdpt, PDPT_IDX(v));
if (pd == NULL) {
debugf("[pag] Failed to create PD. Not enough memory.\n");
if (pdpt != NULL) pmm_free(pdpt);
return NULL;
}
uint64_t* pt = pag_new_next_tab(pd, PD_IDX(v));
if (pt == NULL) {
debugf("[pag] Failed to create PT. Not enough memory.\n");
if (pd != NULL) pmm_free(pd);
if (pdpt != NULL) pmm_free(pdpt);
return NULL;
}
return &pt[PT_IDX(v)];
}
void pag_map(uint64_t* pml4, void* vaddr, void* paddr, uint64_t flags) {
uintptr_t p = (uintptr_t)paddr;
if (p % PAGE_SIZE != 0) {
debugf("[pag] Could not map %p to %p. Physical address is not aligned.\n", paddr, vaddr);
return;
}
uint64_t* page = pag_new_pte(pml4, vaddr);
*page = PTE_ADDR(p) | flags | PTE_FLAG_PRESENT;
pag_invalidate(vaddr);
}
void pag_map_region(uint64_t* pml4, void* vaddr, void* paddr, size_t count, uint64_t flags) {
for (size_t i = 0; i < count; i++) {
pag_map(pml4, vaddr + i * PAGE_SIZE, paddr + i * PAGE_SIZE, flags);
}
}
// TODO: This is a pmm memory leak. If all pages in a PT are unmapped, remove
// that PT from it's PD. This applies to PDs & PDPTs too.
void pag_unmap(uint64_t* pml4, void* vaddr) {
uint64_t* pte = pag_get_pte(pml4, vaddr);
*pte = 0;
pag_invalidate(vaddr);
}
void pag_unmap_region(uint64_t* pml4, void* vaddr, size_t count) {
for (size_t i = 0; i < count; i++) {
pag_unmap(pml4, vaddr + i * PAGE_SIZE);
}
}
void pag_protect(uint64_t* pml4, void* vaddr, uint64_t flags) {
uint64_t* pte = pag_get_pte(pml4, vaddr);
*pte = PTE_ADDR(*pte) | flags | PTE_FLAG_PRESENT;
}
void* pag_get_phys(uint64_t* pml4, void* vaddr) {
uint64_t* pte = pag_get_pte(pml4, vaddr);
return (void*)PTE_ADDR(*pte);
}
void pag_set_pml4(uint64_t* pml4) {
__asm__ volatile("mov %0, %%cr3" :: "r" (TO_PHYS(pml4)) : "memory");
}
uint64_t* pag_get_pml4(void) {
uint64_t* pml4;
__asm__ volatile("movq %%cr3, %0" : "=r"(pml4) :: "memory");
return TO_VIRT(pml4);
}
uint64_t* pag_get_next_tab(uint64_t* tab, offset_t index) {
if (index >= 512 || tab == NULL) return NULL;
else if (tab[index] & PTE_FLAG_PRESENT) return TO_VIRT(tab[index] & PTE_ADDR_MASK);
else return NULL;
}
uint64_t* pag_new_next_tab(uint64_t* tab, offset_t index) {
if (tab == NULL) return NULL;
uint64_t* vaddr = pag_get_next_tab(tab, index);
if (vaddr != NULL) return vaddr;
void* paddr = pmm_alloc();
if (!paddr) {
debugf("[pag] Failed to allocate page for new table.\n");
return NULL;
}
vaddr = TO_VIRT(paddr);
memset(vaddr, 0, PAGE_SIZE);
tab[index] = (uintptr_t)paddr | PTE_FLAG_PRESENT | PTE_FLAG_WRITE | PTE_FLAG_USER;
return vaddr;
}