From 943f2beab988a876fbd8cbd3679b79ae27033a20 Mon Sep 17 00:00:00 2001 From: 0x35c Date: Sat, 21 Sep 2024 12:17:27 +0200 Subject: [PATCH] feature: kmalloc kfree and krealloc are good --- headers/alloc.h | 85 ++++++++++++++++++++ headers/memory.h | 2 +- libbozo/Makefile | 36 +++++---- libbozo/headers/string.h | 2 + libbozo/src/string/memmove.c | 21 +++++ libbozo/src/string/strcpy.c | 12 +++ src/kernel.c | 7 ++ src/memory/alloc/allocator.c | 64 +++++++++++++++ src/memory/alloc/info.c | 49 ++++++++++++ src/memory/alloc/kalloc.c | 150 +++++++++++++++++++++++++++++++++++ src/memory/alloc/kfree.c | 115 +++++++++++++++++++++++++++ src/memory/alloc/krealloc.c | 36 +++++++++ src/memory/alloc/utils.c | 34 ++++++++ src/memory/frame.c | 14 ++-- src/shell/exec.c | 3 +- 15 files changed, 607 insertions(+), 23 deletions(-) create mode 100644 headers/alloc.h create mode 100644 libbozo/src/string/memmove.c create mode 100644 libbozo/src/string/strcpy.c create mode 100644 src/memory/alloc/allocator.c create mode 100644 src/memory/alloc/info.c create mode 100644 src/memory/alloc/kalloc.c create mode 100644 src/memory/alloc/kfree.c create mode 100644 src/memory/alloc/krealloc.c create mode 100644 src/memory/alloc/utils.c diff --git a/headers/alloc.h b/headers/alloc.h new file mode 100644 index 0000000..9361045 --- /dev/null +++ b/headers/alloc.h @@ -0,0 +1,85 @@ +#pragma once + +// boolean types +#include + +// size_t, already in libft.h but for readability +#include + +// Remove this and replace it with header +// for debugging purposes +/* #include */ +#define assert(bool) + +// BPZ = Blocks Per Zone, which is the max +// number of blocks for a new zone +enum { BPZ = 128, PAGES_TINY = 16, PAGES_SMALL = 64, MEM_ALIGN = 8 }; + +typedef enum { TINY, SMALL, LARGE } block_type_t; + +/* METADATA: + * ptr: the ptr to return with kalloc (aligned) + * size: the actual size + * sub_size: the size asked by the user (different + * from size only if krealloc and krealloc size < size) + * in_use: bool to track block state + * zone: the zone containing the block + * + * LINKED LIST: + * next and prev will never change, it's the original block's + * position (initialized when creating the blocks) + * next/prev_used: linked list for the + * in_use blocks (Block *used in struct Zone) + * next/prev_free: linked list for the + * available blocks (Block *free in struct Zone) + */ +typedef struct Block { + + void *ptr; + size_t size; + size_t sub_size; + bool in_use; + struct Zone *zone; + + struct Block *prev; + struct Block *next; + struct Block *prev_used; + struct Block *next_used; + struct Block *prev_free; + struct Block *next_free; +} Block; + +/* free is the first list, when creating the blocks + * used is a list at the end of the free list, which contains all the blocks + * in_use + */ +typedef struct Zone { + size_t size; + struct Zone *prev; + struct Zone *next; + block_type_t type; + Block *free; + Block *used; +} Zone; + +/* Linked list to store all the zones (pages) mapped. + * The attribute type is either TINY, SMALL or LARGE. + * For TINY and SMALL, the zone will be divided in blocks. + * For LARGE, it will be entire page(s). + */ +extern Zone *zones[3]; + +/*----------- UTILS ----------*/ +block_type_t get_type(size_t size); +size_t get_zone_size(block_type_t type); +size_t align_mem(size_t addr); +/*----------------------------*/ + +/*-------- ALLOCATOR ---------*/ +int new_zone(block_type_t type, size_t size); +/*----------------------------*/ + +void *kalloc(size_t size); +void kfree(void *ptr); +void *krealloc(void *ptr, size_t size); +void show_alloc_mem(void); diff --git a/headers/memory.h b/headers/memory.h index 318ef00..f653109 100644 --- a/headers/memory.h +++ b/headers/memory.h @@ -11,4 +11,4 @@ void init_memory(void); void *kalloc_frame(uint32_t nb_frames); -void kfree_frame(void *frame, uint32_t nb_frames); +int kfree_frame(void *frame, uint32_t nb_frames); diff --git a/libbozo/Makefile b/libbozo/Makefile index ab9f590..87528f6 100644 --- a/libbozo/Makefile +++ b/libbozo/Makefile @@ -1,34 +1,38 @@ -SRCDIR = src -OBJDIR = obj -BUILDDIR = build +SSRC := $(shell find src -name '*.s') +CSRC := $(shell find src -name '*.c') +OBJ := $(patsubst src/%.c,obj/%.o,$(CSRC))\ + $(patsubst src/%.s,obj/%.o,$(SSRC)) -SRC := $(shell find $(SRCDIR) -name '*.c') -OBJ := $(patsubst $(SRCDIR)/%.c,$(OBJDIR)/%.o,$(SRC)) +CC := i386-elf-gcc +CFLAGS := -std=gnu99 -ffreestanding -O2 -Wall -Wextra -iquoteheaders -c -CC = i386-elf-gcc -CFLAGS = -std=gnu99 -ffreestanding -O2 -Wall -Wextra -iquoteheaders -c - -AR = ar -ARFLAGS = +AS := i386-elf-as +ASFLAGS := +AR := ar +ARFLAGS := NAME = libbozo.a -$(OBJDIR)/%.o: $(SRCDIR)/%.c +obj/%.o: src/%.s + mkdir -p $(dir $@) + $(AS) $(ASFLAGS) $< -o $@ + +obj/%.o: src/%.c mkdir -p $(dir $@) $(CC) $(CFLAGS) $< -o $@ all : $(NAME) clean : - rm -rf $(OBJDIR) + rm -rf obj fclean : clean - rm -rf $(BUILDDIR) + rm -rf build $(NAME) : $(OBJ) - mkdir -p $(BUILDDIR) - $(AR) -rc $(BUILDDIR)/$(NAME) $(OBJ) + mkdir -p build + $(AR) -rc build/$(NAME) $(OBJ) re: fclean all -.PHONY: clean fclean test all re +.PHONY: clean fclean all re diff --git a/libbozo/headers/string.h b/libbozo/headers/string.h index 3584da7..b3d2f2e 100644 --- a/libbozo/headers/string.h +++ b/libbozo/headers/string.h @@ -7,6 +7,8 @@ int strcmp(const char *s1, const char *s2); int strncmp(const char *s1, const char *s2, size_t n); size_t strlen(const char *str); char *strstr(const char *haystack, const char *needle); +char *strcpy(char *dest, const char *src); void *memcpy(void *dest, const void *src, size_t n); int memcmp(const void *s1, const void *s2, size_t n); void *memset(void *str, int c, size_t n); +void *memmove(void *dest, const void *src, size_t n); diff --git a/libbozo/src/string/memmove.c b/libbozo/src/string/memmove.c new file mode 100644 index 0000000..6729edd --- /dev/null +++ b/libbozo/src/string/memmove.c @@ -0,0 +1,21 @@ +#include + +void *memmove(void *dest, const void *src, size_t n) +{ + size_t i = 0; + const char *cast1 = (const char *)src; + char *cast2 = (char *)dest; + + if (!cast1 && !cast2 && n > 0) + return (0); + if (&cast1[0] > &cast2[0]) { + while (i < n) { + cast2[i] = cast1[i]; + i++; + } + } else { + while (n--) + cast2[n] = cast1[n]; + } + return cast2; +} diff --git a/libbozo/src/string/strcpy.c b/libbozo/src/string/strcpy.c new file mode 100644 index 0000000..900e2a7 --- /dev/null +++ b/libbozo/src/string/strcpy.c @@ -0,0 +1,12 @@ +#include + +char *strcpy(char *dest, const char *src) +{ + size_t i = 0; + if (!src) + return NULL; + for (; src[i]; i++) + dest[i] = src[i]; + dest[i] = '\0'; + return dest; +} diff --git a/src/kernel.c b/src/kernel.c index ab23571..e49d2e7 100644 --- a/src/kernel.c +++ b/src/kernel.c @@ -1,3 +1,4 @@ +#include "alloc.h" #include "gdt.h" #include "kprintf.h" #include "memory.h" @@ -31,5 +32,11 @@ void kernel_main(void) kprintf(KERN_NOTICE "KERN_NOTICE\n"); kprintf(KERN_INFO "KERN_INFO\n"); kprintf(KERN_DEBUG "KERN_DEBUG\n"); + char *str = kalloc(10); + kfree(str); + str = kalloc(10); + show_alloc_mem(); + strcpy(str, "Hello world\n"); + kprintf("%s", str); shell_init(); } diff --git a/src/memory/alloc/allocator.c b/src/memory/alloc/allocator.c new file mode 100644 index 0000000..57ef882 --- /dev/null +++ b/src/memory/alloc/allocator.c @@ -0,0 +1,64 @@ +#include "alloc.h" +#include "kprintf.h" +#include "memory.h" + +Zone *zones[3]; + +static void add_zone(Zone *zone, block_type_t type) +{ + // We put the zone at the beginning of the list + if (zones[type]) { + zone->next = zones[type]; + zones[type]->prev = zone; + } + zones[type] = zone; +} + +static void new_block(Zone *zone, size_t zone_size) +{ + Block *new_block = (Block *)align_mem((size_t)zone + sizeof(Zone)); + + // Metadata + new_block->in_use = false; + new_block->size = zone_size - sizeof(Zone) - sizeof(Block); + new_block->sub_size = new_block->size; + new_block->ptr = (Block *)((size_t)new_block + sizeof(Block)); + new_block->zone = zone; + + // Init future linked lists + new_block->prev = NULL; + new_block->prev_free = NULL; + new_block->prev_used = NULL; + new_block->next = NULL; + new_block->next_free = NULL; + new_block->next_used = NULL; + + if (zone->free) { + zone->free->prev = new_block; + zone->free->prev_free = new_block; + new_block->next = zone->free; + new_block->next_free = zone->free; + } + zone->free = new_block; +} + +int new_zone(block_type_t type, size_t size) +{ + void *heap = kalloc_frame(size); + if (heap == NULL) { + kprintf(KERN_ERR "error: syscall mmap failed\n"); + return (-1); + } + + Zone *zone = (Zone *)heap; + zone->type = type; + zone->size = size; + zone->used = NULL; + zone->next = NULL; + zone->prev = NULL; + + new_block(zone, size); + add_zone(heap, type); + + return (0); +} diff --git a/src/memory/alloc/info.c b/src/memory/alloc/info.c new file mode 100644 index 0000000..3e13483 --- /dev/null +++ b/src/memory/alloc/info.c @@ -0,0 +1,49 @@ +#include "alloc.h" +#include "kprintf.h" + +// FULL_INFO is to display (or not) both used and unused blocks +#define FULL_INFO 0 + +void show_alloc_mem(void) +{ + char *const zones_name[3] = {"TINY", "SMALL", "LARGE"}; + size_t total_size = 0; + + for (block_type_t type = 0; type < 3; ++type) { + int count = 0; + for (Zone *zone_it = zones[type]; zone_it != NULL; + zone_it = zone_it->next) { +#if FULL_INFO + if (zone_it->kfree) + kprintf("---------- AVAILABLE %s [n°%d - %p] " + "----------\n", + zones_name[type], count, zone_it); + for (Block *block_it = zone_it->kfree; block_it != NULL; + block_it = block_it->next_kfree) { + kprintf("%p - %p : %u bytes\n", block_it->ptr, + (size_t)block_it->ptr + + block_it->sub_size + sizeof(Block), + block_it->sub_size); + } + if (zone_it->kfree) + ft_printf("\n"); +#endif + if (zone_it->used) + kprintf("---------- IN_USE %s [n°%d - %p] " + "----------\n", + zones_name[type], count, zone_it); + for (Block *block_it = zone_it->used; block_it != NULL; + block_it = block_it->next_used) { + kprintf("%p - %p : %u bytes\n", block_it->ptr, + (size_t)block_it->ptr + + block_it->sub_size + sizeof(Block), + block_it->sub_size); + total_size += block_it->sub_size; + } + if (zone_it->used) + kprintf("\n"); + count++; + } + } + kprintf("Total: %u\n", total_size); +} diff --git a/src/memory/alloc/kalloc.c b/src/memory/alloc/kalloc.c new file mode 100644 index 0000000..7ed2a22 --- /dev/null +++ b/src/memory/alloc/kalloc.c @@ -0,0 +1,150 @@ +#include "alloc.h" +#include "kprintf.h" + +/* + * Find first available (not in_use) block + * in a zone matching the size we need + */ +static Block *find_block(Zone *head, size_t size) +{ + for (Zone *zone_it = head; zone_it != NULL; zone_it = zone_it->next) { + for (Block *block_it = zone_it->free; block_it != NULL; + block_it = block_it->next_free) { + assert(!block_it->in_use); + if (size <= block_it->size) { + assert(block_it->zone == zone_it); + return (block_it); + } + } + } + return (NULL); +} + +// PARTIALLY DEPRECATED +/* + * This will split the newly allocated block to use + * the remaining bytes for a new block + * This is our linked list of blocks + * ... -> [5] -> [6] -> ... + * After the allocation, this will become + * ... -> [5] -> [new] -> [6] -> ... + * + * For an example of [5].size = 32 and requiring a kalloc of 10 + * Let's say the metadata takes a size of 2: + * ... -> [metadata][data][remaining size] -> [6] + * ^ ^ ^ + * 2 10 20 + * + * So now our block [new] will become: + * [5] -> [metadata][available data] -> [6] + * ^ ^ + * 2 18 + * We can see that it now has its own metadata and available + * data and it points towards [6] + */ +static void frag_block(Zone *zone, Block *old_block, size_t size) +{ + Block *new_block = (Block *)align_mem((size_t)old_block + size); + assert(!(new_block >= + (Block *)((size_t)zone + get_zone_size(zone->type)))); + + // Newly created block metadata + new_block->size = old_block->size - size; + new_block->sub_size = new_block->size; + new_block->in_use = false; + new_block->ptr = (void *)((size_t)new_block + sizeof(Block)); + new_block->zone = zone; + + new_block->prev = old_block; + new_block->next = old_block->next; + old_block->next = new_block; + + new_block->prev_used = NULL; + new_block->next_used = NULL; + + new_block->prev_free = old_block->prev_free; + new_block->next_free = old_block->next_free; + + if (zone->free == old_block) + zone->free = new_block; + + old_block->next_free = NULL; + old_block->prev_free = NULL; + + // Newly in_use block metadata + old_block->in_use = true; + old_block->size = size - sizeof(Block); + old_block->sub_size = old_block->size; + + if (zone->used == NULL) { + zone->used = old_block; + return; + } + old_block->prev_used = NULL; + old_block->next_used = zone->used; + zone->used->prev_used = old_block; + zone->used = old_block; +} + +// Set the block to use and unset free +static void save_block(Zone *head, Block *block, Zone *zone) +{ + zone->free = NULL; + block->in_use = true; + if (head->used) { + head->used->prev_used = block; + head->used->prev = block; + block->next = head->used; + block->next_used = head->used; + } + head->used = block; +} + +/* + * size: size needed by the user to get allocated + * + * First, we init the allocator if it's the first time + * Then we search if there is an available block in all + * the zones currently mapped + * If no block has been found (NULL), we create 1 new zone of + * the corresponding type + * We then search again for an available block (should not be NULL) + * Finally, if type == LARGE, we just have to change the block to used + * else, we frag the block to use just what's needed + * + * ptr: returns the aligned pointer of the block (after the metadata) + */ +void *kalloc(size_t size) +{ + void *ptr = NULL; + + if (size == 0) { + kprintf(KERN_WARNING "kalloc: can't kalloc(0)\n"); + return NULL; + } + + // Find the zone we need to search + block_type_t type = get_type(size); + Zone *head = zones[type]; + + // Find an available block in a zone of type "type" + Block *available = find_block(head, size); + if (available == NULL) { + size_t full_size; + if (type == LARGE) + full_size = size + sizeof(Block) + sizeof(Zone); + else + full_size = get_zone_size(type); + if (new_zone(type, full_size) == -1) + return NULL; + head = zones[type]; + available = find_block(head, size); + } + assert(available != NULL); + if (type == LARGE) + save_block(head, available, available->zone); + else + frag_block(available->zone, available, size + sizeof(Block)); + ptr = available->ptr; + return ptr; +} diff --git a/src/memory/alloc/kfree.c b/src/memory/alloc/kfree.c new file mode 100644 index 0000000..2948054 --- /dev/null +++ b/src/memory/alloc/kfree.c @@ -0,0 +1,115 @@ +#include "alloc.h" +#include "kprintf.h" +#include "memory.h" + +static void remove_used(Block *to_kfree) +{ + Block *left = to_kfree->prev_used; + Block *right = to_kfree->next_used; + + to_kfree->next_used = NULL; + to_kfree->prev_used = NULL; + + if (!left && !right) { + to_kfree->zone->used = NULL; + return; + } + if (!left) + to_kfree->zone->used = right; + else + left->next_used = right; + if (right) + right->prev_used = left; +} + +/* + * If all the blocks of the zone have been kfreed, + * we can unmap the zone and delete it from the list of zones + */ +static int unmap_zone(Zone *zone) +{ + int err = 0; + block_type_t type = zone->type; + Zone *left = zone->prev; + Zone *right = zone->next; + zone->prev = NULL; + zone->next = NULL; + + if (!left && !right) { + zones[type] = NULL; + goto unmap; + } + if (!left) + zones[type] = right; + else + left->next = right; + if (right) + right->prev = left; +unmap: + err = kfree_frame((void *)zone, zone->size); + if (err) + kprintf(KERN_ERR "error: munmap failed\n"); + return (err); +} + +/* + * If the newly kfreed block is next to another previously + * kfreed block, merge both of these and update the size + */ +static Block *merge_blocks(Block *left, Block *right) +{ + if (right->next) + right->next->prev = left; + if (right->next_free) { + right->next_free->prev_free = left; + left->next_free = right->next_free; + } + left->next = right->next; + left->size += right->size + sizeof(Block); + return (left); +} + +// Simply add the new block to the list of available blocks +static int add_available(Block *available, Block *merged) +{ + Zone *zone = available->zone; + if (merged != zone->free && available != zone->free) + available->next_free = zone->free; + if (zone->free) + zone->free->prev_free = available; + zone->free = available; + if (zone->type == LARGE) + return (unmap_zone(zone)); + return (0); +} + +/* + * ptr: pointer to kfree, if the pointer is invalid the kfree() + * function will have an undefined behavior (most likely segfault) + * + * First, we remove the block from the list of in_use blocks + * Then, we check if the block needs to be merged with another + * neighboring block, if so we replace the previous block by the + * newly merged block + * Finally, we add the block to the list of available blocks + */ +void kfree(void *ptr) +{ + if (ptr == NULL) + return; + Block *to_kfree = (Block *)((size_t)ptr - sizeof(Block)); + Block *to_merge = NULL; + to_kfree->in_use = false; + remove_used(to_kfree); + if (to_kfree->prev && !to_kfree->prev->in_use) { + to_merge = to_kfree; + to_kfree = merge_blocks(to_kfree->prev, to_kfree); + } + if (to_kfree->next && !to_kfree->next->in_use) { + to_merge = to_kfree->next; + to_kfree = merge_blocks(to_kfree, to_kfree->next); + } + int err = add_available(to_kfree, to_merge); + if (err) + kprintf(KERN_ERR "kfree: fatal error\n"); +} diff --git a/src/memory/alloc/krealloc.c b/src/memory/alloc/krealloc.c new file mode 100644 index 0000000..fbf963a --- /dev/null +++ b/src/memory/alloc/krealloc.c @@ -0,0 +1,36 @@ +#include "alloc.h" +#include "string.h" + +// Prototype for kfree and kalloc +void kfree(void *ptr); +void *kalloc(size_t size); + +/* + * ptr: block to resize (undefined behavior if invalid) + * size: size needed by the user to get kreallocated + * + * If we have a size <= to the previous size, we don't have + * to do anything, we just change sub_size for info purposes + * and return the same pointer + * Else, we allocate a new block and copy the content of + * the previous block in the new one and kfree the old block + * + * ptr: returns the aligned pointer of the kreallocated block + */ +void *krealloc(void *ptr, size_t size) +{ + void *new_ptr = NULL; + if (ptr == NULL) + return NULL; + Block *block = (Block *)((size_t)ptr - sizeof(Block)); + if (block->size >= size) { + block->sub_size = size; + return (ptr); + } + new_ptr = kalloc(size); + if (new_ptr == NULL) + return NULL; + memmove(new_ptr, ptr, block->size); + kfree(ptr); + return (new_ptr); +} diff --git a/src/memory/alloc/utils.c b/src/memory/alloc/utils.c new file mode 100644 index 0000000..b0e24ec --- /dev/null +++ b/src/memory/alloc/utils.c @@ -0,0 +1,34 @@ +#include "alloc.h" +#include "memory.h" + +static size_t get_block_size(block_type_t type) +{ + if (type == TINY) + return ((PAGES_TINY * PAGE_SIZE) / BPZ - sizeof(Block)); + if (type == SMALL) + return ((PAGES_SMALL * PAGE_SIZE) / BPZ - sizeof(Block)); + return (0); +} + +block_type_t get_type(size_t size) +{ + if (size <= get_block_size(TINY)) + return (TINY); + if (size <= get_block_size(SMALL)) + return (SMALL); + return (LARGE); +} + +size_t get_zone_size(block_type_t type) +{ + if (type == TINY) + return (PAGES_TINY * PAGE_SIZE); + if (type == SMALL) + return (PAGES_SMALL * PAGE_SIZE); + return (0); +} + +size_t align_mem(size_t addr) +{ + return (addr + (MEM_ALIGN - 1)) & ~(MEM_ALIGN - 1); +} diff --git a/src/memory/frame.c b/src/memory/frame.c index 026929a..e3dd31f 100644 --- a/src/memory/frame.c +++ b/src/memory/frame.c @@ -21,12 +21,14 @@ extern uint32_t end_kernel; static uint8_t frame_table[CEIL(MAX_FRAMES, 8)]; static uint32_t remaining_frames = MAX_FRAMES; -void *kalloc_frame(uint32_t nb_frames) +void *kalloc_frame(size_t size) { + const uint32_t nb_frames = CEIL(size, PAGE_SIZE); if (nb_frames > remaining_frames) { kprintf(KERN_CRIT "Not enough frames (max: %d)\n", MAX_FRAMES); return NULL; } + size_t i = 0; while (i < MAX_FRAMES) { size_t free_frames = 1; @@ -48,21 +50,23 @@ end: return NULL; } -void kfree_frame(void *frame, uint32_t nb_frames) +int kfree_frame(void *frame, size_t size) { + const uint32_t nb_frames = CEIL(size, PAGE_SIZE); const uint32_t start = (frame - (void *)&end_kernel) / PAGE_SIZE; if (start > MAX_FRAMES || frame < (void *)&end_kernel) { kprintf(KERN_WARNING "Address out of range\n"); - return; + return -1; } else if ((uint32_t)frame % PAGE_SIZE) { kprintf(KERN_WARNING "Invalid address\n"); - return; + return -1; } else if (start + nb_frames > MAX_FRAMES) { kprintf(KERN_WARNING "Invalid number of frames\n"); - return; + return -1; } for (size_t i = start; i < start + nb_frames; i++) SET_FRAME(i, 0); remaining_frames += nb_frames; + return 0; } diff --git a/src/shell/exec.c b/src/shell/exec.c index f1bfde0..f867dca 100644 --- a/src/shell/exec.c +++ b/src/shell/exec.c @@ -115,7 +115,8 @@ void shell_init(void) } } if (invalid && screen->line[0]) - kprintf("invalid command: %s\n", screen->line); + kprintf(KERN_WARNING "invalid command: %s\n", + screen->line); memset(screen->line, '\0', sizeof(screen->line)); } }