diff --git a/.github/workflows/pr-test.yml b/.github/workflows/pr-test.yml deleted file mode 100644 index 481659b..0000000 --- a/.github/workflows/pr-test.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: PR Test - -on: - pull_request: - branches: [ "main" ] - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - name: Install deps - run: | - sudo apt update - sudo apt install build-essential qemu-system-x86 make ovmf -y - - name: Build OS - run: make all -j$(nproc) diff --git a/Makefile b/Makefile index bdadf82..98c043e 100644 --- a/Makefile +++ b/Makefile @@ -4,11 +4,11 @@ LDFLAGS = -shared -Bsymbolic -Lgnu-efi/x86_64/lib -Lgnu-efi/x86_64/gnuefi -Tgnu- LDLIBS = -lgnuefi -lefi --no-undefined SRC_C = $(wildcard *.c) -SRC_CPP = $(wildcard */*.cpp) +SRC_CPP = $(wildcard */*.cpp) $(wildcard */*/*.cpp) OBJ = $(SRC_C:%.c=build/%.o) $(SRC_CPP:%.cpp=build/%.o) _bd: - @mkdir -p build/graphics build/kernel build/fonts + @mkdir -p build/graphics build/kernel build/fonts build/kernel/memory gnu-efi/x86_64/gnuefi/crt0-efi-x86_64.o: @echo "* Building gnu-efi..." diff --git a/include/memory/heap.h b/include/memory/heap.h new file mode 100644 index 0000000..6a3bab8 --- /dev/null +++ b/include/memory/heap.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +void init_heap(); +void* kmalloc(UINTN size); +void kfree(void* ptr); +void* kcalloc(UINTN num, UINTN size); +void* krealloc(void* ptr, UINTN new_size); diff --git a/include/memory/pmm.h b/include/memory/pmm.h new file mode 100644 index 0000000..41415d4 --- /dev/null +++ b/include/memory/pmm.h @@ -0,0 +1,25 @@ +#pragma once + +#include + +#define PAGE_SIZE 4096 +#define PAGE_SHIFT 12 +#define PAGE_MASK (~(PAGE_SIZE - 1ULL)) + +typedef struct { + UINT8* bitmap; + UINTN bitmap_size; + UINTN total_pages; + UINTN free_pages; + void* free_list_head; + UINT64 base_addr; + UINT64 max_addr; +} pmm_t; + +extern pmm_t g_pmm; + +EFI_STATUS pmm_init(); +void* pmm_alloc_pages(UINTN n); +void pmm_free_pages(void* addr, UINTN n); +UINTN pmm_get_free_count(); +BOOLEAN pmm_is_page_free(void* addr); diff --git a/include/serial.h b/include/serial.h index 88ae3e0..6cfccc7 100644 --- a/include/serial.h +++ b/include/serial.h @@ -12,4 +12,5 @@ extern serial_context g_serial; void serial_init(EFI_SERIAL_IO_PROTOCOL *SerialIo); // 初始化串行驱动 void serial_write(const char *str); // 往串行写string void serial_write_char(char c); // 往串行写char(不推荐使用) +void serial_write_hex(UINTN val); // 往串行写十六进制数字 char serial_read_char(); // 读串行 \ No newline at end of file diff --git a/kernel/main.cpp b/kernel/main.cpp index 72bc386..08b6163 100644 --- a/kernel/main.cpp +++ b/kernel/main.cpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include inline void init_gop() { // 初始化 GOP @@ -51,6 +53,47 @@ extern "C" void kernel_main() { uefi_call_wrapper((void*)ST->ConOut->ClearScreen, 1, ST->ConOut); serial_write("\n\n"); // 防止和前面串了serial.log看不清 + // init memory managers + serial_write("Sylva: init PMM...\n"); + EFI_STATUS st = pmm_init(); + if (EFI_ERROR(st)) { + serial_write("Sylva: PMM init FAILED!\n"); + } else { + serial_write("Sylva: PMM init OK\n"); + serial_write("Sylva: free pages = "); + serial_write_hex(pmm_get_free_count()); + serial_write("\n"); + } + + serial_write("Sylva: init heap...\n"); + init_heap(); + + // test kmalloc/kfree + serial_write("Sylva: kmalloc test...\n"); + void* p1 = kmalloc(64); + void* p2 = kmalloc(128); + void* p3 = kmalloc(256); + serial_write("Sylva: p1 = "); + serial_write_hex((UINTN)p1); + serial_write(" p2 = "); + serial_write_hex((UINTN)p2); + serial_write(" p3 = "); + serial_write_hex((UINTN)p3); + serial_write("\n"); + + serial_write("Sylva: kfree test...\n"); + kfree(p2); + kfree(p1); + kfree(p3); + + void* p4 = kmalloc(32); + serial_write("Sylva: realloc p4 = "); + serial_write_hex((UINTN)p4); + serial_write("\n"); + kfree(p4); + + serial_write("Sylva: memory init done.\n"); + pf_print("Welcome to Sylva OS!\n"); serial_write(" Kernel prepared well, start loop.\n"); diff --git a/kernel/memory/heap.cpp b/kernel/memory/heap.cpp new file mode 100644 index 0000000..1cf4d0f --- /dev/null +++ b/kernel/memory/heap.cpp @@ -0,0 +1,234 @@ +#include +#include +#include +#include + +struct heap_block { + UINTN size; // includes header; bit 0 = 1 used, 0 free + struct heap_block* next; // free list link (only valid when free) +}; + +#define HEAP_ALIGN 16 +#define HEADER_SIZE ((UINTN)sizeof(struct heap_block)) +#define MIN_BLOCK_SIZE (HEADER_SIZE + HEAP_ALIGN) + +#define HEAP_INIT_PAGES 4 + +#define BLOCK_SIZE(block) ((block)->size & ~(UINTN)1) +#define IS_USED(block) ((block)->size & 1) +#define IS_FREE(block) (!IS_USED(block)) + +static struct heap_block* g_heap_free_list = NULL; +static void* g_heap_start = NULL; +static void* g_heap_end = NULL; + +static UINTN align_up(UINTN val, UINTN align) { + return (val + align - 1) & ~(align - 1ULL); +} + +static struct heap_block* next_block(struct heap_block* block) { + return (struct heap_block*)((UINT8*)block + BLOCK_SIZE(block)); +} + +static void heap_expand(UINTN min_size) { + UINTN pages = (min_size + PAGE_SIZE - 1) / PAGE_SIZE; + void* mem = pmm_alloc_pages(pages); + if (!mem) { + serial_write("HEAP: expand failed!\n"); + return; + } + + struct heap_block* new_block = (struct heap_block*)mem; + new_block->size = pages * PAGE_SIZE; + new_block->next = NULL; + + // Add to free list (sorted by address for coalescing) + struct heap_block** prev = &g_heap_free_list; + while (*prev && (UINT8*)*prev < (UINT8*)new_block) { + prev = &(*prev)->next; + } + new_block->next = *prev; + *prev = new_block; + + // Try to merge with the previous free block if adjacent + if (prev != &g_heap_free_list) { + struct heap_block* prev_block = g_heap_free_list; + while (prev_block->next != new_block) { + prev_block = prev_block->next; + } + if ((UINT8*)prev_block + BLOCK_SIZE(prev_block) == (UINT8*)new_block) { + prev_block->size += new_block->size; + prev_block->next = new_block->next; + new_block = prev_block; + } + } + + if ((UINT8*)new_block + BLOCK_SIZE(new_block) > (UINT8*)g_heap_end) { + g_heap_end = (UINT8*)new_block + BLOCK_SIZE(new_block); + } + + if (ENABLE_SERIAL_PRINTS) { + serial_write("HEAP: expanded by "); + serial_write_hex(pages * PAGE_SIZE); + serial_write(" bytes\n"); + } +} + +void init_heap() { + void* mem = pmm_alloc_pages(HEAP_INIT_PAGES); + if (!mem) { + serial_write("HEAP: init failed!\n"); + return; + } + + g_heap_start = mem; + g_heap_end = (void*)((UINT8*)mem + HEAP_INIT_PAGES * PAGE_SIZE); + + struct heap_block* initial = (struct heap_block*)mem; + initial->size = HEAP_INIT_PAGES * PAGE_SIZE; + initial->next = NULL; + g_heap_free_list = initial; + + if (ENABLE_SERIAL_PRINTS) { + serial_write("HEAP: init OK, "); + serial_write_hex(HEAP_INIT_PAGES * PAGE_SIZE); + serial_write(" bytes @ "); + serial_write_hex((UINTN)mem); + serial_write("\n"); + } +} + +void* kmalloc(UINTN size) { + if (size == 0) return NULL; + + UINTN alloc_size = align_up(size + HEADER_SIZE, HEAP_ALIGN); + if (alloc_size < MIN_BLOCK_SIZE) alloc_size = MIN_BLOCK_SIZE; + + struct heap_block** prev = &g_heap_free_list; + while (*prev) { + UINTN block_sz = BLOCK_SIZE(*prev); + if (block_sz >= alloc_size) { + // Found a suitable block + struct heap_block* block = *prev; + + // Split if remaining space is useful + if (block_sz >= alloc_size + MIN_BLOCK_SIZE) { + struct heap_block* split = (struct heap_block*)((UINT8*)block + alloc_size); + split->size = block_sz - alloc_size; + // Insert split into free list + split->next = block->next; + block->size = alloc_size; + *prev = split; + } else { + // Use the whole block + *prev = block->next; + block->size = block_sz | 1; // mark used + } + + if (ENABLE_SERIAL_PRINTS && size > 1024) { + serial_write("HEAP: kmalloc "); + serial_write_hex(size); + serial_write(" -> "); + serial_write_hex((UINTN)(block + 1)); + serial_write("\n"); + } + + return (void*)(block + 1); + } + prev = &(*prev)->next; + } + + // Out of memory in current heap — expand + UINTN expand_size = alloc_size > PAGE_SIZE ? alloc_size : PAGE_SIZE; + heap_expand(expand_size); + + // Retry after expansion + return kmalloc(size); +} + +void kfree(void* ptr) { + if (!ptr) return; + + struct heap_block* block = (struct heap_block*)ptr - 1; + if (IS_FREE(block)) { + serial_write("HEAP: double free detected!\n"); + return; + } + + // Mark as free + block->size &= ~(UINTN)1; + + // Merge with next block if it's free + struct heap_block* next = next_block(block); + if ((UINT8*)next < (UINT8*)g_heap_end) { + if (IS_FREE(next)) { + // Remove next from free list and merge + block->size += next->size; + struct heap_block** prev = &g_heap_free_list; + while (*prev && *prev != next) { + prev = &(*prev)->next; + } + if (*prev) *prev = next->next; + } + } + + // Insert block into free list + struct heap_block** prev = &g_heap_free_list; + while (*prev && (UINT8*)*prev < (UINT8*)block) { + prev = &(*prev)->next; + } + block->next = *prev; + *prev = block; + + if (ENABLE_SERIAL_PRINTS) { + serial_write("HEAP: kfree @ "); + serial_write_hex((UINTN)ptr); + serial_write("\n"); + } +} + +void* kcalloc(UINTN num, UINTN size) { + UINTN total = num * size; + void* ptr = kmalloc(total); + if (ptr) { + UINT8* p = (UINT8*)ptr; + for (UINTN i = 0; i < total; i++) { + p[i] = 0; + } + } + return ptr; +} + +void* krealloc(void* ptr, UINTN new_size) { + if (!ptr) return kmalloc(new_size); + if (new_size == 0) { + kfree(ptr); + return NULL; + } + + struct heap_block* block = (struct heap_block*)ptr - 1; + UINTN old_size = BLOCK_SIZE(block) - HEADER_SIZE; + + if (old_size >= new_size) { + // Can we split the shrinkage? + UINTN shrink = old_size - new_size; + if (shrink >= MIN_BLOCK_SIZE) { + block->size = (new_size + HEADER_SIZE) | 1; + struct heap_block* split = (struct heap_block*)((UINT8*)ptr + new_size); + split->size = shrink; + kfree(split + 1); + } + return ptr; + } + + void* new_ptr = kmalloc(new_size); + if (new_ptr) { + UINT8* src = (UINT8*)ptr; + UINT8* dst = (UINT8*)new_ptr; + for (UINTN i = 0; i < old_size; i++) { + dst[i] = src[i]; + } + kfree(ptr); + } + return new_ptr; +} diff --git a/kernel/memory/pmm.cpp b/kernel/memory/pmm.cpp new file mode 100644 index 0000000..916e5d0 --- /dev/null +++ b/kernel/memory/pmm.cpp @@ -0,0 +1,267 @@ +#include +#include +#include +#include + +pmm_t g_pmm; + +static inline void bitmap_set(UINTN idx) { + g_pmm.bitmap[idx / 8] |= (1 << (idx % 8)); +} + +static inline void bitmap_clear(UINTN idx) { + g_pmm.bitmap[idx / 8] &= ~(1 << (idx % 8)); +} + +static inline BOOLEAN bitmap_test(UINTN idx) { + return (g_pmm.bitmap[idx / 8] >> (idx % 8)) & 1; +} + +// Clean stale entries from free list head +static void clean_free_list() { + while (g_pmm.free_list_head != NULL && + bitmap_test((UINTN)g_pmm.free_list_head / PAGE_SIZE)) { + g_pmm.free_list_head = *(void**)g_pmm.free_list_head; + } +} + +EFI_STATUS pmm_init() { + UINTN map_size = 0; + UINTN map_key; + UINTN desc_size; + UINT32 desc_version; + + EFI_STATUS status = uefi_call_wrapper( + (void*)ST->BootServices->GetMemoryMap, 5, + &map_size, NULL, &map_key, &desc_size, &desc_version + ); + + map_size += desc_size * 64; + + EFI_MEMORY_DESCRIPTOR* mem_map = NULL; + status = uefi_call_wrapper( + (void*)ST->BootServices->AllocatePool, 3, + EfiLoaderData, map_size, (void**)&mem_map + ); + if (EFI_ERROR(status)) return status; + + status = uefi_call_wrapper( + (void*)ST->BootServices->GetMemoryMap, 5, + &map_size, mem_map, &map_key, &desc_size, &desc_version + ); + if (EFI_ERROR(status)) { + uefi_call_wrapper((void*)ST->BootServices->FreePool, 1, mem_map); + return status; + } + + UINTN entry_count = map_size / desc_size; + + // First pass: count total pages and find max physical address + UINT64 max_addr = 0; + UINT64 total_free = 0; + for (UINTN i = 0; i < entry_count; i++) { + EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((UINT8*)mem_map + i * desc_size); + UINT64 end = desc->PhysicalStart + desc->NumberOfPages * PAGE_SIZE; + if (end > max_addr) max_addr = end; + if (desc->Type == EfiConventionalMemory) { + total_free += desc->NumberOfPages * PAGE_SIZE; + } + } + + g_pmm.base_addr = 0; + g_pmm.max_addr = max_addr; + + // How many pages does the bitmap cover? + UINTN total_pages = (UINTN)(max_addr / PAGE_SIZE); + g_pmm.total_pages = total_pages; + + // Bitmap size in bytes, rounded up to page boundary + g_pmm.bitmap_size = ((total_pages + 7) / 8); + UINTN bitmap_pages = (g_pmm.bitmap_size + PAGE_SIZE - 1) / PAGE_SIZE; + g_pmm.bitmap_size = bitmap_pages * PAGE_SIZE; // round to full pages + + // Place bitmap at the end of the highest free conventional memory region + UINT64 bitmap_addr = 0; + for (UINTN i = 0; i < entry_count; i++) { + EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((UINT8*)mem_map + i * desc_size); + if (desc->Type == EfiConventionalMemory) { + UINT64 region_bytes = desc->NumberOfPages * PAGE_SIZE; + if (region_bytes >= g_pmm.bitmap_size) { + UINT64 candidate = desc->PhysicalStart + region_bytes - g_pmm.bitmap_size; + if (candidate > bitmap_addr) { + bitmap_addr = candidate; + } + } + } + } + + if (bitmap_addr == 0) { + serial_write("PMM: ERROR - no space for bitmap!\n"); + uefi_call_wrapper((void*)ST->BootServices->FreePool, 1, mem_map); + return EFI_OUT_OF_RESOURCES; + } + + g_pmm.bitmap = (UINT8*)(UINTN)bitmap_addr; + + // Init bitmap: mark ALL pages as used (0xFF) + for (UINTN i = 0; i < g_pmm.bitmap_size; i++) { + g_pmm.bitmap[i] = 0xFF; + } + + // Mark free pages (EfiConventionalMemory) as free in bitmap + g_pmm.free_pages = 0; + UINT64 bm_start_page = bitmap_addr / PAGE_SIZE; + UINT64 bm_end_page = (bitmap_addr + g_pmm.bitmap_size + PAGE_SIZE - 1) / PAGE_SIZE; + + for (UINTN i = 0; i < entry_count; i++) { + EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((UINT8*)mem_map + i * desc_size); + if (desc->Type != EfiConventionalMemory) continue; + + UINT64 start_page = desc->PhysicalStart / PAGE_SIZE; + UINT64 end_page = start_page + desc->NumberOfPages; + + for (UINT64 p = start_page; p < end_page; p++) { + // Skip bitmap pages + if (p >= bm_start_page && p < bm_end_page) continue; + bitmap_clear((UINTN)p); + g_pmm.free_pages++; + } + } + + // Mark bitmap pages as used + for (UINT64 p = bm_start_page; p < bm_end_page; p++) { + bitmap_set((UINTN)p); + } + + // Build free list by linking free pages + g_pmm.free_list_head = NULL; + void* prev = NULL; + for (UINTN i = 0; i < entry_count; i++) { + EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((UINT8*)mem_map + i * desc_size); + if (desc->Type != EfiConventionalMemory) continue; + + UINT64 start_page = desc->PhysicalStart / PAGE_SIZE; + UINT64 end_page = start_page + desc->NumberOfPages; + + for (UINT64 p = start_page; p < end_page; p++) { + if (p >= bm_start_page && p < bm_end_page) continue; + void* page = (void*)(UINTN)(p * PAGE_SIZE); + if (prev) { + *(void**)prev = page; + } else { + g_pmm.free_list_head = page; + } + prev = page; + } + } + if (prev) *(void**)prev = NULL; + + uefi_call_wrapper((void*)ST->BootServices->FreePool, 1, mem_map); + + if (ENABLE_SERIAL_PRINTS) { + serial_write("PMM: init OK, total "); + serial_write_hex(total_pages); + serial_write(" pages ("); + serial_write_hex(total_free / (1024*1024)); + serial_write(" MB free), bitmap "); + serial_write_hex(bitmap_pages); + serial_write(" pages @ "); + serial_write_hex(bitmap_addr); + serial_write("\n"); + } + + return EFI_SUCCESS; +} + +void* pmm_alloc_pages(UINTN n) { + if (n == 0) return NULL; + + clean_free_list(); + + if (n == 1) { + if (g_pmm.free_list_head == NULL) { + serial_write("PMM: OOM (no free pages)\n"); + return NULL; + } + void* page = g_pmm.free_list_head; + g_pmm.free_list_head = *(void**)page; + *(void**)page = NULL; // clear the next pointer + + UINTN idx = (UINTN)page / PAGE_SIZE; + bitmap_set(idx); + g_pmm.free_pages--; + + if (ENABLE_SERIAL_PRINTS) { + serial_write("PMM: alloc 1 page @ "); + serial_write_hex((UINTN)page); + serial_write("\n"); + } + return page; + } + + // n > 1: scan bitmap for n consecutive free pages + UINTN consecutive = 0; + UINTN start_idx = 0; + for (UINTN i = 0; i < g_pmm.total_pages; i++) { + if (!bitmap_test(i)) { + if (consecutive == 0) start_idx = i; + consecutive++; + if (consecutive == n) { + // Found n consecutive free pages + void* base = (void*)(UINTN)(start_idx * PAGE_SIZE); + for (UINTN j = 0; j < n; j++) { + bitmap_set(start_idx + j); + } + g_pmm.free_pages -= n; + + if (ENABLE_SERIAL_PRINTS) { + serial_write("PMM: alloc "); + serial_write_hex(n); + serial_write(" pages @ "); + serial_write_hex((UINTN)base); + serial_write("\n"); + } + return base; + } + } else { + consecutive = 0; + } + } + + serial_write("PMM: OOM (no contiguous free pages)\n"); + return NULL; +} + +void pmm_free_pages(void* addr, UINTN n) { + UINTN start_idx = (UINTN)addr / PAGE_SIZE; + + // Add freed pages to free list + for (UINTN i = 0; i < n; i++) { + UINTN idx = start_idx + i; + bitmap_clear(idx); + g_pmm.free_pages++; + + void* page = (void*)(UINTN)(idx * PAGE_SIZE); + // Push to front of free list + *(void**)page = g_pmm.free_list_head; + g_pmm.free_list_head = page; + } + + if (ENABLE_SERIAL_PRINTS) { + serial_write("PMM: free "); + serial_write_hex(n); + serial_write(" pages @ "); + serial_write_hex((UINTN)addr); + serial_write("\n"); + } +} + +UINTN pmm_get_free_count() { + return g_pmm.free_pages; +} + +BOOLEAN pmm_is_page_free(void* addr) { + UINTN idx = (UINTN)addr / PAGE_SIZE; + if (idx >= g_pmm.total_pages) return FALSE; + return !bitmap_test(idx); +} diff --git a/kernel/serial.cpp b/kernel/serial.cpp index b625f0b..5c29238 100644 --- a/kernel/serial.cpp +++ b/kernel/serial.cpp @@ -35,6 +35,18 @@ void serial_write(const char *str) { } } +void serial_write_hex(UINTN val) { + char buf[19]; + buf[0] = '0'; buf[1] = 'x'; + for (int i = 17; i >= 2; i--) { + UINTN digit = val & 0xF; + buf[i] = digit < 10 ? '0' + digit : 'A' + digit - 10; + val >>= 4; + } + buf[18] = '\0'; + serial_write(buf); +} + char serial_read_char() { // 后面可能用的上,比如远程调试? if (!g_serial.SerialIo) return 0;