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:
@@ -7,6 +7,7 @@ add_executable(kernel
|
|||||||
idt.c
|
idt.c
|
||||||
isr.c
|
isr.c
|
||||||
pic.c
|
pic.c
|
||||||
|
pmm.c
|
||||||
interrupts.S
|
interrupts.S
|
||||||
kernel.c
|
kernel.c
|
||||||
)
|
)
|
||||||
|
|||||||
12
src/kernel.c
12
src/kernel.c
@@ -5,6 +5,7 @@
|
|||||||
#include "idt.h"
|
#include "idt.h"
|
||||||
#include "pic.h"
|
#include "pic.h"
|
||||||
#include "port_io.h"
|
#include "port_io.h"
|
||||||
|
#include "pmm.h"
|
||||||
|
|
||||||
void offset_print(const char *str)
|
void offset_print(const char *str)
|
||||||
{
|
{
|
||||||
@@ -47,6 +48,17 @@ void kernel_main(uint32_t magic, uint32_t addr) {
|
|||||||
init_pic();
|
init_pic();
|
||||||
offset_print("PIC initialized\n");
|
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 */
|
/* Enable interrupts */
|
||||||
asm volatile("sti");
|
asm volatile("sti");
|
||||||
offset_print("Interrupts enabled\n");
|
offset_print("Interrupts enabled\n");
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ SECTIONS
|
|||||||
{
|
{
|
||||||
. = 1M;
|
. = 1M;
|
||||||
|
|
||||||
|
_kernel_start = .;
|
||||||
|
|
||||||
.text BLOCK(4K) : ALIGN(4K)
|
.text BLOCK(4K) : ALIGN(4K)
|
||||||
{
|
{
|
||||||
KEEP(*(.multiboot))
|
KEEP(*(.multiboot))
|
||||||
@@ -25,4 +27,6 @@ SECTIONS
|
|||||||
*(COMMON)
|
*(COMMON)
|
||||||
*(.bss)
|
*(.bss)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_kernel_end = .;
|
||||||
}
|
}
|
||||||
|
|||||||
146
src/pmm.c
Normal file
146
src/pmm.c
Normal 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
36
src/pmm.h
Normal 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
|
||||||
Reference in New Issue
Block a user