From 59c0792906236a9de74acbe1b6b5c5484da136c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20DUBOIN?= Date: Thu, 25 Dec 2025 17:04:51 +0100 Subject: [PATCH 1/8] memory: remove trash vm_direct file This file was pushed by mistake, remove it. --- kernel/memory/vm_direct.c | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 kernel/memory/vm_direct.c diff --git a/kernel/memory/vm_direct.c b/kernel/memory/vm_direct.c deleted file mode 100644 index e69de29b..00000000 From b1a5906f15d53c7fd986bf3bbea068e5d6ed991d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20DUBOIN?= Date: Wed, 12 Nov 2025 09:17:07 +0100 Subject: [PATCH 2/8] memory: check for NULL address space in page_fault handler The current process's is NULL until we initialize the kernel process in process_init_kernel_process(). Things get messy and memory is corrupted if a NULL reference exception happens while in this state. When the page fault exception is triggred by a valid lazy allocation, the address space used should be &kernel_address_space and not that of the currently running process. Make sure that an address space is defined, and panic if not the case. --- kernel/arch/i686/mmu.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/kernel/arch/i686/mmu.c b/kernel/arch/i686/mmu.c index 179fef92..332b8322 100644 --- a/kernel/arch/i686/mmu.c +++ b/kernel/arch/i686/mmu.c @@ -609,10 +609,16 @@ static INTERRUPT_HANDLER_FUNCTION(page_fault) if (!error.present || is_cow) { as = IS_KERNEL_ADDRESS(faulty_address) ? &kernel_address_space : current->process->as; + if (unlikely(!as)) { + log_err("page_fault: address space is NULL"); + goto page_fault_panic; + } + if (!address_space_fault(as, faulty_address, is_cow)) return E_SUCCESS; } +page_fault_panic: PANIC("PAGE FAULT at " FMT32 ": %s access on a %s page %s", faulty_address, error.write ? "write" : "read", error.present ? "protected" : "non-present", From 8365333b2a3ce9041fdfce090d9a721fe1edb931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20DUBOIN?= Date: Thu, 25 Dec 2025 16:43:22 +0100 Subject: [PATCH 3/8] memory/vm: add vm_map() function This function can be used to manually associate a virtual address segment with backing physical pages. This should be used when requiring these pages before performing lazy allocation. --- include/kernel/vm.h | 9 +++++++++ kernel/memory/address_space.c | 26 ++++++++++++++++++++++++++ kernel/memory/vm_normal.c | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+) diff --git a/include/kernel/vm.h b/include/kernel/vm.h index b15a62d1..0999329c 100644 --- a/include/kernel/vm.h +++ b/include/kernel/vm.h @@ -133,6 +133,9 @@ struct vm_segment_driver { * located. */ error_t (*vm_fault)(struct address_space *, struct vm_segment *); + + /** Map this segment onto a physical address. */ + error_t (*vm_map)(struct address_space *, struct vm_segment *, vm_flags_t); }; /** Kernel-only address-space. @@ -224,4 +227,10 @@ void vm_free(struct address_space *, void *); /** Find the address space's segment that contains the given address */ struct vm_segment *vm_find(const struct address_space *, void *); +/** Map a virtual address segment onto a physical page. + * + * @return E_EXIST if the virtual address contained a previous mapping. + */ +error_t vm_map(struct address_space *, void *); + #endif /* KERNEL_VM_H */ diff --git a/kernel/memory/address_space.c b/kernel/memory/address_space.c index e72ebd7f..f7654216 100644 --- a/kernel/memory/address_space.c +++ b/kernel/memory/address_space.c @@ -358,6 +358,32 @@ void vm_free(struct address_space *as, void *addr) } } +error_t vm_map(struct address_space *as, void *addr) +{ + struct vm_segment *segment; + error_t ret; + + if (addr == NULL) + return E_SUCCESS; + + if ((vaddr_t)addr % PAGE_SIZE) { + log_warn("freeing unaligned virtual address: %p (skipping)", addr); + return E_INVAL; + } + + locked_scope (&as->lock) { + segment = vm_find(as, addr); + if (!segment) { + log_dbg("free: no backing segment for %p", addr); + return E_NOENT; + } + + ret = segment->driver->vm_map(as, segment, segment->flags); + } + + return ret; +} + static int vm_segment_contains(const void *this, const void *addr) { const struct vm_segment *segment = to_segment(this); diff --git a/kernel/memory/vm_normal.c b/kernel/memory/vm_normal.c index 378e45d3..96059653 100644 --- a/kernel/memory/vm_normal.c +++ b/kernel/memory/vm_normal.c @@ -13,6 +13,37 @@ struct vm_segment *vm_normal_alloc(struct address_space *as, vaddr_t addr, return vmm_allocate(as->vmm, addr, size, flags); } +static error_t vm_normal_map(struct address_space *as, + struct vm_segment *segment, vm_flags_t flags) +{ + size_t size = segment->size; + paddr_t phys; + error_t err; + + AS_ASSERT_OWNED(as); + + for (size_t off = 0; off < size; off += PAGE_SIZE) { + + /* If the address already contained a mapping keep it. */ + if (mmu_is_mapped(segment->start + off)) + continue; + + phys = pmm_allocate(); + if (phys == PMM_INVALID_PAGEFRAME) { + err = E_NOMEM; + goto exit_error; + } + + mmu_map(segment->start + off, phys, flags); + } + + return E_SUCCESS; + +exit_error: + /* Don't free pages, they should be released by vm_free(). */ + return err; +} + struct vm_segment *vm_normal_alloc_at(struct address_space *as, paddr_t phys, size_t size, vm_flags_t flags) { @@ -143,4 +174,5 @@ const struct vm_segment_driver vm_normal = { .vm_free = vm_normal_free, .vm_fault = vm_normal_fault, .vm_resize = vm_normal_resize, + .vm_map = vm_normal_map, }; From cac1605cc7845fd726790ee1318a31172b5f87d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20DUBOIN?= Date: Sun, 9 Nov 2025 12:25:34 +0100 Subject: [PATCH 4/8] memory: add memory_init() function This function is responsible for initializing the whole memory subsystem instead of letting the kernel_main() function do everything explicitely. This makes it more obvious what's done in the main function. --- include/kernel/memory.h | 16 ++++++++++++++++ kernel/build.mk | 1 + kernel/main.c | 14 ++++---------- kernel/memory/memory.c | 22 ++++++++++++++++++++++ kernel/memory/pmm.c | 2 -- 5 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 kernel/memory/memory.c diff --git a/include/kernel/memory.h b/include/kernel/memory.h index ae985e24..eec008e7 100644 --- a/include/kernel/memory.h +++ b/include/kernel/memory.h @@ -170,4 +170,20 @@ extern u32 _kernel_code_end; #define PAGE_ALIGN_UP(_ptr) align_up_ptr(_ptr, PAGE_SIZE) #define PAGE_ALIGNED(_ptr) is_aligned_ptr(_ptr, PAGE_SIZE) +#ifndef __ASSEMBLER__ + +struct multiboot_info; + +/** + * Initialize the memory subsystem. + * + * After this function returns it is safe to call the different memory + * allocation APIs (vm_*, kmalloc). + * + * @param mbt System information structure passed by the bootloader. + */ +void memory_init(struct multiboot_info *); + +#endif /* !__ASSEMBLER__ */ + #endif /* KERNEL_MEMORY_H */ diff --git a/kernel/build.mk b/kernel/build.mk index 46ddfb74..e55ec92e 100644 --- a/kernel/build.mk +++ b/kernel/build.mk @@ -36,6 +36,7 @@ KERNEL_SRCS := \ misc/worker.c \ misc/semaphore.c \ misc/uacpi.c \ + memory/memory.c \ memory/pmm.c \ memory/vmm.c \ memory/address_space.c \ diff --git a/kernel/main.c b/kernel/main.c index 328fc026..c51fcbf4 100644 --- a/kernel/main.c +++ b/kernel/main.c @@ -13,10 +13,9 @@ #include #include #include -#include +#include #include #include -#include #include #include #include @@ -170,16 +169,11 @@ void kernel_main(struct multiboot_info *mbt, unsigned int magic) /* * Now that we have a minimal working setup, we can enable paging * and initialize the virtual memory allocation API. - * After this step, we are able to allocate & free kernel memory as usual. + * + * After this step, we are able to allocate & free kernel memory. */ - if (!pmm_init(mbt)) - PANIC("Could not initialize the physical memory manager"); + memory_init(mbt); - log_info("Initializing MMU"); - if (!mmu_init()) - PANIC("Failed to initialize virtual address space"); - - address_space_init(&kernel_address_space); process_init_kernel_process(); /* diff --git a/kernel/memory/memory.c b/kernel/memory/memory.c new file mode 100644 index 00000000..a3e21699 --- /dev/null +++ b/kernel/memory/memory.c @@ -0,0 +1,22 @@ +#define LOG_DOMAIN "memory" + +#include +#include +#include +#include +#include + +#include + +void memory_init(struct multiboot_info *mbt) +{ + log_info("Initializing pageframe allocator"); + if (!pmm_init(mbt)) + PANIC("Failed to initialize the physical memory manager"); + + log_info("Initializing MMU"); + if (!mmu_init()) + PANIC("Failed to initialize virtual address space"); + + address_space_init(&kernel_address_space); +} diff --git a/kernel/memory/pmm.c b/kernel/memory/pmm.c index 14d13c18..60bf9d14 100644 --- a/kernel/memory/pmm.c +++ b/kernel/memory/pmm.c @@ -225,8 +225,6 @@ void page_put(struct page *page) bool pmm_init(struct multiboot_info *mbt) { - log_info("Initializing pageframe allocator"); - if (!pmm_initialize_pages(mbt)) { return false; } From afff51076f0c4db1b4f39fa25feb672c6a49b2dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20DUBOIN?= Date: Sun, 9 Nov 2025 12:06:26 +0100 Subject: [PATCH 5/8] memory: add slab allocator implementation --- docs/specs/bonwick_slab.pdf | Bin 0 -> 55502 bytes include/kernel/memory/slab.h | 72 +++++ include/kernel/pmm.h | 10 + include/kernel/spinlock.h | 6 + include/utils/math.h | 3 + kernel/build.mk | 1 + kernel/memory/memory.c | 3 + kernel/memory/slab.c | 562 +++++++++++++++++++++++++++++++++++ 8 files changed, 657 insertions(+) create mode 100644 docs/specs/bonwick_slab.pdf create mode 100644 include/kernel/memory/slab.h create mode 100644 kernel/memory/slab.c diff --git a/docs/specs/bonwick_slab.pdf b/docs/specs/bonwick_slab.pdf new file mode 100644 index 0000000000000000000000000000000000000000..9af19213d5f99cf92730f2c37e199bb3842738f0 GIT binary patch literal 55502 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 O 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≶3zB7v#H7W*i>secw{CzSmb%KT&%kb5LG& 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 bAMcLN?vAEr@G#8G@8Ds`$wlSG;9>qhXuwd1 literal 0 HcmV?d00001 diff --git a/include/kernel/memory/slab.h b/include/kernel/memory/slab.h new file mode 100644 index 00000000..8fa4ac9e --- /dev/null +++ b/include/kernel/memory/slab.h @@ -0,0 +1,72 @@ +#ifndef KERNEL_MEMORY_SLAB_H +#define KERNEL_MEMORY_SLAB_H + +#include +#include +#include + +#include +#include + +/* + * + */ +struct kmem_cache { + llist_t slabs_full; + llist_t slabs_partial; + llist_t slabs_free; + spinlock_t lock; + + size_t obj_size; + int obj_align; + size_t obj_real_size; + unsigned int coloring_offset_next; + + void (*constructor)(void *data); + void (*destructor)(void *data); + + const char *name; + int flags; +}; + +/* + * + */ +struct kmem_slab { + void *page; + struct kmem_bufctl *free; + struct kmem_cache *cache; + atomic_t refcount; + unsigned int coloring_offset; + node_t this; +}; + +/** Create a new cache. */ +struct kmem_cache *kmem_cache_create(const char *name, size_t obj_size, + int obj_align, void (*constructor)(void *), + void (*destructor)(void *)); + +/** Allocate an object from a cache + * + * @param cache The cache + * @param flags Combination of allocation flags + */ +void *kmem_cache_alloc(struct kmem_cache *cache, int flags); + +/** Free a cache and all its slabs. + * + * NOTE: The caller should be sure that no objects allocated from this cache + * are still being used when calling this function. + */ +void kmem_cache_destroy(struct kmem_cache *cache); + +/** Free an object allocated by a cache. + * + * @param cache The cache the object was allocated from + * @param obj The object to free + */ +void kmem_cache_free(struct kmem_cache *cache, void *obj); + +int kmem_cache_api_init(void); + +#endif /* KERNEL_MEMORY_SLAB_H */ diff --git a/include/kernel/pmm.h b/include/kernel/pmm.h index d475341c..f5c14e0e 100644 --- a/include/kernel/pmm.h +++ b/include/kernel/pmm.h @@ -63,6 +63,7 @@ enum page_flags { PAGE_AVAILABLE = BIT(0), ///< This page has not been allocated PAGE_COW = BIT(1), ///< Currently used in a CoW mapping + PAGE_SLAB = BIT(2), ///< Page allocated by the slab allocator }; /** Represents a physical pageframe @@ -71,6 +72,15 @@ enum page_flags { struct page { uint8_t flags; ///< Combination of @ref page_flags uint8_t refcount; ///< How many processes reference that page + + union { + /* + * Data for pages allocated by the slab allocator (flags & PAGE_SLAB). + */ + struct { + struct kmem_cache *cache; + } slab; + }; }; /** diff --git a/include/kernel/spinlock.h b/include/kernel/spinlock.h index c503269e..c40a6cec 100644 --- a/include/kernel/spinlock.h +++ b/include/kernel/spinlock.h @@ -92,6 +92,12 @@ static ALWAYS_INLINE void spinlock_release(spinlock_t *lock) __atomic_clear(&lock->locked, __ATOMIC_RELEASE); } +/** Check whether a lock is currently held by someone. */ +static ALWAYS_INLINE bool spinlock_is_held(const spinlock_t *lock) +{ + return __atomic_load_n(&lock->locked, __ATOMIC_ACQUIRE); +} + typedef struct { spinlock_t *lock; bool done; diff --git a/include/utils/math.h b/include/utils/math.h index 96531d1a..27eb665e 100644 --- a/include/utils/math.h +++ b/include/utils/math.h @@ -38,6 +38,9 @@ _tmp < 0 ? -_tmp : _tmp; \ }) +/** @return whether a value is a power of two */ +#define is_power_of_2(_x) (_x != 0 && ((_x & (_x - 1)) == 0)) + #define __align_mask(_value, _power) ((__typeof__(_value))((_power)-1)) /** @brief Align @c _value to the next multiple of @c _power diff --git a/kernel/build.mk b/kernel/build.mk index e55ec92e..61e086dd 100644 --- a/kernel/build.mk +++ b/kernel/build.mk @@ -41,6 +41,7 @@ KERNEL_SRCS := \ memory/vmm.c \ memory/address_space.c \ memory/vm_normal.c \ + memory/slab.c \ net/net.c \ net/packet.c \ net/socket.c \ diff --git a/kernel/memory/memory.c b/kernel/memory/memory.c index a3e21699..46ef0d29 100644 --- a/kernel/memory/memory.c +++ b/kernel/memory/memory.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -19,4 +20,6 @@ void memory_init(struct multiboot_info *mbt) PANIC("Failed to initialize virtual address space"); address_space_init(&kernel_address_space); + + kmem_cache_api_init(); } diff --git a/kernel/memory/slab.c b/kernel/memory/slab.c new file mode 100644 index 00000000..e55f0c14 --- /dev/null +++ b/kernel/memory/slab.c @@ -0,0 +1,562 @@ +/* + * SunOs' slab allocator implementation. + * + * TODO: kmem_cache_reap() for reclaiming memory from caches when running low. + * + * References: + * - Bonwick94 + */ + +#define LOG_DOMAIN "slab" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum kmem_cache_flag { + CACHE_F_EXTERNAL, /* slab & bufctl structs stored in an external buffer. */ +}; + +/* + * + */ +struct kmem_bufctl { + union { + struct kmem_bufctl *next; /* Used when bufctl is inside the freelist. */ + struct kmem_slab *slab; /* Used when the object has been allocated. */ + }; + + /* + * NOTE: The object and the hash table entry are placed inside a union + * so that the object and the entry's key can be used interchangeably. + */ + union { + void *obj; + struct hashtable_entry hash; + }; +}; + +static struct kmem_cache kmem_cache_cache; +static struct kmem_cache kmem_slab_cache; +static struct kmem_cache kmem_bufctl_cache; + +#define KMEM_SLAB_MIN_SIZE sizeof(struct kmem_bufctl) +#define KMEM_SLAB_MIN_ALIGN 1 + +/* + * Slabs whose objects are larger than this one are considered 'large slabs'. + * + * Large slabs do not store the kmem_bufctl structures inside the slab directly, + * but keep them stored inside a dedicated buffer. + */ +#define KMEM_SLAB_LARGE_SIZE (PAGE_SIZE / 8) + +/* + * Address to kmem_bufctl hashmap. + */ +static DECLARE_HASHTABLE(kmem_bufctl_hashmap, 256); +static DECLARE_SPINLOCK(kmem_bufctl_hashmap_lock); + +/* + * NOTE: Using a spinlock to protect against simultaneous accesses to the + * slab lists makes it unsafe to use the kmem_cache API in uninterruptible + * contexts. We should switch to disabling interrupts instead if we need + * to allocate memory while inside an interrupt handler. + */ +static inline void kmem_cache_lock(struct kmem_cache *cache) +{ + spinlock_acquire(&cache->lock); +} + +/* + * + */ +static inline void kmem_cache_unlock(struct kmem_cache *cache) +{ + spinlock_release(&cache->lock); +} + +/* + * Find the start address of a slab object. + */ +static inline void *kmem_slab_obj_start(const struct kmem_slab *slab, void *obj) +{ + off_t offset = obj - slab->page; + + return obj - (offset % slab->cache->obj_real_size); +} + +/* + * Find the start address of the next object in a slab. + * + * This function assumes that @c obj points to the start of the object. + */ +static inline void *kmem_slab_obj_next(const struct kmem_slab *slab, void *obj) +{ + return obj + slab->cache->obj_real_size; +} + +/* + * Check whether @c has been allocated by @c slab. + */ +static inline bool +kmem_slab_contains_obj(const struct kmem_slab *slab, void *obj) +{ + return PAGE_ALIGN_DOWN(obj) == slab->page; +} + +/* + * Free a single slab and all the bufctl structure it contains. + */ +static void kmem_slab_destroy(struct kmem_slab *slab) +{ + struct kmem_cache *cache = slab->cache; + struct kmem_bufctl *bufctl; + struct kmem_bufctl *next; + + if (atomic_read(&slab->refcount)) { + log_warn("%s: slab@%p has %d active entries when destroying", + cache->name, slab->page, atomic_read(&slab->refcount)); + } + + llist_remove(&slab->this); + vm_free(&kernel_address_space, slab->page); + + next = slab->free; + while ((bufctl = next) != NULL) { + next = bufctl->next; + locked_scope (&kmem_bufctl_hashmap_lock) { + hashtable_remove(&kmem_bufctl_hashmap, &bufctl->hash.key); + } + if (cache->destructor) + cache->destructor(bufctl->obj); + if (BIT_READ(cache->flags, CACHE_F_EXTERNAL)) + kmem_cache_free(&kmem_bufctl_cache, bufctl); + } + + kmem_cache_free(&kmem_slab_cache, slab); +} + +/* + * + */ +static void +kmem_slab_free_obj(struct kmem_slab *slab, struct kmem_bufctl *bufctl) +{ + bufctl->next = slab->free; + SWAP(slab->free, bufctl); + + if (unlikely(atomic_dec(&slab->refcount) <= 1)) { + /* All objects in the slab are now free. */ + llist_remove(&slab->this); + llist_add(&slab->cache->slabs_free, &slab->this); + } else if (unlikely(bufctl == NULL)) { + /* Slab was full before freeing the object. */ + llist_remove(&slab->this); + llist_add(&slab->cache->slabs_partial, &slab->this); + } +} + +/* + * Generate the slab's initial freelist and initialize each object. + * + * This function only works for normal sized objects. For larger objects, + * use kmem_slab_init_large_objects(). + */ +static void kmem_slab_init_objects(struct kmem_slab *slab) +{ + struct kmem_cache *cache = slab->cache; + struct kmem_bufctl *bufctl; + struct kmem_bufctl **next_bufctl; + void *end = slab; + void *obj; + + obj = slab->page + slab->coloring_offset; + next_bufctl = &slab->free; + + while (obj + cache->obj_real_size <= end) { + bufctl = obj + cache->obj_size; + *next_bufctl = bufctl; + next_bufctl = &bufctl->next; + bufctl->obj = obj; + + locked_scope (&kmem_bufctl_hashmap_lock) { + bufctl->hash.key = bufctl->obj; + hashtable_insert(&kmem_bufctl_hashmap, &bufctl->hash); + } + + if (cache->constructor) + cache->constructor(obj); + obj += cache->obj_real_size; + } + + *next_bufctl = NULL; +} + +/* + * Generate the slab's initial freelist and initiliaze each object. + * + * This is the large object version of @ref kmem_slab_init_objects() for slabs + * in which bufctl structures are stored in a dedicated external page. + */ +static error_t kmem_slab_init_large_objects(struct kmem_slab *slab) +{ + struct kmem_cache *cache = slab->cache; + size_t slab_size = align_up(cache->obj_size, PAGE_SIZE); + struct kmem_bufctl *bufctl; + struct kmem_bufctl **next_bufctl; + void *end = slab->page + slab_size; + void *obj; + + obj = slab->page + slab->coloring_offset; + next_bufctl = &slab->free; + + while (obj + cache->obj_real_size <= end) { + bufctl = kmem_cache_alloc(&kmem_bufctl_cache, 0); + if (!bufctl) { + *next_bufctl = NULL; + goto err; + } + + *next_bufctl = bufctl; + next_bufctl = &bufctl->next; + bufctl->obj = obj; + + locked_scope (&kmem_bufctl_hashmap_lock) { + bufctl->hash.key = bufctl->obj; + hashtable_insert(&kmem_bufctl_hashmap, &bufctl->hash); + } + + if (cache->constructor) + cache->constructor(obj); + obj += cache->obj_real_size; + } + + *next_bufctl = NULL; + + return 0; + +err: + bufctl = slab->free; + while (bufctl) { + struct kmem_bufctl *to_free = bufctl; + bufctl = bufctl->next; + kmem_cache_free(&kmem_bufctl_cache, to_free); + } + + return -1; +} + +/* + * Allocate, construct and add a new slab to the cache. + * + * The frontend slab API should call this function when all slabs are full when + * allocating. + * + * This function must be called with the cache's lock held. + * + * @return The added slab, or an pointer encoded error. + */ +static struct kmem_slab *kmem_cache_grow(struct kmem_cache *cache, int flags) +{ + struct kmem_slab *slab; + void *page; + paddr_t paddr; + size_t slab_size; + unsigned int max_color_offset; + + UNUSED(flags); + + slab_size = align_up(cache->obj_size, PAGE_SIZE); + + page = vm_alloc(&kernel_address_space, slab_size, VM_KERNEL_RW); + if (!page) + return PTR_ERR(E_NOMEM); + + if (BIT_READ(cache->flags, CACHE_F_EXTERNAL)) { + max_color_offset = slab_size % cache->obj_real_size; + slab = kmem_cache_alloc(&kmem_slab_cache, 0); + if (!slab) { + slab = PTR_ERR(E_NOMEM); + goto err; + } + } else { + /* The slab struct is placed the end of the slab */ + slab = page + slab_size - sizeof(*slab); + max_color_offset = (slab_size - sizeof(*slab)) % cache->obj_real_size; + } + + slab->page = page; + slab->cache = cache; + + /* + * Set this slab's cache coloring offset and update the kmem_cache + * offset for the next allocated slab. + */ + slab->coloring_offset = cache->coloring_offset_next; + cache->coloring_offset_next += cache->obj_align; + if (cache->coloring_offset_next > max_color_offset) + cache->coloring_offset_next = 0; + + if (BIT_READ(cache->flags, CACHE_F_EXTERNAL)) { + /* The page is not guaranteed to be accessed in this case, but we need + * a physical page to initialize the page structure later. */ + vm_map(&kernel_address_space, page); + if (kmem_slab_init_large_objects(slab)) + goto err; + } else { + kmem_slab_init_objects(slab); + } + + paddr = mmu_find_physical((vaddr_t)page); + for (size_t off = 0; off < slab_size; off += PAGE_SIZE) { + address_to_page(paddr + off)->flags |= PAGE_SLAB; + address_to_page(paddr + off)->slab.cache = cache; + } + + atomic_write(&slab->refcount, 0); + llist_add_tail(&cache->slabs_free, &slab->this); + + return slab; + +err: + vm_free(&kernel_address_space, page); + return slab; +} + +/* + * + */ +void *kmem_cache_alloc(struct kmem_cache *cache, int flags) +{ + struct kmem_slab *slab; + struct kmem_bufctl *bufctl; + llist_t *slabs; + void *obj = NULL; + + kmem_cache_lock(cache); + + /* + * Find a slab with at least one free object, and if none are present + * allocate one. + */ + if (!llist_is_empty(&cache->slabs_partial)) { + slabs = &cache->slabs_partial; + slab = container_of(llist_first(slabs), struct kmem_slab, this); + } else { + slabs = &cache->slabs_free; + if (!llist_is_empty(&cache->slabs_free)) { + slab = container_of(llist_first(slabs), struct kmem_slab, this); + } else { + slab = kmem_cache_grow(cache, flags); + if (IS_ERR(slab)) + goto out; + } + } + + /* Pop the free object from the slab's freelist. */ + bufctl = slab->free; + atomic_inc(&slab->refcount); + slab->free = bufctl->next; + + /* Make the now active bufctl point to its slab. */ + bufctl->slab = slab; + obj = bufctl->obj; + + if (slab->free == NULL) { + llist_remove(&slab->this); + llist_add(&cache->slabs_full, &slab->this); + } else if (slabs == &cache->slabs_free) { + llist_remove(&slab->this); + llist_add(&cache->slabs_partial, &slab->this); + } + +out: + kmem_cache_unlock(cache); + return obj; +} + +/* + * + */ +void kmem_cache_free(struct kmem_cache *cache, void *obj) +{ + struct kmem_slab *slab; + struct kmem_bufctl *bufctl; + struct hashtable_entry *hash_entry; + struct page *page; + paddr_t paddr; + + UNUSED(cache); + + /* + * Slab pages always have a backing physical page since we access them + * to create the freelist in kmem_cache_grow(). If this is not verified + * it either means this page is not a slab page or that something went + * wrong somewhere. + */ + paddr = mmu_find_physical((vaddr_t)obj); + if (WARN_ON_MSG(IS_ERR(paddr), "free: no backing page for object at %p", + obj)) { + return; + } + + page = address_to_page(paddr); + if (WARN_ON_MSG(!(page->flags & PAGE_SLAB), + "free: object at %p is not a slab object (flags: %04x)", + obj, page->flags)) { + return; + } + + locked_scope (&kmem_bufctl_hashmap_lock) { + obj = align_down_ptr(obj, cache->obj_align); + hash_entry = hashtable_find(&kmem_bufctl_hashmap, obj); + if (WARN_ON_MSG(!hash_entry, "free: no bufctl found for %p", obj)) + return; + } + + bufctl = container_of(hash_entry, struct kmem_bufctl, hash); + slab = bufctl->slab; + if (WARN_ON(slab->cache != cache)) + return; + + kmem_cache_lock(cache); + kmem_slab_free_obj(slab, bufctl); + kmem_cache_unlock(cache); +} + +/* + * Initialize a kmem_cache structure. + */ +static void kmem_cache_init(struct kmem_cache *cache, const char *name, + size_t obj_size, int obj_align, + void (*constructor)(void *), + void (*destructor)(void *)) +{ + cache->name = name; + cache->obj_size = obj_size; + cache->obj_align = obj_align; + cache->constructor = constructor; + cache->destructor = destructor; + + cache->coloring_offset_next = 0; + cache->flags = 0; + + /* + * Slabs and bufctl struct for large objects are stored inside a dedicated + * external buffer. Regular slabs append the kmem_bufctl struct after + * the object directly inside the slab. + */ + if (obj_size >= KMEM_SLAB_LARGE_SIZE) { + BIT_SET(cache->flags, CACHE_F_EXTERNAL); + cache->obj_real_size = align_up(obj_size, obj_align); + } else { + cache->obj_real_size = align_up(obj_size + sizeof(struct kmem_bufctl), + obj_align); + } +} + +/* + * + */ +struct kmem_cache *kmem_cache_create(const char *name, size_t obj_size, + int obj_align, void (*constructor)(void *), + void (*destructor)(void *)) +{ + struct kmem_cache *cache; + + if (obj_size < KMEM_SLAB_MIN_SIZE) + return PTR_ERR(E_INVAL); + + if (obj_align < KMEM_SLAB_MIN_ALIGN || !is_power_of_2(obj_align)) + return PTR_ERR(E_INVAL); + + cache = kmem_cache_alloc(&kmem_cache_cache, 0); + if (!cache) { + log_err("failed to allocate %s cache", name); + return PTR_ERR(E_NOMEM); + } + + kmem_cache_init(cache, name, obj_size, obj_align, constructor, destructor); + + return cache; +} + +/* + * + */ +void kmem_cache_destroy(struct kmem_cache *cache) +{ + struct kmem_slab *slab; + struct kmem_slab *next; + + FOREACH_LLIST_ENTRY_SAFE (slab, next, &cache->slabs_partial, this) { + kmem_slab_destroy(slab); + } + + FOREACH_LLIST_ENTRY_SAFE (slab, next, &cache->slabs_full, this) { + kmem_slab_destroy(slab); + } + + FOREACH_LLIST_ENTRY_SAFE (slab, next, &cache->slabs_free, this) { + kmem_slab_destroy(slab); + } +} + +/* + * Constructor for kmem_cache structures. + */ +static void kmem_cache_constructor(void *data) +{ + struct kmem_cache *cache = data; + + INIT_LLIST(cache->slabs_full); + INIT_LLIST(cache->slabs_partial); + INIT_LLIST(cache->slabs_free); + INIT_SPINLOCK(cache->lock); +} + +/* + * Destructor for kmem_cache structures. + */ +static void kmem_cache_destructor(void *data) +{ + UNUSED(data); +} + +/* + * + */ +int kmem_cache_api_init(void) +{ + /* + * Larger slabs require a separate hashtable, wihch we would need to + * dynamically allocate entries for. This is not feasible for the bootstrap + * cache of kmem_cache structures. + */ + static_assert(sizeof(struct kmem_cache) < KMEM_SLAB_LARGE_SIZE); + + hashtable_init(&kmem_bufctl_hashmap); + INIT_SPINLOCK(kmem_bufctl_hashmap_lock); + + kmem_cache_constructor(&kmem_cache_cache); + kmem_cache_init(&kmem_cache_cache, "kmem_cache", sizeof(struct kmem_cache), + 1, kmem_cache_constructor, kmem_cache_destructor); + + kmem_cache_constructor(&kmem_slab_cache); + kmem_cache_init(&kmem_slab_cache, "kmem_slab", sizeof(struct kmem_slab), 1, + NULL, NULL); + + kmem_cache_constructor(&kmem_bufctl_cache); + kmem_cache_init(&kmem_bufctl_cache, "kmem_bufctl", + sizeof(struct kmem_bufctl), 1, NULL, NULL); + + return 0; +} From 117476e2bbd83a6df33dff9f239d691f391502b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20DUBOIN?= Date: Wed, 12 Nov 2025 06:53:21 +0100 Subject: [PATCH 6/8] memory/kmalloc: use sized caches for global allocations --- include/kernel/kmalloc.h | 57 ++++++++-- kernel/memory/kmalloc.c | 232 +++++++++------------------------------ kernel/memory/memory.c | 2 + 3 files changed, 101 insertions(+), 190 deletions(-) diff --git a/include/kernel/kmalloc.h b/include/kernel/kmalloc.h index f373c701..6dbd5023 100644 --- a/include/kernel/kmalloc.h +++ b/include/kernel/kmalloc.h @@ -51,6 +51,8 @@ #include +#include + /** * @enum kmalloc_flags * @brief Feature flags passed to the kmalloc function family @@ -59,17 +61,56 @@ typedef enum kmalloc_flags { KMALLOC_KERNEL = 0, /* Default allocation flags. */ } kmalloc_flags; -/** - * Allocate @c size bytes and return a pointer to the allocated memory. +#define KMALLOC_CACHE_MIN_SIZE 16 +#define KMALLOC_CACHE_MAX_SIZE 16384 +#define KMALLOC_CACHE_COUNT 11 + +/* + * @return The index of the smallest cache that can contain a @size bytes object * - * An area allocated using this function MUST be freed manually. + * This function is inlined and marked 'const' so that it is optimized out by + * the compiler when passing a value known at compile time. + */ +static ALWAYS_INLINE __attribute__((const)) int kmalloc_cache_index(size_t size) +{ + if (size <= 16) return 0; + if (size <= 32) return 1; + if (size <= 64) return 2; + if (size <= 128) return 3; + if (size <= 256) return 4; + if (size <= 512) return 5; + if (size <= 1024) return 6; + if (size <= 2048) return 7; + if (size <= 4096) return 8; + if (size <= 8192) return 9; + if (size <= 16384) return 10; + + return -1; +} + +/** Allocate kernel memory from one of the global memory caches. * - * @param size The number of bytes to allocate - * @param flags Feature flags, must be a combination of @ref kmalloc_flags + * @see kmalloc_cache_index() to know which cache_index to use. * - * @return The starting address of the newly allocated area + * @param cache_index Index of the cache to allocate an object from. + * @param flags Allocation flags to use. */ -void *kmalloc(size_t size, int flags); +void *kmalloc_from_cache(int cache_index, int flags); + +/* + * + */ +static ALWAYS_INLINE void *kmalloc(size_t size, int flags) +{ + int cache_index; + + cache_index = kmalloc_cache_index(size); + if (cache_index < 0) + return NULL; + + return kmalloc_from_cache(cache_index, flags); +} + /** * Allocate @c nmemb members of @c size bytes and initialize its content to 0. @@ -127,4 +168,6 @@ void *kmalloc_dma(size_t size); /** Free a buffer allocated through @ref kmalloc_dma */ void kfree_dma(void *dma_ptr); +void kmalloc_api_init(void); + /** @} */ diff --git a/kernel/memory/kmalloc.c b/kernel/memory/kmalloc.c index 39ed02c2..869ef8a1 100644 --- a/kernel/memory/kmalloc.c +++ b/kernel/memory/kmalloc.c @@ -1,180 +1,26 @@ -#include +#define LOG_DOMAIN "kmalloc" + #include #include #include +#include #include #include -#include -#include -#include -#include - -#include -#include -#include -#include -#include +#include #include -/** - * @defgroup kmalloc_internals Kmalloc - Internals - * @ingroup kmalloc - * - * Internal functions and structures used for managing buckets. - * - * @{ - */ - -static DECLARE_LLIST(kernel_buckets); - -/** All returned addresses are aligned on a 32B boundary */ -#define KMALLOC_ALIGNMENT (32) - -/** - * @brief Magic value to detect if a block is free - * - * To prevent corrupting the metadata by freeing the same block multiple times - * we write this number right after the linked list node when freeing a block. - * We then check for this arbitrary value before freeing it. If it's present - * this means we're freeing an already free block. - */ -#define KMALLOC_FREE_MAGIC (0x3402CECE) -#define BLOCK_FREE_MAGIC(_block) \ - ((uint32_t *)(((void *)_block) + sizeof(node_t))) - -/** - * @brief The metadata for a single bucket - * @struct bucket_meta - * - * A bucket's metadata is stored at the beginning of its mapped area, - * inside its first block. - * - */ -typedef struct bucket_meta { - u32 block_size; ///< The size of each blocks inside this bucket - u16 block_count; ///< Number of blocks currently malloc'd - u16 flags; ///< Flags for this bucket - llist_t free; ///< Head of the freelist - node_t this; - char data[] __attribute__((aligned(KMALLOC_ALIGNMENT))); -} bucket_t; - -static_assert(sizeof(bucket_t) <= KMALLOC_ALIGNMENT, "Bucket metadata MUST fit " - "into a single block"); - -static inline struct bucket_meta *to_bucket(node_t *this) -{ - return container_of(this, bucket_t, this); -} - -static inline size_t bucket_compute_size(size_t block_size) -{ - return align_up(KMALLOC_ALIGNMENT + block_size, PAGE_SIZE); -} +static struct kmem_cache *kmalloc_size_caches[KMALLOC_CACHE_COUNT]; -/** Find a bucket containing with at least one free block of the given size */ -static bucket_t *bucket_find(llist_t *buckets, size_t size, const u16 flags) -{ - FOREACH_LLIST (node, buckets) { - bucket_t *bucket = to_bucket(node); - if (bucket->block_size == size && !llist_is_empty(&bucket->free) && - bucket->flags == flags) - return bucket; - } - - return NULL; -} +static const char *kmalloc_cache_names[] = { + "size-16", "size-32", "size-64", "size-128", + "size-256", "size-512", "size-1024", "size-2048", + "size-4096", "size-8192", "size-16384", +}; -/** Reserve a free block inside a bucket */ -static void *bucket_get_free_block(bucket_t *bucket) +void *kmalloc_from_cache(int cache_index, int flags) { - void *block = llist_pop(&bucket->free); - bucket->block_count += 1; - // remove KMALLOC_FREE_MAGIC - *BLOCK_FREE_MAGIC(block) = 0x0; - return block; -} - -/** Create a new empty bucket for blocks of size @c block_size */ -static struct bucket_meta *bucket_create(struct address_space *as, - llist_t *buckets, size_t block_size, - const u16 flags) -{ - size_t bucket_size = bucket_compute_size(block_size); - bucket_t *bucket = vm_alloc(as, bucket_size, VM_KERNEL_RW | flags); - - if (IS_ERR(bucket)) - return NULL; - - bucket->block_size = block_size; - bucket->block_count = 0; - bucket->flags = flags; - - INIT_LLIST(bucket->free); - - // Generate the intrusive freelist - node_t *node = (node_t *)bucket->data; - size_t nb_blocks = (bucket_size - sizeof(bucket_t)) / block_size; - for (size_t i = 0; i < nb_blocks; ++i) { - *BLOCK_FREE_MAGIC(node) = KMALLOC_FREE_MAGIC; - llist_add(&bucket->free, node); - node = (void *)node + block_size; - } - - llist_add(buckets, &bucket->this); - - return bucket; -} - -/** Free a block inside a bucket */ -static void -bucket_free_block(struct address_space *as, bucket_t *bucket, void *block) -{ - // Check if block is already free or not - uint32_t *const block_free_magic = BLOCK_FREE_MAGIC(block); - if (*block_free_magic == KMALLOC_FREE_MAGIC) - return; // block is already free - - // If all blocks are free, unmap the bucket to avoid hording memory - if (bucket->block_count == 1) { - llist_remove(&bucket->this); - vm_free(as, bucket); - return; - } - - *block_free_magic = KMALLOC_FREE_MAGIC; - llist_add(&bucket->free, block); - bucket->block_count -= 1; -} - -static ALWAYS_INLINE bucket_t *bucket_from_block(void *block) -{ - return (bucket_t *)align_down((u32)block, PAGE_SIZE); -} - -/** @} */ - -void *kmalloc(size_t size, int flags) -{ - struct address_space *as; - - if (size == 0) - return NULL; - - as = &kernel_address_space; - - size = align_up(size, KMALLOC_ALIGNMENT); - size = bit_next_pow32(size); - - bucket_t *bucket = bucket_find(&kernel_buckets, size, flags); - if (bucket == NULL) - bucket = bucket_create(as, &kernel_buckets, size, flags); - - if (bucket == NULL) - return NULL; - - return bucket_get_free_block(bucket); + return kmem_cache_alloc(kmalloc_size_caches[cache_index], flags); } void *kcalloc(size_t nmemb, size_t size, int flags) @@ -191,40 +37,45 @@ void *kcalloc(size_t nmemb, size_t size, int flags) void kfree(void *ptr) { - struct address_space *as; + struct page *page; + paddr_t paddr; if (ptr == NULL) return; - as = &kernel_address_space; - bucket_free_block(as, bucket_from_block(ptr), ptr); + paddr = mmu_find_physical((vaddr_t)ptr); + page = address_to_page(paddr); + + kmem_cache_free(page->slab.cache, ptr); } void *krealloc(void *ptr, size_t size, int flags) { - if (size == 0) { - kfree(ptr); - return NULL; - } + struct page *page; + paddr_t paddr; if (ptr == NULL) return kmalloc(size, flags); - size = align_up(size, KMALLOC_ALIGNMENT); - size = bit_next_pow32(size); + paddr = mmu_find_physical((vaddr_t)ptr); + page = address_to_page(paddr); - // Reuse same block if it is large enough - bucket_t *bucket = bucket_from_block(ptr); - if (bucket->block_size >= size) + if (!(page->flags & PAGE_SLAB)) { + WARN("reallocating an invalid pointer: %p", ptr); return ptr; + } - void *new = kmalloc(size, flags); - if (new != NULL) - memcpy(new, ptr, size); + if (size == 0) { + kfree(ptr); + return NULL; + } - kfree(ptr); + /* No need to reallocate, current slab object is large enough already. */ + if (page->slab.cache->obj_size >= size) + return ptr; - return new; + kfree(ptr); + return kmalloc(size, flags); } void *krealloc_array(void *ptr, size_t nmemb, size_t size, int flags) @@ -262,3 +113,18 @@ void kfree_dma(void *dma_ptr) vm_free(&kernel_address_space, dma_ptr); } + +void kmalloc_api_init(void) +{ + struct kmem_cache *cache; + size_t obj_size = KMALLOC_CACHE_MIN_SIZE; + + for (int i = 0; i < KMALLOC_CACHE_COUNT; ++i, obj_size <<= 1) { + cache = kmem_cache_create(kmalloc_cache_names[i], obj_size, 16, NULL, + NULL); + if (!cache) + PANIC("failed to init kmalloc cache: '%s'", kmalloc_cache_names[i]); + + kmalloc_size_caches[i] = cache; + } +} diff --git a/kernel/memory/memory.c b/kernel/memory/memory.c index 46ef0d29..793fd94b 100644 --- a/kernel/memory/memory.c +++ b/kernel/memory/memory.c @@ -1,6 +1,7 @@ #define LOG_DOMAIN "memory" #include +#include #include #include #include @@ -22,4 +23,5 @@ void memory_init(struct multiboot_info *mbt) address_space_init(&kernel_address_space); kmem_cache_api_init(); + kmalloc_api_init(); } From f42d33297dc9d84f651537d3eed2911b814fb339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20DUBOIN?= Date: Thu, 25 Dec 2025 18:29:13 +0100 Subject: [PATCH 7/8] fs/tar: fix invalid address in tar_node_free() We were passing the address of the tree node to the kfree() function instead of the tar node. This worked fine until now since the former is embedded inside the latter, but does not work anymore since the switch to a slab allocator. Freeing the correct address instead fixes the following error: kmem_cache_free:419: failed to find bufctl for c09f6700 (align: 16, obj: c09f6700) Call stack: #0 0xc010e337 in #1 0xc011173e in #2 0xc010342d in #3 0xc010348e in #4 0xc01037c5 in #5 0xc0103dbb in #6 0xc0101fc7 in #7 0xc010204c in #8 0xc010120d in #9 0xc0101477 in #10 0xc011571b in === --- kernel/fs/tar.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kernel/fs/tar.c b/kernel/fs/tar.c index 85980004..bb465a28 100644 --- a/kernel/fs/tar.c +++ b/kernel/fs/tar.c @@ -193,7 +193,7 @@ static void tar_node_free(tree_node_t *node) /* Do not free vnode, let it be free'd by vnode_release(). */ if (tar_node->vnode) tar_node->vnode->pdata = NULL; - kfree(node); + kfree(tar_node); } /* From 74a83311404efbcfa9a67b4bd552dc88b6a4888a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A9o=20DUBOIN?= Date: Thu, 25 Dec 2025 18:51:51 +0100 Subject: [PATCH 8/8] sys/process: do not free the startup thread's structure The kernel_process_initial_thread (TID 0) is statically allocated and not kmalloc'd like all other threads in the kernel. This means that we cannot call kfree() on it as we usually would. We should be able to release this memory as well as other structures that are only required during the setup process, but this is another matter. Do not free the initial kernel thread in thread_free(). This fixes the following error: [process] terminating thread 0 (kstartup) [vm] freeing unaligned virtual address: c0954990 (skipping) kfree:49: !(page->flags & PAGE_SLAB) Call stack: #0 0xc0111693 in #1 0xc0105c86 in #2 0xc0105ccb in #3 0xc0104a07 in #4 0xc0104a4d in #5 0xc0118925 in #6 0xc0115de7 in #7 0xc0115ec7 in <__common_handler+7> #8 0xc01032cb in #9 0xc0103bff in #10 0xc010c0ba in #11 0xc0107e83 in #12 0xc0105ed0 in <__process_execute_in_userland+65> #13 0xc01172b1 in === --- kernel/sys/process.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/kernel/sys/process.c b/kernel/sys/process.c index a0b6fb21..d75df4d0 100644 --- a/kernel/sys/process.c +++ b/kernel/sys/process.c @@ -425,7 +425,10 @@ static void thread_free(thread_t *thread) llist_remove(&thread->proc_this); vm_free(&kernel_address_space, thread_get_kernel_stack(thread)); - kfree(thread); + + /* initial thread is statically allocated so we can't kfree() it. */ + if (thread != &kernel_process_initial_thread) + kfree(thread); /* * Release reference this threads holds onto the process.