diff --git a/include/blockmalloc/blockmalloc.h b/include/blockmalloc/blockmalloc.h index b4fd719..b66b08d 100644 --- a/include/blockmalloc/blockmalloc.h +++ b/include/blockmalloc/blockmalloc.h @@ -1,28 +1,10 @@ -#pragma once /* -blockmalloc: 固定大小内存块分配器(block pool)。C11,无外部依赖。 - -用途:管理固定 block_size 的块数组,线程/进程安全(原子自旋锁)。 -boxmalloc 依赖本库管理 box_head_t 节点。 - -布局:meta 区(blocks_meta_t)+ block 区(连续 block 数组,每 block = block_head + data)。 -空闲链表:单向链,free_next_id 哨兵 -1。block head 自动选 2/4/8 字节节省内存。 -*/ - -/* -blockmalloc: 固定大小内存块分配器(block pool)。C11,无外部依赖。 - -用途:管理固定 block_size 的块数组,线程/进程安全(原子自旋锁)。 -boxmalloc 依赖本库管理 box_head_t 节点。 - -布局:meta 区(blocks_meta_t)+ block 区(连续 block 数组,每 block = block_head + data)。 -空闲链表:单向链,free_next_id 哨兵 -1。block head 自动选 2/4/8 字节节省内存。 - -用法: - - 链接 .so: #include "blockmalloc/blockmalloc.h" + link -lblockmalloc - - 单头文件: 在一个 .c 中 #define BLOCKMALLOC_IMPLEMENTATION,然后 #include "blockmalloc/blockmalloc.h" - 其余 .c 直接 #include "blockmalloc/blockmalloc.h" -*/ + * blockmalloc: fixed-size block pool allocator (C11, header-only, no external deps). + * + * Layout: separate meta (blocks_meta_t, 48B) and block pool array. + * Each block i at i*(head+block_size) is [head (2/4/8B LE): bit0=used, bit1=has_next, bits2..=next_free_id][data (block_size B)]. + * Lock-free readers use blocks_head_size / blocks_malloc_count; updates to malloc_blocks must hold meta->lock and use blocks_set_malloc_count. + */ #ifndef blockmalloc_H #define blockmalloc_H @@ -33,354 +15,329 @@ boxmalloc 依赖本库管理 box_head_t 节点。 typedef struct { uint64_t total_size; uint64_t block_size; - uint8_t sizeof_block_head:4; - uint64_t malloc_blocks:60; + union { + struct { + uint64_t sizeof_block_head : 4; // 2/4/8 + uint64_t malloc_blocks : 60; // high-water allocated block count + }; + uint64_t head_and_blocks; // full-word alias for atomic access + }; uint64_t used_blocks; - int64_t free_next_id; // 首个空闲块 ID,-1=无空闲 - int64_t lock; // 原子自旋锁 + int64_t free_next_id; // head of free list, -1 if empty + int64_t lock; // atomic spinlock } blocks_meta_t; +// Persisted in shared memory and files; ABI layout is fixed. +_Static_assert(sizeof(blocks_meta_t) == 48, "blocks_meta_t layout changed"); +_Static_assert(offsetof(blocks_meta_t, head_and_blocks) == 16, "blocks_meta_t layout changed"); +_Static_assert(offsetof(blocks_meta_t, lock) == 40, "blocks_meta_t layout changed"); + int blocks_init(blocks_meta_t *meta, uint64_t total_size, uint64_t block_size); int64_t blocks_alloc(blocks_meta_t *meta, void *block_start); void blocks_free(blocks_meta_t *meta, void *block_start, uint64_t block_id); +int64_t blockid_byblockoffset(const blocks_meta_t *meta, uint64_t offset); +int64_t blockid_bydataoffset(const blocks_meta_t *meta, uint64_t data_offset); -// 辅助偏移计算(实现 TU 内联;无外部消费者,不导出符号) -#ifdef BLOCKMALLOC_IMPLEMENTATION -#define BLOCKS_INLINE static inline +// ---- Full-word accessors for head_and_blocks ---- +#ifdef _MSC_VER +static inline uint64_t bm_load_u64(const uint64_t *p) { return *(const volatile uint64_t *)p; } +static inline uint64_t bm_load_u64_acquire(const uint64_t *p) +{ + uint64_t v = *(const volatile uint64_t *)p; + _ReadWriteBarrier(); + return v; +} +static inline void bm_store_u64(uint64_t *p, uint64_t v) { *(volatile uint64_t *)p = v; } +static inline void bm_store_u64_release(uint64_t *p, uint64_t v) +{ + _ReadWriteBarrier(); + *(volatile uint64_t *)p = v; +} #else -#define BLOCKS_INLINE +static inline uint64_t bm_load_u64(const uint64_t *p) { return __atomic_load_n(p, __ATOMIC_RELAXED); } +static inline uint64_t bm_load_u64_acquire(const uint64_t *p) { return __atomic_load_n(p, __ATOMIC_ACQUIRE); } +static inline void bm_store_u64(uint64_t *p, uint64_t v) { __atomic_store_n(p, v, __ATOMIC_RELAXED); } +static inline void bm_store_u64_release(uint64_t *p, uint64_t v) { __atomic_store_n(p, v, __ATOMIC_RELEASE); } #endif -BLOCKS_INLINE int64_t block_offset(const blocks_meta_t *meta, uint64_t block_id); -BLOCKS_INLINE int64_t blockdata_offset(const blocks_meta_t *meta, uint64_t block_id); -int64_t blockid_byblockoffset(const blocks_meta_t *meta, uint64_t block_offset); -int64_t blockid_bydataoffset(const blocks_meta_t *meta, uint64_t data_offset); +// Bit-field positions within head_and_blocks (verified in blocks_init). +#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ +#define BLOCKMALLOC_HEAD_SHIFT 60 +#define BLOCKMALLOC_COUNT_SHIFT 0 +#else +#define BLOCKMALLOC_HEAD_SHIFT 0 +#define BLOCKMALLOC_COUNT_SHIFT 4 #endif +#define BLOCKMALLOC_COUNT_MASK ((UINT64_C(1) << 60) - 1) + +static inline uint64_t bm_word_head(uint64_t w) { return (w >> BLOCKMALLOC_HEAD_SHIFT) & 0xFu; } +static inline uint64_t bm_word_count(uint64_t w) { return (w >> BLOCKMALLOC_COUNT_SHIFT) & BLOCKMALLOC_COUNT_MASK; } +static inline uint64_t bm_word_make(uint64_t head, uint64_t count) +{ + return ((head & 0xFu) << BLOCKMALLOC_HEAD_SHIFT) | ((count & BLOCKMALLOC_COUNT_MASK) << BLOCKMALLOC_COUNT_SHIFT); +} + +// ---- Public lock-free inline helpers ---- +static inline uint64_t blocks_head_size(const blocks_meta_t *meta) +{ + return bm_word_head(bm_load_u64(&meta->head_and_blocks)); +} +static inline uint64_t blocks_malloc_count(const blocks_meta_t *meta) +{ + return bm_word_count(bm_load_u64_acquire(&meta->head_and_blocks)); +} +// Caller must hold meta->lock. +static inline void blocks_set_malloc_count(blocks_meta_t *meta, uint64_t n) +{ + uint64_t w = bm_load_u64(&meta->head_and_blocks); + bm_store_u64_release(&meta->head_and_blocks, bm_word_make(bm_word_head(w), n)); +} +static inline int64_t block_offset(const blocks_meta_t *meta, uint64_t block_id) +{ + return (int64_t)(block_id * (blocks_head_size(meta) + meta->block_size)); +} +static inline int64_t blockdata_offset(const blocks_meta_t *meta, uint64_t block_id) +{ + uint64_t head = blocks_head_size(meta); + return (int64_t)(block_id * (head + meta->block_size) + head); +} -#ifdef BLOCKMALLOC_IMPLEMENTATION +#endif // blockmalloc_H -// ============================================================ -// implementation (from src/blockmalloc.c + src/logutil.h) -// ============================================================ +#if defined(BLOCKMALLOC_IMPLEMENTATION) && !defined(BLOCKMALLOC_IMPLEMENTED) +#define BLOCKMALLOC_IMPLEMENTED -// --- logutil --- -#ifdef ENABLE_LOG - #ifdef PLATFORM_LOG - #define LOG(fmt, ...) PLATFORM_LOG("[blockmalloc] " fmt, ##__VA_ARGS__) - #else - #define LOG(fmt, ...) ((void)0) - #endif +#if defined(ENABLE_LOG) && defined(PLATFORM_LOG) +#define BM_LOG(...) PLATFORM_LOG("[blockmalloc] " __VA_ARGS__) #else - #define LOG(fmt, ...) ((void)0) +#define BM_LOG(...) ((void)0) #endif -// --- spinlock --- -#if defined(_MSC_VER) +// ---- spinlock ---- +#ifdef _MSC_VER #include #include #endif -/* architecture-specific CPU-relax / pause */ #if defined(__x86_64__) || defined(__i386__) -static inline void cpu_relax(void) { __asm__ __volatile__("pause" ::: "memory"); } +static inline void bm_cpu_relax(void) { __asm__ __volatile__("pause" ::: "memory"); } #elif defined(__aarch64__) || defined(__arm__) -static inline void cpu_relax(void) { __asm__ __volatile__("yield" ::: "memory"); } +static inline void bm_cpu_relax(void) { __asm__ __volatile__("yield" ::: "memory"); } #elif defined(_MSC_VER) -static inline void cpu_relax(void) { YieldProcessor(); } +static inline void bm_cpu_relax(void) { YieldProcessor(); } #else -static inline void cpu_relax(void) { (void)0; } +static inline void bm_cpu_relax(void) { (void)0; } #endif -/* atomic exchange / store helpers (platform specific) */ #ifdef _MSC_VER -static inline int64_t spin_atomic_exchange(int64_t *ptr, int64_t val) +static inline int64_t bm_atomic_xchg(int64_t *p, int64_t v) { - /* InterlockedExchange64 works with LONG64; cast to/from int64_t */ - return (int64_t)InterlockedExchange64((volatile LONG64 *)ptr, (LONG64)val); + return (int64_t)InterlockedExchange64((volatile LONG64 *)p, (LONG64)v); } -static inline void spin_atomic_store(int64_t *ptr, int64_t val) +static inline int64_t bm_atomic_load_relaxed(const int64_t *p) { return *(const volatile int64_t *)p; } +static inline void bm_atomic_store_release(int64_t *p, int64_t v) { - /* Use InterlockedExchange64 as a store (returns previous, ignore it) */ - InterlockedExchange64((volatile LONG64 *)ptr, (LONG64)val); + InterlockedExchange64((volatile LONG64 *)p, (LONG64)v); } #else -static inline int64_t spin_atomic_exchange(int64_t *ptr, int64_t val) -{ - return __atomic_exchange_n((int64_t *)ptr, val, __ATOMIC_ACQ_REL); -} -static inline void spin_atomic_store(int64_t *ptr, int64_t val) -{ - __atomic_store_n((int64_t *)ptr, val, __ATOMIC_RELEASE); -} +static inline int64_t bm_atomic_xchg(int64_t *p, int64_t v) { return __atomic_exchange_n(p, v, __ATOMIC_ACQ_REL); } +static inline int64_t bm_atomic_load_relaxed(const int64_t *p) { return __atomic_load_n(p, __ATOMIC_RELAXED); } +static inline void bm_atomic_store_release(int64_t *p, int64_t v) { __atomic_store_n(p, v, __ATOMIC_RELEASE); } #endif +// Keep spin_lock / spin_unlock / setblock_t names stable for kvspace-c art_bump. +// TTAS spinlock avoids cacheline bouncing while spinning. static inline void spin_lock(int64_t *lock) { - while (spin_atomic_exchange(lock, 1)) - { - cpu_relax(); + while (bm_atomic_xchg(lock, 1)) { + while (bm_atomic_load_relaxed(lock)) + bm_cpu_relax(); } } +static inline void spin_unlock(int64_t *lock) { bm_atomic_store_release(lock, 0); } -static inline void spin_unlock(int64_t *lock) -{ - spin_atomic_store(lock, 0); -} - -#ifdef _MSC_VER -#pragma pack(push, 1) +// ---- block head (2/4/8-byte little-endian integer) ---- +typedef struct { + uint8_t used; + uint8_t has_next_free; + uint64_t next_free_id; +} bm_head_t; + +// Unaligned-safe memcpy access; byte-swap on big-endian targets. +#if defined(__GNUC__) || defined(__clang__) +#define BM_MEMCPY __builtin_memcpy +#else +#include +#define BM_MEMCPY memcpy #endif - -typedef struct -{ - uint8_t used : 1; - uint8_t has_next_free : 1; - uint16_t next_free_id : 14; -} -#ifdef __GNUC__ -__attribute__((packed)) +#if BLOCKMALLOC_HEAD_SHIFT != 0 +#define BM_LE16(x) __builtin_bswap16(x) +#define BM_LE32(x) __builtin_bswap32(x) +#define BM_LE64(x) __builtin_bswap64(x) +#else +#define BM_LE16(x) (x) +#define BM_LE32(x) (x) +#define BM_LE64(x) (x) #endif -block16_t; -typedef struct +static inline uint64_t bm_head_load(const void *p, uint64_t head) { - uint8_t used : 1; - uint8_t has_next_free : 1; - uint32_t next_free_id : 30; + switch (head) { + case 2: { uint16_t v; BM_MEMCPY(&v, p, 2); return BM_LE16(v); } + case 4: { uint32_t v; BM_MEMCPY(&v, p, 4); return BM_LE32(v); } + case 8: { uint64_t v; BM_MEMCPY(&v, p, 8); return BM_LE64(v); } + default: + BM_LOG("[ERROR] invalid sizeof_block_head %u", (unsigned)head); + return 0; + } } -#ifdef __GNUC__ -__attribute__((packed)) -#endif -block32_t; - -typedef struct +static inline void bm_head_store(void *p, uint64_t head, uint8_t used, int64_t free_next_id) { - uint8_t used : 1; - uint8_t has_next_free : 1; - uint64_t next_free_id : 62; + uint64_t v = (uint64_t)(used & 1u); + if (free_next_id != -1) + v |= 2u | ((uint64_t)free_next_id << 2); + switch (head) { + case 2: { uint16_t x = (uint16_t)BM_LE16((uint16_t)v); BM_MEMCPY(p, &x, 2); break; } + case 4: { uint32_t x = (uint32_t)BM_LE32((uint32_t)v); BM_MEMCPY(p, &x, 4); break; } + case 8: { uint64_t x = BM_LE64(v); BM_MEMCPY(p, &x, 8); break; } + default: BM_LOG("[ERROR] invalid sizeof_block_head %u", (unsigned)head); break; + } } -#ifdef __GNUC__ -__attribute__((packed)) -#endif -block64_t; - -#ifdef _MSC_VER -#pragma pack(pop) -#endif static inline void setblock_t(const blocks_meta_t *meta, void *block_ptr, uint8_t used, int64_t free_next_id) { - switch (meta->sizeof_block_head) - { - case 2: - { - block16_t *block = (block16_t *)block_ptr; - block->used = used; - block->has_next_free = (free_next_id != -1) ? 1 : 0; - block->next_free_id = (free_next_id != -1) ? (uint16_t)free_next_id : 0; - break; - } - case 4: - { - block32_t *block = (block32_t *)block_ptr; - block->used = used; - block->has_next_free = (free_next_id != -1) ? 1 : 0; - block->next_free_id = (free_next_id != -1) ? (uint32_t)free_next_id : 0; - break; - } - case 8: - { - block64_t *block = (block64_t *)block_ptr; - block->used = used; - block->has_next_free = (free_next_id != -1) ? 1 : 0; - block->next_free_id = (free_next_id != -1) ? (uint64_t)free_next_id : 0; - break; - } - default: - LOG("[ERROR] Invalid sizeof_block_head: %d, returning default block", meta->sizeof_block_head); - // result 已初始化为 0,无需额外操作 - break; - } + bm_head_store(block_ptr, blocks_head_size(meta), used, free_next_id); } -static inline block64_t getblock_t(const blocks_meta_t *meta, void *block_ptr) +static inline bm_head_t getblock_t(const blocks_meta_t *meta, const void *block_ptr) { - switch (meta->sizeof_block_head) - { - case 2: - { - block16_t *block = (block16_t *)block_ptr; - return (block64_t){ - .used = block->used, - .has_next_free = block->has_next_free, - .next_free_id = block->next_free_id, - }; - } - case 4: - { - block32_t *block = (block32_t *)block_ptr; - return (block64_t){ - .used = block->used, - .has_next_free = block->has_next_free, - .next_free_id = block->next_free_id, - }; - } - case 8: - { - block64_t *block = (block64_t *)block_ptr; - return (block64_t){ - .used = block->used, - .has_next_free = block->has_next_free, - .next_free_id = block->next_free_id, - }; - } - default: - // 处理无效 sizeof_block_head(例如,数据损坏) - LOG("[ERROR] Invalid sizeof_block_head: %d, returning default block", meta->sizeof_block_head); - // result 已初始化为 0,无需额外操作 - return (block64_t){0}; - } + uint64_t v = bm_head_load(block_ptr, blocks_head_size(meta)); + return (bm_head_t){ + .used = (uint8_t)(v & 1u), + .has_next_free = (uint8_t)((v >> 1) & 1u), + .next_free_id = v >> 2, + }; } -#define BLOCK_SIZE(meta) (meta->sizeof_block_head + meta->block_size) - -int blocks_init(blocks_meta_t *meta, const uint64_t total_size, const uint64_t block_size) +int blocks_init(blocks_meta_t *meta, uint64_t total_size, uint64_t block_size) { - if (meta == NULL) - { - LOG("[ERROR] meta is NULL"); + if (meta == NULL || block_size > UINT64_MAX - 8) { + BM_LOG("[ERROR] meta is NULL or block_size too large"); return -1; } + // Estimate max blocks with 2B head; pick 2/4/8B head based on next_free_id width (14/30/62 bits). + uint64_t max_blocks = total_size / (2 + block_size); + uint64_t head = max_blocks <= 32767ULL / 4 ? 2 : max_blocks <= 2147483647ULL / 4 ? 4 : 8; *meta = (blocks_meta_t){ .total_size = total_size, .block_size = block_size, - .malloc_blocks = 0, .used_blocks = 0, - .free_next_id = -1, // 初始化为 -1,表示没有空闲块,需要新增分配 - .lock = 0, // 初始化锁为未锁定状态 + .free_next_id = -1, + .lock = 0, }; - uint64_t min_header_size = 2; // 假设最小 2 字节 (int16_t) - uint64_t max_blocks = meta->total_size / (min_header_size + meta->block_size); - if (max_blocks <= (32767ULL / 4ULL)) - meta->sizeof_block_head = 2; // int16_t - else if (max_blocks <= (2147483647ULL / 4ULL)) - meta->sizeof_block_head = 4; // int32_t - else - meta->sizeof_block_head = 8; // int64_t + meta->head_and_blocks = bm_word_make(head, 0); + if (meta->sizeof_block_head != head || meta->malloc_blocks != 0) { + BM_LOG("[ERROR] unsupported bit-field layout of blocks_meta_t"); + return -1; + } return 0; } -BLOCKS_INLINE int64_t block_offset(const blocks_meta_t *meta, const uint64_t block_id) -{ - int64_t offset = block_id * BLOCK_SIZE(meta); - return offset; -} -BLOCKS_INLINE int64_t blockdata_offset(const blocks_meta_t *meta, const uint64_t block_id) -{ - int64_t offset = block_offset(meta, block_id); - return offset + meta->sizeof_block_head; -} - -int64_t blockid_byblockoffset(const blocks_meta_t *meta, const uint64_t block_offset) +int64_t blockid_byblockoffset(const blocks_meta_t *meta, uint64_t offset) { - uint64_t block_size = BLOCK_SIZE(meta); - if (block_offset % block_size != 0) - { - // 计算建议的正确偏移量(向下取整到最近的块边界) - uint64_t suggested_offset = block_offset - (block_offset % block_size); - LOG("[ERROR] block_offset %zu is not aligned with BLOCK_SIZE %zu, suggested offset: %zu", - block_offset, block_size, suggested_offset); + uint64_t w = bm_load_u64_acquire(&meta->head_and_blocks); + uint64_t stride = bm_word_head(w) + meta->block_size; + if (offset % stride != 0) { + BM_LOG("[ERROR] offset %llu is not a multiple of block stride %llu", + (unsigned long long)offset, (unsigned long long)stride); return -1; } - uint64_t block_id = block_offset / block_size; - return block_id; + uint64_t block_id = offset / stride; + if (block_id >= bm_word_count(w)) { + BM_LOG("[ERROR] offset %llu is beyond the allocated blocks", (unsigned long long)offset); + return -1; + } + return (int64_t)block_id; } -int64_t blockid_bydataoffset(const blocks_meta_t *meta, const uint64_t data_offset) +int64_t blockid_bydataoffset(const blocks_meta_t *meta, uint64_t data_offset) { - return blockid_byblockoffset(meta, data_offset - meta->sizeof_block_head); + uint64_t head = blocks_head_size(meta); + if (data_offset < head) { + BM_LOG("[ERROR] data offset %llu is inside the first block head", (unsigned long long)data_offset); + return -1; + } + return blockid_byblockoffset(meta, data_offset - head); } int64_t blocks_alloc(blocks_meta_t *meta, void *block_start) { - if (meta == NULL || block_start == NULL) - { - LOG("[ERROR] meta or block_start is NULL"); + if (meta == NULL || block_start == NULL) { + BM_LOG("[ERROR] meta or block_start is NULL"); return -1; } + uint8_t *base = (uint8_t *)block_start; + int64_t block_id; spin_lock(&meta->lock); - if (meta->free_next_id == -1) - { - uint64_t totalused_size = block_offset(meta, meta->malloc_blocks); - if (totalused_size + BLOCK_SIZE(meta) > meta->total_size) - { - LOG("[ERROR] out of memory,%zu(used_size)= %zu(malloc_blocks)*(sizeof(block_head)=%d)+%zu(block_size)),when total_size %zu", - totalused_size, (uint64_t)(meta->malloc_blocks), (uint8_t)(meta->sizeof_block_head), meta->block_size, meta->total_size); + // Load head_and_blocks once under lock. + uint64_t w = bm_load_u64(&meta->head_and_blocks); + uint64_t head = bm_word_head(w); + uint64_t stride = head + meta->block_size; + if (meta->free_next_id != -1) { + block_id = meta->free_next_id; + uint64_t v = bm_head_load(base + (uint64_t)block_id * stride, head); + meta->free_next_id = (v & 2u) ? (int64_t)(v >> 2) : -1; + bm_head_store(base + (uint64_t)block_id * stride, head, 1, -1); + BM_LOG("[INFO] reuse block %lld", (long long)block_id); + } else { + uint64_t n = bm_word_count(w); + uint64_t used_size = n * stride; + if (used_size > meta->total_size || meta->total_size - used_size < stride) { + BM_LOG("[ERROR] out of memory: %llu blocks * %llu bytes, total_size %llu", + (unsigned long long)n, (unsigned long long)stride, (unsigned long long)meta->total_size); spin_unlock(&meta->lock); return -1; } - else - { - meta->malloc_blocks++; - meta->used_blocks++; - - uint64_t block_id = meta->malloc_blocks - 1; - int64_t b_offset = block_offset(meta, block_id); - setblock_t(meta, (uint8_t *)block_start + b_offset, 1, -1); - LOG("[INFO] append block %zu,blocks usage: %zu/%zu", block_id, (uint64_t)(meta->used_blocks), (uint64_t)(meta->malloc_blocks)); - spin_unlock(&meta->lock); - return block_id; - } - } - else - { - uint64_t free_id = meta->free_next_id; - int64_t b_offset = block_offset(meta, free_id); - block64_t free_block = getblock_t(meta, (uint8_t *)block_start + b_offset); - meta->free_next_id = free_block.has_next_free ? (int64_t)free_block.next_free_id : -1; - meta->used_blocks++; - setblock_t(meta, (uint8_t *)block_start + b_offset, 1, -1); - - LOG("[INFO] reusing block %zu, blocks usage: %zu/%zu", - free_id, (uint64_t)(meta->used_blocks), (uint64_t)(meta->malloc_blocks)); - spin_unlock(&meta->lock); - return free_id; + block_id = (int64_t)n; + bm_head_store(base + (uint64_t)block_id * stride, head, 1, -1); + bm_store_u64_release(&meta->head_and_blocks, bm_word_make(head, n + 1)); + BM_LOG("[INFO] append block %lld", (long long)block_id); } + meta->used_blocks++; + spin_unlock(&meta->lock); + return block_id; } -void blocks_free(blocks_meta_t *meta, void *block_start, const uint64_t block_id) +void blocks_free(blocks_meta_t *meta, void *block_start, uint64_t block_id) { - if (meta == NULL || block_start == NULL) - { - LOG("[ERROR] meta or block_start must not NULL"); + if (meta == NULL || block_start == NULL) { + BM_LOG("[ERROR] meta or block_start is NULL"); return; } spin_lock(&meta->lock); - if (block_id >= meta->malloc_blocks) - { - LOG("[ERROR] block id %zu out of range", block_id); - spin_unlock(&(meta->lock)); + uint64_t w = bm_load_u64(&meta->head_and_blocks); + uint64_t head = bm_word_head(w); + if (block_id >= bm_word_count(w)) { + BM_LOG("[ERROR] block id %llu out of range", (unsigned long long)block_id); + spin_unlock(&meta->lock); return; } - int64_t b_offset = block_offset(meta, block_id); - block64_t free_block = getblock_t(meta, (uint8_t *)block_start + b_offset); - - switch (free_block.used) - { - case 0: - LOG("[WARN] block id %zu already free", block_id); - break; - case 1: - setblock_t(meta, (uint8_t *)block_start + b_offset, 0, meta->free_next_id); - meta->free_next_id = block_id; + uint8_t *h = (uint8_t *)block_start + block_id * (head + meta->block_size); + if (!(bm_head_load(h, head) & 1u)) { + BM_LOG("[WARN] block id %llu already free", (unsigned long long)block_id); + } else { + bm_head_store(h, head, 0, meta->free_next_id); + meta->free_next_id = (int64_t)block_id; meta->used_blocks--; - break; - default: - LOG("[WARN] block id %zu status invalid %d,who make this?", block_id, free_block.used); - break; + BM_LOG("[INFO] block id %llu freed", (unsigned long long)block_id); } - LOG("[INFO] block id %zu freed", block_id); - spin_unlock(&(meta->lock)); + spin_unlock(&meta->lock); } -#endif +#undef BM_LOG +#undef BM_MEMCPY +#undef BM_LE16 +#undef BM_LE32 +#undef BM_LE64 + +#endif // BLOCKMALLOC_IMPLEMENTATION diff --git a/tutorial/01_basic_alloc_free.c b/tutorial/01_basic_alloc_free.c index 9141529..7fbc757 100644 --- a/tutorial/01_basic_alloc_free.c +++ b/tutorial/01_basic_alloc_free.c @@ -1,28 +1,40 @@ -#include +/* Basic alloc/free: sequential append, LIFO reuse, double/OOB free ignored, offset helpers. */ +#include "common.h" -#include "blockmalloc/blockmalloc.h" +int main(void) +{ + static uint8_t pool[1024 * 4]; + blocks_meta_t m; + CHECK(blocks_init(&m, sizeof pool, 64) == 0); + CHECK(blocks_head_size(&m) == 2); -void test_free(blocks_meta_t *blocks, void* block_start) { - int64_t block_id = blocks_alloc(blocks, block_start); - for(int i=0;i<10;i++){ - block_id = blocks_alloc(blocks, block_start); - } - blocks_free(blocks, block_start, 9); - blocks_alloc(blocks, block_start); - blocks_free(blocks, block_start, 9); - blocks_free(blocks, block_start, 3); - blocks_alloc(blocks, block_start); - blocks_free(blocks, block_start, 5); - blocks_free(blocks, block_start, 4); - blocks_alloc(blocks, block_start); -} + for (int64_t i = 0; i < 11; i++) + CHECK(blocks_alloc(&m, pool) == i); // append when freelist is empty + blocks_free(&m, pool, 9); + CHECK(blocks_alloc(&m, pool) == 9); // reuse freed block + blocks_free(&m, pool, 9); + blocks_free(&m, pool, 3); + CHECK(blocks_alloc(&m, pool) == 3); // LIFO reuse + CHECK(blocks_alloc(&m, pool) == 9); + CHECK(blocks_alloc(&m, pool) == 11); // freelist empty, append again + blocks_free(&m, pool, 5); + blocks_free(&m, pool, 4); + blocks_free(&m, pool, 4); // double free: ignored + blocks_free(&m, pool, 100); // OOB free: ignored + CHECK(m.used_blocks == 10); + CHECK(blocks_alloc(&m, pool) == 4); + CHECK(blocks_alloc(&m, pool) == 5); + CHECK(m.used_blocks == 12 && blocks_malloc_count(&m) == 12); + + // Offset helpers: stride = 2 + 64 + CHECK(block_offset(&m, 3) == 3 * 66); + CHECK(blockdata_offset(&m, 3) == 3 * 66 + 2); + CHECK(blockid_byblockoffset(&m, 3 * 66) == 3); + CHECK(blockid_bydataoffset(&m, 3 * 66 + 2) == 3); + CHECK(blockid_byblockoffset(&m, 3 * 66 + 1) == -1); // misaligned offset + CHECK(blockid_bydataoffset(&m, 1) == -1); // inside first block head + CHECK(blockid_bydataoffset(&m, 12 * 66 + 2) == -1); // beyond allocated blocks -int main(int argc, char *argv[]) { - - uint8_t pool[1024 * 4]; - blocks_meta_t blocks; - blocks_init(&blocks, 1024 * 4, 64); - printf("blocks inited\n"); - test_free(&blocks, pool); + printf("basic alloc/free ok\n"); return 0; -} \ No newline at end of file +} diff --git a/tutorial/02_alloc_max.c b/tutorial/02_alloc_max.c index acd8299..140f410 100644 --- a/tutorial/02_alloc_max.c +++ b/tutorial/02_alloc_max.c @@ -1,22 +1,35 @@ -#include -#include -#include "blockmalloc/blockmalloc.h" +/* Allocate to capacity, verify full data write preserves heads, free all and reallocate. */ +#include "common.h" -void test_alloc(blocks_meta_t *blocks, void *block_start) { - for (size_t i = 0; i < 1000; i++) { - int64_t block_id= blocks_alloc(blocks, block_start); - if (block_id == -1) { - printf("Alloc failed at iteration %zu\n", i); - return; - } +int main(void) +{ + const uint64_t total = 1024 * 4, bs = 64; + uint8_t *pool = malloc(total); + blocks_meta_t m; + CHECK(pool != NULL && blocks_init(&m, total, bs) == 0); + const int64_t cap = (int64_t)(total / (2 + bs)); // 62 + CHECK((int64_t)pool_capacity(&m) == cap); + + for (int64_t i = 0; i < cap; i++) { + CHECK(blocks_alloc(&m, pool) == i); + memset(pool + blockdata_offset(&m, (uint64_t)i), 0xFF, bs); } -} + CHECK(blocks_alloc(&m, pool) == -1); + CHECK(m.used_blocks == (uint64_t)cap); + + for (int64_t i = 0; i < cap; i++) + blocks_free(&m, pool, (uint64_t)i); + CHECK(m.used_blocks == 0); -int main() { - uint64_t totalsize=1024 * 4; - uint8_t *pool=malloc(totalsize); - blocks_meta_t blocks; - blocks_init(&blocks,totalsize, 64); - test_alloc(&blocks, pool); + uint8_t seen[64] = {0}; + for (int64_t i = 0; i < cap; i++) { + int64_t id = blocks_alloc(&m, pool); + CHECK(id >= 0 && id < cap && !seen[id]); + seen[id] = 1; + } + CHECK(blocks_alloc(&m, pool) == -1); + + printf("alloc until full ok: %lld blocks\n", (long long)cap); + free(pool); return 0; -} \ No newline at end of file +} diff --git a/tutorial/03_multithread.c b/tutorial/03_multithread.c index 925f02a..4b9621e 100644 --- a/tutorial/03_multithread.c +++ b/tutorial/03_multithread.c @@ -1,56 +1,45 @@ +/* Multi-threaded concurrent alloc/free within a single process. */ #include -#include -#include -#include -#include "blockmalloc/blockmalloc.h" +#include "common.h" #define NUM_THREADS 8 #define BLOCKS_PER_THREAD 500 +#define ROUNDS 20 #define POOL_SIZE (1024 * 1024 * 4) -#define BLOCK_SIZE 64 +#define DATA_SIZE 64 static blocks_meta_t meta; static uint8_t *pool; +static uint32_t *owners; -void* thread_func(void *arg) { - int64_t *ids = malloc(BLOCKS_PER_THREAD * sizeof(int64_t)); - - for (int i = 0; i < BLOCKS_PER_THREAD; i++) { - ids[i] = blocks_alloc(&meta, pool); - if (ids[i] < 0) { - printf("[ERROR] alloc failed at i=%d\n", i); - free(ids); - return (void*)1; - } - } - for (int i = 0; i < BLOCKS_PER_THREAD; i++) { - blocks_free(&meta, pool, ids[i]); - } - free(ids); - return NULL; +static void *thread_func(void *arg) +{ + uint32_t tag = (uint32_t)(uintptr_t)arg; + return stress_worker(&meta, pool, owners, tag, BLOCKS_PER_THREAD, ROUNDS) ? (void *)1 : NULL; } -int main() { - pool = malloc(POOL_SIZE); - memset(pool, 0, POOL_SIZE); - blocks_init(&meta, POOL_SIZE, BLOCK_SIZE); +int main(void) +{ + pool = calloc(1, POOL_SIZE); + CHECK(pool != NULL && blocks_init(&meta, POOL_SIZE, DATA_SIZE) == 0); + owners = calloc(pool_capacity(&meta), sizeof *owners); + CHECK(owners != NULL); pthread_t threads[NUM_THREADS]; + for (uintptr_t i = 0; i < NUM_THREADS; i++) + CHECK(pthread_create(&threads[i], NULL, thread_func, (void *)(i + 1)) == 0); int errors = 0; - - for (int i = 0; i < NUM_THREADS; i++) { - pthread_create(&threads[i], NULL, thread_func, NULL); - } for (int i = 0; i < NUM_THREADS; i++) { void *ret; pthread_join(threads[i], &ret); - if (ret != NULL) errors++; + if (ret != NULL) + errors++; } - - printf("multithread: used=%zu malloc=%zu errors=%d\n", - (size_t)meta.used_blocks, (size_t)meta.malloc_blocks, errors); - - int ok = (meta.used_blocks == 0 && errors == 0); + printf("multithread: used=%llu malloc=%llu errors=%d\n", + (unsigned long long)meta.used_blocks, (unsigned long long)blocks_malloc_count(&meta), errors); + CHECK(errors == 0 && meta.used_blocks == 0); + CHECK(blocks_malloc_count(&meta) <= (uint64_t)NUM_THREADS * BLOCKS_PER_THREAD); + free(owners); free(pool); - return ok ? 0 : 1; + return 0; } diff --git a/tutorial/04_multiprocess.c b/tutorial/04_multiprocess.c index 4d37d2f..b4400cc 100644 --- a/tutorial/04_multiprocess.c +++ b/tutorial/04_multiprocess.c @@ -1,66 +1,49 @@ +/* Multi-process concurrent alloc/free over MAP_SHARED anonymous memory. */ #define _GNU_SOURCE -#include -#include -#include -#include -#include #include -#include "blockmalloc/blockmalloc.h" +#include +#include +#include "common.h" #define NUM_PROCESSES 4 #define BLOCKS_PER_PROCESS 500 +#define ROUNDS 20 #define POOL_SIZE (1024 * 1024 * 4) -#define BLOCK_SIZE 64 +#define DATA_SIZE 64 -int main() { - blocks_meta_t *meta = mmap(NULL, sizeof(blocks_meta_t), - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); - uint8_t *pool = mmap(NULL, POOL_SIZE, - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); +static void *shared(size_t n) +{ + void *p = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + return p == MAP_FAILED ? NULL : p; +} - if (meta == MAP_FAILED || pool == MAP_FAILED) { - perror("mmap"); - return 1; - } - memset(pool, 0, POOL_SIZE); - blocks_init(meta, POOL_SIZE, BLOCK_SIZE); +int main(void) +{ + blocks_meta_t *meta = shared(sizeof *meta); + uint8_t *pool = shared(POOL_SIZE); + CHECK(meta != NULL && pool != NULL && blocks_init(meta, POOL_SIZE, DATA_SIZE) == 0); + size_t owners_size = pool_capacity(meta) * sizeof(uint32_t); + uint32_t *owners = shared(owners_size); + CHECK(owners != NULL); for (int p = 0; p < NUM_PROCESSES; p++) { pid_t pid = fork(); - if (pid == 0) { - int64_t *ids = malloc(BLOCKS_PER_PROCESS * sizeof(int64_t)); - - for (int i = 0; i < BLOCKS_PER_PROCESS; i++) { - ids[i] = blocks_alloc(meta, pool); - if (ids[i] < 0) { - printf("[ERROR] process %d alloc failed at i=%d\n", getpid(), i); - free(ids); - _exit(1); - } - } - for (int i = 0; i < BLOCKS_PER_PROCESS; i++) { - blocks_free(meta, pool, ids[i]); - } - free(ids); - _exit(0); - } else if (pid < 0) { - perror("fork"); - return 1; - } + CHECK(pid >= 0); + if (pid == 0) + _exit(stress_worker(meta, pool, owners, (uint32_t)p + 1, BLOCKS_PER_PROCESS, ROUNDS)); } - int ok = 1; for (int i = 0; i < NUM_PROCESSES; i++) { int status; - wait(&status); - if (WEXITSTATUS(status) != 0) ok = 0; + if (wait(&status) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) + ok = 0; // fail on signal termination as well } - - printf("multiprocess: used=%zu malloc=%zu\n", - (size_t)meta->used_blocks, (size_t)meta->malloc_blocks); - - ok = ok && (meta->used_blocks == 0); - munmap(meta, sizeof(blocks_meta_t)); + printf("multiprocess: used=%llu malloc=%llu\n", + (unsigned long long)meta->used_blocks, (unsigned long long)blocks_malloc_count(meta)); + CHECK(ok && meta->used_blocks == 0); + CHECK(blocks_malloc_count(meta) <= (uint64_t)NUM_PROCESSES * BLOCKS_PER_PROCESS); + munmap(owners, owners_size); munmap(pool, POOL_SIZE); - return ok ? 0 : 1; + munmap(meta, sizeof *meta); + return 0; } diff --git a/tutorial/05_multiprocess_multithread.c b/tutorial/05_multiprocess_multithread.c index 2789554..07dfb3b 100644 --- a/tutorial/05_multiprocess_multithread.c +++ b/tutorial/05_multiprocess_multithread.c @@ -1,85 +1,75 @@ +/* Multi-process x multi-thread concurrent alloc/free over shared meta and pool. */ #define _GNU_SOURCE #include -#include -#include -#include -#include -#include #include -#include "blockmalloc/blockmalloc.h" +#include +#include +#include "common.h" #define NUM_PROCESSES 3 #define NUM_THREADS 3 #define BLOCKS_PER_THREAD 300 +#define ROUNDS 20 #define POOL_SIZE (1024 * 1024 * 4) -#define BLOCK_SIZE 64 +#define DATA_SIZE 64 static blocks_meta_t *meta; static uint8_t *pool; +static uint32_t *owners; -void* thread_func(void *arg) { - int64_t *ids = malloc(BLOCKS_PER_THREAD * sizeof(int64_t)); - - for (int i = 0; i < BLOCKS_PER_THREAD; i++) { - ids[i] = blocks_alloc(meta, pool); - if (ids[i] < 0) { - printf("[ERROR] alloc failed at i=%d\n", i); - free(ids); - return (void*)1; - } - } - for (int i = 0; i < BLOCKS_PER_THREAD; i++) { - blocks_free(meta, pool, ids[i]); - } - free(ids); - return NULL; +static void *shared(size_t n) +{ + void *p = mmap(NULL, n, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); + return p == MAP_FAILED ? NULL : p; } -int main() { - meta = mmap(NULL, sizeof(blocks_meta_t), - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); - pool = mmap(NULL, POOL_SIZE, - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0); +static void *thread_func(void *arg) +{ + uint32_t tag = (uint32_t)(uintptr_t)arg; + return stress_worker(meta, pool, owners, tag, BLOCKS_PER_THREAD, ROUNDS) ? (void *)1 : NULL; +} - if (meta == MAP_FAILED || pool == MAP_FAILED) { - perror("mmap"); - return 1; - } - memset(pool, 0, POOL_SIZE); - blocks_init(meta, POOL_SIZE, BLOCK_SIZE); +int main(void) +{ + meta = shared(sizeof *meta); + pool = shared(POOL_SIZE); + CHECK(meta != NULL && pool != NULL && blocks_init(meta, POOL_SIZE, DATA_SIZE) == 0); + size_t owners_size = pool_capacity(meta) * sizeof(uint32_t); + owners = shared(owners_size); + CHECK(owners != NULL); for (int p = 0; p < NUM_PROCESSES; p++) { pid_t pid = fork(); + CHECK(pid >= 0); if (pid == 0) { pthread_t threads[NUM_THREADS]; - - for (int t = 0; t < NUM_THREADS; t++) { - pthread_create(&threads[t], NULL, thread_func, NULL); + int rc = 0; + for (uintptr_t t = 0; t < NUM_THREADS; t++) { + uintptr_t tag = (uintptr_t)p * NUM_THREADS + t + 1; + if (pthread_create(&threads[t], NULL, thread_func, (void *)tag) != 0) + _exit(1); } for (int t = 0; t < NUM_THREADS; t++) { void *ret; pthread_join(threads[t], &ret); - if (ret != NULL) _exit(1); + if (ret != NULL) + rc = 1; } - _exit(0); - } else if (pid < 0) { - perror("fork"); - return 1; + _exit(rc); } } - int ok = 1; for (int i = 0; i < NUM_PROCESSES; i++) { int status; - wait(&status); - if (WEXITSTATUS(status) != 0) ok = 0; + if (wait(&status) < 0 || !WIFEXITED(status) || WEXITSTATUS(status) != 0) + ok = 0; // fail on signal termination as well } - - printf("multiprocess+multithread: used=%zu malloc=%zu\n", - (size_t)meta->used_blocks, (size_t)meta->malloc_blocks); - - ok = ok && (meta->used_blocks == 0); - munmap(meta, sizeof(blocks_meta_t)); + printf("multiprocess+multithread: used=%llu malloc=%llu\n", + (unsigned long long)meta->used_blocks, (unsigned long long)blocks_malloc_count(meta)); + CHECK(ok && meta->used_blocks == 0); + CHECK(blocks_malloc_count(meta) <= (uint64_t)NUM_PROCESSES * NUM_THREADS * BLOCKS_PER_THREAD); + munmap(owners, owners_size); munmap(pool, POOL_SIZE); - return ok ? 0 : 1; + munmap(meta, sizeof *meta); + return 0; } diff --git a/tutorial/common.h b/tutorial/common.h new file mode 100644 index 0000000..f2c700b --- /dev/null +++ b/tutorial/common.h @@ -0,0 +1,70 @@ +/* Shared test assertion and concurrent stress worker. */ +#pragma once +#include +#include +#include +#include +#include "blockmalloc/blockmalloc.h" + +#define CHECK(cond) \ + do { \ + if (!(cond)) { \ + printf("[FAIL] %s:%d: %s\n", __FILE__, __LINE__, #cond); \ + return 1; \ + } \ + } while (0) + +static inline uint64_t pool_capacity(const blocks_meta_t *meta) +{ + return meta->total_size / (blocks_head_size(meta) + meta->block_size); +} + +/* Allocate nblocks, verify ownership and payload integrity, then free in two passes. */ +static inline int stress_worker(blocks_meta_t *meta, uint8_t *pool, uint32_t *owners, + uint32_t tag, int nblocks, int rounds) +{ + int64_t *ids = malloc((size_t)nblocks * sizeof *ids); + if (ids == NULL) + return 1; + int rc = 0; + for (int r = 0; r < rounds && rc == 0; r++) { + int n = 0; + for (; n < nblocks; n++) { + int64_t id = blocks_alloc(meta, pool); + if (id < 0) { + printf("[ERROR] tag %u: alloc failed at %d\n", tag, n); + rc = 1; + break; + } + uint32_t expect = 0; + if (!__atomic_compare_exchange_n(&owners[id], &expect, tag, 0, + __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE)) { + printf("[ERROR] block %lld given to tag %u while held by tag %u\n", + (long long)id, tag, expect); + rc = 1; + break; + } + ids[n] = id; + memset(pool + blockdata_offset(meta, (uint64_t)id), (int)(tag & 0xFFu), (size_t)meta->block_size); + } + for (int i = 0; i < n && rc == 0; i++) { + const uint8_t *d = pool + blockdata_offset(meta, (uint64_t)ids[i]); + for (uint64_t k = 0; k < meta->block_size; k++) { + if (d[k] != (uint8_t)tag) { + printf("[ERROR] block %lld byte %llu corrupted\n", (long long)ids[i], (unsigned long long)k); + rc = 1; + break; + } + } + } + /* Free even indices then odd indices to permute freelist order. */ + for (int pass = 0; pass < 2; pass++) { + for (int i = pass; i < n; i += 2) { + __atomic_store_n(&owners[ids[i]], 0u, __ATOMIC_RELEASE); + blocks_free(meta, pool, (uint64_t)ids[i]); + } + } + } + free(ids); + return rc; +}