Implement physical memory allocator with zone support (AI)

- Added bitmap-based physical memory manager (PMM) that parses the Multiboot2
  memory map to discover available RAM regions.
- Supports two allocation zones: PMM_ZONE_DMA (below 16MB) and PMM_ZONE_NORMAL
  (above 16MB), with automatic fallback from NORMAL to DMA when the preferred
  zone is exhausted.
- Marks kernel memory region and multiboot info structure as reserved using
  _kernel_start/_kernel_end linker symbols.
- Page 0 is always marked as used to prevent returning NULL as a valid address.
- Added linker script symbols (_kernel_start, _kernel_end) to track kernel
  memory boundaries for the allocator.
- Kernel now initializes PMM after PIC and performs test allocations to verify
  the subsystem works.
This commit is contained in:
AI
2026-02-23 10:52:06 +00:00
parent f1923fdbcf
commit cf3059747a
5 changed files with 199 additions and 0 deletions

View File

@@ -7,6 +7,7 @@ add_executable(kernel
idt.c
isr.c
pic.c
pmm.c
interrupts.S
kernel.c
)

View File

@@ -5,6 +5,7 @@
#include "idt.h"
#include "pic.h"
#include "port_io.h"
#include "pmm.h"
void offset_print(const char *str)
{
@@ -47,6 +48,17 @@ void kernel_main(uint32_t magic, uint32_t addr) {
init_pic();
offset_print("PIC initialized\n");
init_pmm(addr);
offset_print("PMM initialized\n");
phys_addr_t p1 = pmm_alloc_page(PMM_ZONE_NORMAL);
offset_print("Allocated page at: ");
print_hex(p1);
phys_addr_t p2 = pmm_alloc_page(PMM_ZONE_DMA);
offset_print("Allocated DMA page at: ");
print_hex(p2);
/* Enable interrupts */
asm volatile("sti");
offset_print("Interrupts enabled\n");

View File

@@ -4,6 +4,8 @@ SECTIONS
{
. = 1M;
_kernel_start = .;
.text BLOCK(4K) : ALIGN(4K)
{
KEEP(*(.multiboot))
@@ -25,4 +27,6 @@ SECTIONS
*(COMMON)
*(.bss)
}
_kernel_end = .;
}

146
src/pmm.c Normal file
View File

@@ -0,0 +1,146 @@
#include "pmm.h"
#include <multiboot2.h>
extern uint32_t _kernel_start;
extern uint32_t _kernel_end;
#define MAX_PHYSICAL_MEMORY 0xFFFFFFFF // 4GB
#define BITMAP_SIZE (MAX_PHYSICAL_MEMORY / PAGE_SIZE / 32)
#define FRAMES_PER_INDEX 32
static uint32_t memory_bitmap[BITMAP_SIZE];
static uint32_t memory_size = 0;
// static uint32_t used_frames = 0; // Unused for now
static void set_frame(phys_addr_t frame_addr) {
uint32_t frame = frame_addr / PAGE_SIZE;
uint32_t idx = frame / FRAMES_PER_INDEX;
uint32_t off = frame % FRAMES_PER_INDEX;
memory_bitmap[idx] |= (1 << off);
}
static void clear_frame(phys_addr_t frame_addr) {
uint32_t frame = frame_addr / PAGE_SIZE;
uint32_t idx = frame / FRAMES_PER_INDEX;
uint32_t off = frame % FRAMES_PER_INDEX;
memory_bitmap[idx] &= ~(1 << off);
}
static uint32_t first_free_frame(size_t start_frame, size_t end_frame) {
for (size_t i = start_frame; i < end_frame; i++) {
uint32_t idx = i / FRAMES_PER_INDEX;
uint32_t off = i % FRAMES_PER_INDEX;
if (!(memory_bitmap[idx] & (1 << off))) {
return i;
}
}
return (uint32_t)-1;
}
void init_pmm(uint32_t multiboot_addr) {
struct multiboot_tag *tag;
uint32_t addr = multiboot_addr;
/* Initialize all memory as used (1) initially */
for (int i = 0; i < BITMAP_SIZE; i++) {
memory_bitmap[i] = 0xFFFFFFFF;
}
if (addr & 7) {
// offset_print("Multiboot alignment error\n"); // Need offset_print here? No, kernel handles prints
return; // Alignment issue
}
uint32_t size = *(uint32_t *)addr;
for (tag = (struct multiboot_tag *)(addr + 8);
tag->type != MULTIBOOT_TAG_TYPE_END;
tag = (struct multiboot_tag *)((multiboot_uint8_t *)tag + ((tag->size + 7) & ~7))) {
if (tag->type == MULTIBOOT_TAG_TYPE_BASIC_MEMINFO) {
struct multiboot_tag_basic_meminfo *meminfo = (struct multiboot_tag_basic_meminfo *)tag;
memory_size = meminfo->mem_upper;
} else if (tag->type == MULTIBOOT_TAG_TYPE_MMAP) {
multiboot_memory_map_t *mmap;
struct multiboot_tag_mmap *tag_mmap = (struct multiboot_tag_mmap *)tag;
for (mmap = tag_mmap->entries;
(multiboot_uint8_t *)mmap < (multiboot_uint8_t *)tag + tag->size;
mmap = (multiboot_memory_map_t *)((unsigned long)mmap + tag_mmap->entry_size)) {
if (mmap->type == MULTIBOOT_MEMORY_AVAILABLE) {
uint64_t start = mmap->addr;
uint64_t len = mmap->len;
uint64_t end = start + len;
if (start % PAGE_SIZE != 0) {
start = (start + PAGE_SIZE) & ~(PAGE_SIZE - 1);
}
for (uint64_t addr_iter = start; addr_iter < end; addr_iter += PAGE_SIZE) {
if (addr_iter + PAGE_SIZE <= end && addr_iter < MAX_PHYSICAL_MEMORY) {
clear_frame((phys_addr_t)addr_iter);
}
}
}
}
}
}
uint32_t kstart = (uint32_t)&_kernel_start;
uint32_t kend = (uint32_t)&_kernel_end;
kstart &= ~(PAGE_SIZE - 1);
kend = (kend + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
for (uint32_t i = kstart; i < kend; i += PAGE_SIZE) {
set_frame(i);
}
// Mark multiboot structure
uint32_t mb_start = multiboot_addr;
uint32_t mb_end = multiboot_addr + size;
mb_start &= ~(PAGE_SIZE - 1);
mb_end = (mb_end + PAGE_SIZE - 1) & ~(PAGE_SIZE - 1);
for (uint32_t i = mb_start; i < mb_end; i += PAGE_SIZE) {
set_frame(i);
}
// Mark first page as used to avoid returning 0 (NULL)
set_frame(0);
}
phys_addr_t pmm_alloc_page(pmm_zone_t zone) {
uint32_t start_frame = 0;
uint32_t end_frame = BITMAP_SIZE * FRAMES_PER_INDEX;
uint32_t frame = (uint32_t)-1;
// 16MB boundary is at 16*1024*1024 / 4096 = 4096 frames
uint32_t dma_limit = 4096;
if (zone == PMM_ZONE_DMA) {
end_frame = dma_limit;
frame = first_free_frame(start_frame, end_frame);
} else {
start_frame = dma_limit;
frame = first_free_frame(start_frame, end_frame);
// Fallback to DMA if NORMAL failed
if (frame == (uint32_t)-1) {
frame = first_free_frame(0, dma_limit);
}
}
if (frame != (uint32_t)-1) {
phys_addr_t addr = frame * PAGE_SIZE;
set_frame(addr);
return addr;
}
return 0; // OOM
}
void pmm_free_page(phys_addr_t addr) {
clear_frame(addr);
}

36
src/pmm.h Normal file
View File

@@ -0,0 +1,36 @@
#ifndef PMM_H
#define PMM_H
#include <stdint.h>
#include <stddef.h>
#define PAGE_SIZE 4096
typedef uintptr_t phys_addr_t;
typedef enum {
PMM_ZONE_DMA, // < 16 MB
PMM_ZONE_NORMAL, // Any other memory
PMM_ZONE_HIGHMEM // > 4 GB (if 64-bit or PAE, but we are 32-bit for now) - actually sticking to DMA and NORMAL is enough for 32-bit typically
} pmm_zone_t;
/*
* Initialize the physical memory manager.
* @param multiboot_addr Address of the multiboot info structure
*/
void init_pmm(uint32_t multiboot_addr);
/*
* Allocate a physical page.
* @param zone The preferred zone to allocate from.
* @return Physical address of the allocated page, or 0 if out of memory.
*/
phys_addr_t pmm_alloc_page(pmm_zone_t zone);
/*
* Free a physical page.
* @param addr Physical address of the page to free.
*/
void pmm_free_page(phys_addr_t addr);
#endif