mirror of
https://github.com/Plexi09/Nadir-Kernel.git
synced 2026-10-20 15:07:27 +02:00
Merge pull request #8 from Plexi09/feature/kmalloc-heap
mm: add page-granular kmalloc/kfree heap over the PMM
This commit is contained in:
commit
6a0080127a
4 changed files with 296 additions and 2 deletions
7
Makefile
7
Makefile
|
|
@ -20,7 +20,7 @@ CFLAGS := -std=c17 -ffreestanding -nostdlib -fno-builtin \
|
|||
-fno-pic -fno-pie -fno-stack-protector \
|
||||
-Wall -Wextra -Werror -O2 -Iinclude
|
||||
|
||||
OBJS := $(BUILD)/entry.o $(BUILD)/isr.o $(BUILD)/kmain.o $(BUILD)/klog.o $(BUILD)/console.o $(BUILD)/idt.o $(BUILD)/pic.o $(BUILD)/pit.o $(BUILD)/serial.o $(BUILD)/keyboard.o $(BUILD)/pmm.o
|
||||
OBJS := $(BUILD)/entry.o $(BUILD)/isr.o $(BUILD)/kmain.o $(BUILD)/klog.o $(BUILD)/console.o $(BUILD)/idt.o $(BUILD)/pic.o $(BUILD)/pit.o $(BUILD)/serial.o $(BUILD)/keyboard.o $(BUILD)/pmm.o $(BUILD)/kmalloc.o
|
||||
|
||||
all: $(IMG)
|
||||
|
||||
|
|
@ -33,7 +33,7 @@ $(BUILD)/entry.o: $(ARCH_DIR)/entry.asm | $(BUILD)
|
|||
$(BUILD)/isr.o: $(ARCH_DIR)/isr.asm | $(BUILD)
|
||||
$(NASM) -f elf64 $< -o $@
|
||||
|
||||
$(BUILD)/kmain.o: kernel/core/kmain.c include/console.h include/idt.h include/pic.h include/pit.h include/serial.h include/keyboard.h include/pmm.h include/e820.h include/klog.h | $(BUILD)
|
||||
$(BUILD)/kmain.o: kernel/core/kmain.c include/console.h include/idt.h include/pic.h include/pit.h include/serial.h include/keyboard.h include/pmm.h include/kmalloc.h include/e820.h include/klog.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/klog.o: kernel/core/klog.c include/klog.h include/console.h include/pit.h | $(BUILD)
|
||||
|
|
@ -42,6 +42,9 @@ $(BUILD)/klog.o: kernel/core/klog.c include/klog.h include/console.h include/pit
|
|||
$(BUILD)/pmm.o: kernel/mm/pmm.c include/pmm.h include/e820.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/kmalloc.o: kernel/mm/kmalloc.c include/kmalloc.h include/pmm.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
$(BUILD)/console.o: $(ARCH_DIR)/console.c include/console.h include/io.h | $(BUILD)
|
||||
$(CC) $(CFLAGS) -c $< -o $@
|
||||
|
||||
|
|
|
|||
55
include/kmalloc.h
Normal file
55
include/kmalloc.h
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/* Nadir kernel heap allocator
|
||||
*
|
||||
* A page-granular heap on top of the PMM. Runs in 64-bit long
|
||||
* mode at Ring 0 (CPL 0) under the stage-2 identity map; every address
|
||||
* handed out is a physical address used as-is. There is no virtual
|
||||
* address space yet, so kmalloc returns identity-mapped physical
|
||||
* addresses.
|
||||
*
|
||||
* Design: allocations are rounded up to whole 4KB frames. The first
|
||||
* bytes of the first frame hold a header (magic + frame count) so
|
||||
* kfree can validate and release the whole run. Free runs are kept on
|
||||
* a singly-linked free list; kmalloc takes the first run that fits
|
||||
* (first-fit) and splits the tail. No coalescing yet — adjacent free
|
||||
* runs stay separate until a later change merges them.
|
||||
*
|
||||
* IF/IRQ safety: not interrupt-safe. Callers must not kmalloc/kfree
|
||||
* from an IRQ handler; the PIT/keyboard handlers never allocate.
|
||||
* Single-core kernel, so no locking is needed.
|
||||
*
|
||||
* Preconditions: pmm_init ran (kmalloc calls pmm_alloc_frame).
|
||||
* Failure modes: kmalloc returns 0 (NULL) on out-of-memory or when
|
||||
* the request rounds up to zero frames; kfree silently ignores NULL,
|
||||
* bad magic, and out-of-window addresses.
|
||||
*/
|
||||
|
||||
#ifndef NADIR_KMALLOC_H
|
||||
#define NADIR_KMALLOC_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/* Allocate `size` bytes (size 0 behaves like size 1). Returns an
|
||||
* identity-mapped physical address, or NULL when the PMM is out of
|
||||
* frames. The returned block is NOT zeroed.
|
||||
* Preconditions: pmm_init ran. Failure modes: returns NULL on OOM. */
|
||||
void *kmalloc(size_t size);
|
||||
|
||||
/* Release a block from kmalloc. Silently ignores NULL, pointers with
|
||||
* a bad header magic, and addresses outside the PMM window.
|
||||
* Preconditions: pmm_init ran. Failure modes: none (no fault, no
|
||||
* panic on bad input). */
|
||||
void kfree(void *ptr);
|
||||
|
||||
/* Live counters for stats printouts and the boot self-test.
|
||||
* Preconditions: kmalloc_init ran (0 before that). Failure modes: none. */
|
||||
uint64_t kmalloc_free_frames(void);
|
||||
uint64_t kmalloc_used_frames(void);
|
||||
|
||||
/* One-time heap init: takes the first PMM frames as the free pool.
|
||||
* Called once from kmain after pmm_init, with IF=0.
|
||||
* Preconditions: pmm_init ran. Failure modes: none — with zero frames
|
||||
* handed over, kmalloc always returns NULL. */
|
||||
void kmalloc_init(void);
|
||||
|
||||
#endif /* NADIR_KMALLOC_H */
|
||||
|
|
@ -21,6 +21,7 @@
|
|||
#include "idt.h"
|
||||
#include "keyboard.h"
|
||||
#include "klog.h"
|
||||
#include "kmalloc.h"
|
||||
#include "pic.h"
|
||||
#include "pit.h"
|
||||
#include "pmm.h"
|
||||
|
|
@ -113,6 +114,49 @@ void kmain(void)
|
|||
klog_dec(pmm_usable_count() - pmm_free_count());
|
||||
klog_end();
|
||||
|
||||
/* Heap self-test. init, then alloc/write/free churn to prove the
|
||||
* free-list split and re-insert paths work before interrupts go
|
||||
* live. Runs with IF=0, polling console only. */
|
||||
kmalloc_init();
|
||||
{
|
||||
uint8_t *a = kmalloc(100);
|
||||
uint8_t *b = kmalloc(5000); /* spans 2 frames */
|
||||
uint8_t *c = kmalloc(1);
|
||||
int ok = 1;
|
||||
|
||||
if (a == 0 || b == 0 || c == 0) {
|
||||
ok = 0;
|
||||
} else {
|
||||
uint64_t i;
|
||||
|
||||
for (i = 0; i < 100U; i++) {
|
||||
a[i] = (uint8_t)i;
|
||||
}
|
||||
for (i = 0; i < 100U; i++) {
|
||||
if (a[i] != (uint8_t)i) {
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
b[0] = 0xAAU;
|
||||
b[4999] = 0x55U;
|
||||
if (b[0] != 0xAAU || b[4999] != 0x55U) {
|
||||
ok = 0;
|
||||
}
|
||||
}
|
||||
kfree(a);
|
||||
kfree(b);
|
||||
kfree(c);
|
||||
kfree(0); /* NULL must be a no-op */
|
||||
klog_begin("kmalloc");
|
||||
klog_str("self-test ");
|
||||
klog_str(ok != 0 ? "ok" : "FAILED");
|
||||
klog_str(", free frames ");
|
||||
klog_dec(kmalloc_free_frames());
|
||||
klog_str(", used frames ");
|
||||
klog_dec(kmalloc_used_frames());
|
||||
klog_end();
|
||||
}
|
||||
|
||||
/* Explicit opt-in for the two live lines; idempotent even though
|
||||
* keyboard_init already unmasked IRQ1. */
|
||||
pic_unmask(0);
|
||||
|
|
|
|||
192
kernel/mm/kmalloc.c
Normal file
192
kernel/mm/kmalloc.c
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
/* Nadir kernel heap allocator (see kmalloc.h)
|
||||
*
|
||||
* Page-granular first-fit heap over pmm_alloc_frame. WHY page-granular
|
||||
* for now: the PMM only hands out 4KB frames, and a slab/buddy layer
|
||||
* on top is a later step; this keeps the first heap small enough to
|
||||
* review line by line while giving the kernel a real kmalloc/kfree to
|
||||
* build on.
|
||||
*
|
||||
* Layout of an allocated run:
|
||||
*
|
||||
* frame 0: [ header (16 bytes) | payload ... ]
|
||||
* frames 1..n-1: payload continuation
|
||||
*
|
||||
* The header records the run length in frames so kfree can validate
|
||||
* and release the whole run, plus a magic so a wild pointer is caught
|
||||
* instead of corrupting the free list. Free runs carry a next pointer
|
||||
* and a frame count in their first 16 bytes.
|
||||
*
|
||||
* Ownership: kmalloc_init pulls KMALLOC_INIT_FRAMES frames from the
|
||||
* PMM once; from then on the heap owns those frames exclusively and
|
||||
* the PMM never sees them again. kfree returns runs to the heap free
|
||||
* list only. WHY: the PMM bitmap is the
|
||||
* authoritative owner of every frame, and a frame that is both on the
|
||||
* heap free list and free in the PMM would be handed out twice.
|
||||
*
|
||||
* WHY first-fit with split (no coalescing): simplest correct policy.
|
||||
* Fragmentation is bounded because every allocation is a whole number
|
||||
* of frames and the boot self-test exercises alloc/free churn.
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "kmalloc.h"
|
||||
#include "pmm.h"
|
||||
|
||||
/* Magic stamped into allocated headers: catches kfree of a wild or
|
||||
* double-freed pointer before the free list is corrupted. */
|
||||
#define KMALLOC_MAGIC 0x4E414449524D4148ULL /* "NADIRHAM" */
|
||||
|
||||
/* Frames the heap starts with: 64 frames = 256KB of kernel heap,
|
||||
* pulled from the PMM at init. Enough for early boot structures. */
|
||||
#define KMALLOC_INIT_FRAMES 64U
|
||||
|
||||
struct kmalloc_header {
|
||||
uint64_t magic;
|
||||
uint64_t frames; /* total frames in this run, header included */
|
||||
};
|
||||
|
||||
/* A free run: next pointer + frame count in the first 16 bytes. */
|
||||
struct kmalloc_free {
|
||||
struct kmalloc_free *next;
|
||||
uint64_t frames;
|
||||
};
|
||||
|
||||
/* Free list head: runs of whole frames available for allocation. */
|
||||
static struct kmalloc_free *g_free_list;
|
||||
|
||||
static uint64_t g_free_frames;
|
||||
static uint64_t g_used_frames;
|
||||
|
||||
/* Round a byte count up to whole frames, header included. A request
|
||||
* of `size` bytes needs ceil((size + header) / FRAME) frames. */
|
||||
static uint64_t frames_for(size_t size)
|
||||
{
|
||||
uint64_t bytes;
|
||||
|
||||
if (size == 0U) {
|
||||
size = 1U;
|
||||
}
|
||||
bytes = (uint64_t)size + sizeof(struct kmalloc_header);
|
||||
return (bytes + PMM_FRAME_SIZE - 1U) / PMM_FRAME_SIZE;
|
||||
}
|
||||
|
||||
void kmalloc_init(void)
|
||||
{
|
||||
uint64_t addr = 0U;
|
||||
uint64_t i;
|
||||
struct kmalloc_free *run;
|
||||
|
||||
g_free_list = NULL;
|
||||
g_free_frames = 0;
|
||||
g_used_frames = 0;
|
||||
|
||||
/* Pull the init frames from the PMM. pmm_alloc_frame scans from
|
||||
* frame 0 upward, so consecutive calls return consecutive frames
|
||||
* while the low memory is free. */
|
||||
for (i = 0; i < KMALLOC_INIT_FRAMES; i++) {
|
||||
uint64_t next = pmm_alloc_frame();
|
||||
|
||||
if (next == 0U) {
|
||||
break; /* PMM out of frames */
|
||||
}
|
||||
if (addr != 0U && next != addr + i * PMM_FRAME_SIZE) {
|
||||
break; /* non-contiguous */
|
||||
}
|
||||
if (addr == 0U) {
|
||||
addr = next;
|
||||
}
|
||||
}
|
||||
|
||||
if (addr != 0U) {
|
||||
run = (struct kmalloc_free *)(uintptr_t)addr;
|
||||
run->next = NULL;
|
||||
run->frames = i;
|
||||
g_free_list = run;
|
||||
g_free_frames = i;
|
||||
}
|
||||
}
|
||||
|
||||
void *kmalloc(size_t size)
|
||||
{
|
||||
uint64_t need = frames_for(size);
|
||||
struct kmalloc_free **link = &g_free_list;
|
||||
struct kmalloc_free *run;
|
||||
|
||||
/* walk the free list for the first run that fits. */
|
||||
while (*link != NULL) {
|
||||
run = *link;
|
||||
if (run->frames >= need) {
|
||||
struct kmalloc_header *hdr;
|
||||
uint64_t run_frames = run->frames;
|
||||
|
||||
if (run->frames > need) {
|
||||
/* Split: the tail replaces this run in the free list,
|
||||
* and the head `need` frames become the allocation.
|
||||
* WHY contiguous: a run is always a contiguous frame
|
||||
* range, so the split point is exact. WHY replace (not
|
||||
* keep) the head in the list: the allocation header
|
||||
* overwrites the first 16 bytes of the head, which
|
||||
* would clobber the free node's next/frames fields. */
|
||||
struct kmalloc_free *tail;
|
||||
|
||||
tail = (struct kmalloc_free *)
|
||||
((uintptr_t)run + need * PMM_FRAME_SIZE);
|
||||
tail->next = run->next;
|
||||
tail->frames = run->frames - need;
|
||||
*link = tail; /* tail takes this run's list slot */
|
||||
run_frames = need;
|
||||
} else {
|
||||
*link = run->next;
|
||||
}
|
||||
g_free_frames -= run_frames;
|
||||
hdr = (struct kmalloc_header *)run;
|
||||
hdr->magic = KMALLOC_MAGIC;
|
||||
hdr->frames = run_frames;
|
||||
g_used_frames += run_frames;
|
||||
return (void *)((uintptr_t)hdr + sizeof(struct kmalloc_header));
|
||||
}
|
||||
link = &run->next;
|
||||
}
|
||||
return NULL; /* no run big enough: OOM */
|
||||
}
|
||||
|
||||
void kfree(void *ptr)
|
||||
{
|
||||
struct kmalloc_header *hdr;
|
||||
struct kmalloc_free *node;
|
||||
|
||||
if (ptr == NULL) {
|
||||
return;
|
||||
}
|
||||
hdr = (struct kmalloc_header *)
|
||||
((uintptr_t)ptr - sizeof(struct kmalloc_header));
|
||||
if (hdr->magic != KMALLOC_MAGIC) {
|
||||
return; /* ignore if wild or double-freed pointer */
|
||||
}
|
||||
if (hdr->frames == 0U) {
|
||||
return;
|
||||
}
|
||||
/* Invalidate the magic first so a double free is caught. */
|
||||
hdr->magic = 0U;
|
||||
|
||||
/* Return the run to the free list at the head. WHY head insert:
|
||||
* O(1), and first-fit finds it on the next kmalloc. */
|
||||
node = (struct kmalloc_free *)hdr;
|
||||
node->next = g_free_list;
|
||||
node->frames = hdr->frames;
|
||||
g_free_list = node;
|
||||
g_free_frames += hdr->frames;
|
||||
g_used_frames -= hdr->frames;
|
||||
}
|
||||
|
||||
uint64_t kmalloc_free_frames(void)
|
||||
{
|
||||
return g_free_frames;
|
||||
}
|
||||
|
||||
uint64_t kmalloc_used_frames(void)
|
||||
{
|
||||
return g_used_frames;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue