From d286d0f0eee17174715566de09670795d6b8b66f Mon Sep 17 00:00:00 2001 From: tadryanom_bot Date: Tue, 3 Feb 2026 04:00:08 +0000 Subject: [PATCH] Feat: Add Kernel Heap (kmalloc) and Preemptive Timer (PIT) --- include/heap.h | 11 +++++ include/timer.h | 8 ++++ src/drivers/timer.c | 45 +++++++++++++++++++ src/kernel/main.c | 20 +++++---- src/mm/heap.c | 106 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 182 insertions(+), 8 deletions(-) create mode 100644 include/heap.h create mode 100644 include/timer.h create mode 100644 src/drivers/timer.c create mode 100644 src/mm/heap.c diff --git a/include/heap.h b/include/heap.h new file mode 100644 index 0000000..1d483d5 --- /dev/null +++ b/include/heap.h @@ -0,0 +1,11 @@ +#ifndef HEAP_H +#define HEAP_H + +#include +#include + +void kheap_init(void); +void* kmalloc(size_t size); +void kfree(void* ptr); + +#endif diff --git a/include/timer.h b/include/timer.h new file mode 100644 index 0000000..315e625 --- /dev/null +++ b/include/timer.h @@ -0,0 +1,8 @@ +#ifndef TIMER_H +#define TIMER_H + +#include + +void timer_init(uint32_t frequency); + +#endif diff --git a/src/drivers/timer.c b/src/drivers/timer.c new file mode 100644 index 0000000..5dc9737 --- /dev/null +++ b/src/drivers/timer.c @@ -0,0 +1,45 @@ +#include "timer.h" +#include "idt.h" +#include "io.h" +#include "uart_console.h" +#include "process.h" // For schedule() + +static uint32_t tick = 0; + +void timer_callback(struct registers* regs) { + (void)regs; + tick++; + + // Every 100 ticks (approx 1 sec), print a dot just to show life + if (tick % 100 == 0) { + // uart_print("."); // Commented out to not pollute shell + } + + // PREEMPTION! + // Force a task switch + schedule(); +} + +void timer_init(uint32_t frequency) { + uart_print("[TIMER] Initializing PIT...\n"); + + // Register Timer Callback (IRQ 0 -> Int 32) + register_interrupt_handler(32, timer_callback); + + // The value we send to the PIT divisor is the value to divide it's input clock + // (1193180 Hz) by, to get our required frequency. + uint32_t divisor = 1193180 / frequency; + + // Send the command byte. + // 0x36 = 0011 0110 + // Channel 0 | Access lo/hi byte | Mode 3 (Square Wave) | 16-bit binary + outb(0x43, 0x36); + + // Split divisor into low and high bytes + uint8_t l = (uint8_t)(divisor & 0xFF); + uint8_t h = (uint8_t)( (divisor>>8) & 0xFF ); + + // Send the frequency divisor. + outb(0x40, l); + outb(0x40, h); +} diff --git a/src/kernel/main.c b/src/kernel/main.c index e430184..78e5960 100644 --- a/src/kernel/main.c +++ b/src/kernel/main.c @@ -8,6 +8,8 @@ #include "process.h" #include "keyboard.h" #include "shell.h" +#include "heap.h" +#include "timer.h" /* Check if the compiler thinks we are targeting the wrong operating system. */ #if defined(__linux__) @@ -47,17 +49,23 @@ void kernel_main(unsigned long magic, unsigned long addr) { #if defined(__i386__) vmm_init(); - // 4. Initialize Interrupts (x86) + // 4. Initialize Kernel Heap + kheap_init(); + + // 5. Initialize Interrupts (x86) uart_print("[AdrOS] Initializing IDT...\n"); idt_init(); - // 5. Initialize Drivers + // 6. Initialize Drivers keyboard_init(); - // 6. Initialize Multitasking (Optional for Shell, but good to have) + // 7. Initialize Multitasking uart_print("[AdrOS] Initializing Scheduler...\n"); process_init(); + // 8. Start Timer (Preemption!) - 50Hz + timer_init(50); + // Start Shell as the main interaction loop shell_init(); @@ -68,9 +76,8 @@ void kernel_main(unsigned long magic, unsigned long addr) { uart_print("Welcome to AdrOS (x86/ARM/RISC-V/MIPS)!\n"); // Infinite loop acting as Idle Task - // Shell is interrupt driven, so we just idle here. for(;;) { - // We can execute background tasks here if needed + // HLT puts CPU to sleep until next interrupt (Timer or Keyboard) #if defined(__i386__) || defined(__x86_64__) __asm__("hlt"); #elif defined(__aarch64__) @@ -78,8 +85,5 @@ void kernel_main(unsigned long magic, unsigned long addr) { #elif defined(__riscv) __asm__("wfi"); #endif - - // If we had preemptive scheduling, the timer would wake us up. - // For cooperative, we assume shell is ISR based so HLT is fine. } } diff --git a/src/mm/heap.c b/src/mm/heap.c new file mode 100644 index 0000000..824ead9 --- /dev/null +++ b/src/mm/heap.c @@ -0,0 +1,106 @@ +#include "heap.h" +#include "pmm.h" +#include "vmm.h" +#include "uart_console.h" + +// Heap starts at 3GB + 256MB (Arbitrary safe high location) +#define KHEAP_START 0xD0000000 +#define KHEAP_INITIAL_SIZE (10 * 1024 * 1024) // 10MB +#define PAGE_SIZE 4096 + +typedef struct heap_header { + size_t size; // Size of the data block (excluding header) + uint8_t is_free; // 1 if free, 0 if used + struct heap_header* next; // Next block in the list +} heap_header_t; + +static heap_header_t* head = NULL; + +void kheap_init(void) { + uart_print("[HEAP] Initializing Kernel Heap...\n"); + + // 1. Map pages for the heap + // We need to map Virtual Addresses [KHEAP_START] to [KHEAP_START + SIZE] + // to physical frames. + + uint32_t pages_needed = KHEAP_INITIAL_SIZE / PAGE_SIZE; + if (KHEAP_INITIAL_SIZE % PAGE_SIZE != 0) pages_needed++; + + uint32_t virt_addr = KHEAP_START; + + for (uint32_t i = 0; i < pages_needed; i++) { + void* phys_frame = pmm_alloc_page(); + if (!phys_frame) { + uart_print("[HEAP] OOM during init!\n"); + return; + } + + // Map it! + // Note: vmm_map_page expects 64-bit phys but we give it 32-bit cast + vmm_map_page((uint64_t)(uintptr_t)phys_frame, (uint64_t)virt_addr, + VMM_FLAG_PRESENT | VMM_FLAG_RW); + + virt_addr += PAGE_SIZE; + } + + // 2. Create the initial huge free block + head = (heap_header_t*)KHEAP_START; + head->size = KHEAP_INITIAL_SIZE - sizeof(heap_header_t); + head->is_free = 1; + head->next = NULL; + + uart_print("[HEAP] Initialized 10MB at 0xD0000000.\n"); +} + +void* kmalloc(size_t size) { + if (size == 0) return NULL; + + // Align size to 8 bytes for performance/safety + size_t aligned_size = (size + 7) & ~7; + + heap_header_t* current = head; + + while (current) { + if (current->is_free && current->size >= aligned_size) { + // Found a block! + + // Can we split it? + // Only split if remaining space is big enough for a header + minimal data + if (current->size > aligned_size + sizeof(heap_header_t) + 8) { + heap_header_t* new_block = (heap_header_t*)((uint8_t*)current + sizeof(heap_header_t) + aligned_size); + + new_block->size = current->size - aligned_size - sizeof(heap_header_t); + new_block->is_free = 1; + new_block->next = current->next; + + current->size = aligned_size; + current->next = new_block; + } + + current->is_free = 0; + return (void*)((uint8_t*)current + sizeof(heap_header_t)); + } + current = current->next; + } + + uart_print("[HEAP] OOM: No block large enough!\n"); + return NULL; +} + +void kfree(void* ptr) { + if (!ptr) return; + + // Get header + heap_header_t* header = (heap_header_t*)((uint8_t*)ptr - sizeof(heap_header_t)); + header->is_free = 1; + + // Merge with next block if free (Coalescing) + if (header->next && header->next->is_free) { + header->size += sizeof(heap_header_t) + header->next->size; + header->next = header->next->next; + } + + // TODO: We should ideally merge with PREVIOUS block too, + // but a singly linked list makes that O(N). + // A doubly linked list is better for production heaps. +} -- 2.43.0