Compare commits

27 Commits
old ... master

Author SHA1 Message Date
7134d6ff13 fix bug 2025-06-20 22:27:20 +02:00
786d7213ae Update README 2024-01-16 02:34:19 +01:00
59e2d3ff25 Add more tests 2024-01-16 02:22:23 +01:00
f67a3cc335 Fix arena_delete() 2024-01-16 01:38:49 +01:00
6b46af52aa Update tests
Add test for arena_attach
2024-01-15 16:33:17 +01:00
0af6582a64 Add flag option to not align allocations 2024-01-15 16:32:19 +01:00
6e0e6f7437 Add rudimentary test 2024-01-15 15:04:33 +01:00
d11557a4fe Fix bug and handle more errors
Fix bug where mprotect calls don't match the arena cap increase
(https://github.com/olemorud/arena-allocator/issues/1)

Handle syscall errors in arena_new()

Check for syscall errors in arena_delete() and switch from void to int
to return error status.
2024-01-15 15:02:43 +01:00
44a2982376 Update README 2023-08-19 01:29:21 +02:00
Ole Morud
069f6b11dc Full rewrite 2023-08-19 01:18:32 +02:00
Ole Morud
e5fa743d85 Add arena_free() 2023-08-19 00:49:51 +02:00
Ole Morud
45e6023d42 Add arena_delete() 2023-08-19 00:49:51 +02:00
Ole Morud
d3754e64e5 Rewrite everything 2023-08-19 00:49:51 +02:00
Ole Morud
57979892f8 Add arena_realloc_tail() 2023-08-19 00:49:51 +02:00
olemorud
51feaf1d00 Move header files to include/ 2023-08-19 00:49:51 +02:00
olemorud
529955dbfb Add *.so, *.a to .gitignore 2023-08-19 00:49:49 +02:00
olemorud
05e69d8776 Add dynamic and static as makefile targets 2023-08-19 00:49:49 +02:00
olemorud
9f6eaca42e Add and run WebKit based clang-format 2023-08-19 00:49:49 +02:00
olemorud
d4a9708082 Makefile: save as ar file 2023-08-19 00:49:49 +02:00
olemorud
b9d743e868 Update test_arena.c 2023-08-19 00:49:49 +02:00
olemorud
b95c63c965 Add compile_flags.txt 2023-08-19 00:49:49 +02:00
olemorud
c4fe01a54a Update arena.c, arena.h 2023-08-19 00:49:49 +02:00
olemorud
2b14b86ccb Update Makefile 2023-08-19 00:49:49 +02:00
olemorud
ba39a77c0c Add README 2023-08-19 00:49:49 +02:00
olemorud
6576032654 Makefile: update CFLAGS 2023-08-19 00:49:49 +02:00
olemorud
09528df89b Add .gitignore 2023-08-19 00:49:46 +02:00
olemorud
cd68347dd9 Remove memcpy use 2023-08-19 00:49:41 +02:00
17 changed files with 469 additions and 177 deletions

10
.clang-format Normal file
View File

@@ -0,0 +1,10 @@
BasedOnStyle: WebKit
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: true
AlignConsecutiveDeclarations: true
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: false
PointerAlignment: Right

5
.gitignore vendored Normal file
View File

@@ -0,0 +1,5 @@
obj/
*.o
*.a
*.so
build/

15
CMakeLists.txt Normal file
View File

@@ -0,0 +1,15 @@
cmake_minimum_required(VERSION 3.16)
project("ArenaAllocator"
VERSION 1.0
LANGUAGES C
DESCRIPTION "Arena Allocator in C"
)
add_subdirectory(src)
if(CMAKE_PROJECT_NAME STREQUAL PROJECT_NAME)
message(STATUS "Building tests")
add_subdirectory(test)
endif()

View File

@@ -1,27 +0,0 @@
CC := gcc
CFLAGS := -ggdb -O0 -std=c99 -Wall -Wextra -Wpedantic
OBJS := obj/arena.o obj/alloc_backend.o
all : test/test_arena
test/test_arena : src/test_arena.c $(OBJS) | test
$(CC) -o $@ $(CFLAGS) $^
obj/%.o : src/%.c | obj
$(CC) -o $@ -c $(CFLAGS) $<
obj:
mkdir -p $@
bin:
mkdir -p $@
test:
mkdir -p $@
clean:
rm -rf obj bin test
.PHONY: clean obj test bin all

65
README.md Normal file
View File

@@ -0,0 +1,65 @@
# Arena Allocator
Arena allocators are region based allocators that tie many allocations to a
single region of memory. Benefits are massivly simplified allocation and
deallocation for complex structures, increased performance due to improved
cache locality, and reduced memory fragmentation as long as individual items
don't need to be deallocated. For programs that need to micro-manage individual
allocations this is not an ideal solution.
Deallocating a region of memory with arenas is extremely fast, because the
arena length is just set to 0. Allocating memory is also extremely fast. This
implementation also grows the arena on demand using mmap and mprotect.
Extending the library with a new type of allocation strategy should be easy.
# Reference
## `arena_t arena_new()`
Allocate a new arena.
If `arena_new()` fails, the struct member `data` is set to NULL. This can be
checked with `arena_new_failed()`
## `bool arena_new_failed(arena_t *a)`
Returns true if creating a new arena failed
## `int arena_delete(arena_t *a)`
Delete underlying buffer of arena. Should only be used with arenas from
`arena_new()`.
Returns 0 on success, -1 on failure
## `arena_t arena_attach(void *ptr, size_t size)`
Attach an arena to an existing memory region. The arena will not expand if
capacity is exceeded.
## `void *arena_detatch(arena_t arena)`
Detach an arena from an existing memory region. Returns the underlying data.
## `void arena_reset(arena_t *a)`
Resets an arena.
## `void *arena_alloc(arena_t *a, size_t len)`
Allocate memory with an arena. Returns NULL and sets errno on failure.
## `void *arena_calloc(arena_t *a, size_t nmemb, size_t size)`
Allocate and zero memory with an arena. Returns NULL and sets errno on
failure.

5
compile_flags.txt Normal file
View File

@@ -0,0 +1,5 @@
-Wall
-Wextra
-Wpedantic
-std=c2x
-Iinclude

81
include/arena.h Normal file
View File

@@ -0,0 +1,81 @@
#pragma once
#ifndef ARENA_H
#define ARENA_H
#include <stdbool.h>
#include <stddef.h>
// flags = 0 should be considered the default
enum arena_flags {
ARENA_GROW = 1 << 0,
ARENA_DONTALIGN = 1 << 1,
};
typedef struct arena {
char* data;
size_t size;
size_t cap;
char flags;
} arena_t;
/**
* Allocate a new arena.
* The underlying memory is allocated with mmap.
* Errors can be checked with `arena_new_failed()`
*/
arena_t arena_new();
/**
* Delete memory mapped for arena.
* Should only be used with arenas from arena_new().
* Returns 0 on success, -1 on failure
*/
int arena_delete(arena_t *a);
/**
* Attach an arena to an existing memory region.
* The arena will not expand the region if space is exceeded.
*/
static inline arena_t arena_attach(void *ptr, size_t size)
{
return (arena_t) { .data = ptr, .size = 0, .cap = size, .flags = 0 };
}
/**
* Detach an arena from an existing memory region.
*/
static inline void *arena_detatch(arena_t arena)
{
return arena.data;
}
/**
* Returns true if creating new arena failed
*/
static inline bool arena_new_failed(arena_t *a)
{
return a->data == NULL;
}
/**
* Reset an arena.
*/
static inline void arena_reset(arena_t *a)
{
a->size = 0;
}
/**
* Allocate memory from an arena.
* Returns NULL and sets errno on failure.
*/
void *arena_alloc(arena_t *a, size_t len);
/**
* Allocate and zero memory from an arena.
* Returns NULL and sets errno on failure.
*/
void *arena_calloc(arena_t *a, size_t nmemb, size_t size);
#endif

6
include/knob.h Normal file
View File

@@ -0,0 +1,6 @@
#pragma once
#define KNOB_MMAP_SIZE (1UL << 36UL)
#define KNOB_ALIGNMENT (sizeof(char*))

4
src/CMakeLists.txt Normal file
View File

@@ -0,0 +1,4 @@
add_library(arena arena.c)
target_include_directories(arena PUBLIC ../include)

View File

@@ -1,30 +0,0 @@
#include "alloc_backend.h"
#define _GNU_SOURCE
#include <sys/mman.h>
/*
* Separate function to make switching
* allocation backend easier
*
* must take size_t size as an argument
* must return NULL on failure
*/
void* call_alloc_backend(size_t size)
{
void *p = mmap(
NULL,
size,
PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE,
-1, /* man mmap(2): "[...], some implementations require fd to be
-1 if MAP_ANONYMOUS is specified [...]" */
0
);
if (p == MAP_FAILED)
return NULL;
return p;
}

View File

@@ -1,9 +0,0 @@
#ifndef ALLOC_BACKEND_H
#define ALLOC_BACKEND_H
#include <stddef.h>
void* call_alloc_backend(size_t size);
#endif

View File

@@ -1,60 +1,125 @@
#include "arena.h" #include "arena.h"
#include "alloc_backend.h" #include "knob.h"
#include <errno.h> #include <errno.h> // errno
#include <stdbool.h>
#include <stdio.h> // fprintf
#include <string.h> // strerror
#include <sys/mman.h>
#include <unistd.h> #include <unistd.h>
#include <string.h>
#define ARENA_SIZE ((size_t)(128*sysconf(_SC_PAGE_SIZE)))
/* #ifndef NDEBUG
* Allocates and returns new arena #define arena_err(msg) \
*/ fprintf(stderr, "%s (%s:%d): %s\n", msg, __func__, __LINE__, strerror(errno))
struct arena* arena_new() #else
#define arena_err(msg)
#endif
static bool arena_grow(struct arena *a, size_t min_size)
{ {
size_t size = ARENA_SIZE; if (!(a->flags & ARENA_GROW)) {
return false;
unsigned char *p = call_alloc_backend(size);
if (p == NULL)
return NULL;
struct arena a = {
.begin = p + sizeof(struct arena),
.next = p + sizeof(struct arena),
.cap = size
};
memcpy(p, &a, sizeof a);
return (struct arena*)p;
}
/*
* Frees all memory in arena
*/
void arena_reset(struct arena *a)
{
a->next = a->begin;
}
/*
* Allocate new memory using arena
*/
void* arena_alloc(struct arena *a, size_t len)
{
void *p = a->next;
a->next += len;
if (a->next - a->begin >= a->cap) {
errno = ENOMEM;
return NULL;
} }
size_t new_cap = a->cap * 2;
while (new_cap < min_size) {
new_cap *= 2;
}
int ok = mprotect(
a->data + a->cap,
new_cap - a->cap,
PROT_READ | PROT_WRITE
);
if (ok == -1) {
arena_err("mprotect");
return false;
}
a->cap = new_cap;
return true;
}
arena_t arena_new()
{
size_t size = sysconf(_SC_PAGE_SIZE);
if (size == -1) {
arena_err("sysconf");
goto sysconf_failed;
}
void *p = mmap(NULL, KNOB_MMAP_SIZE, PROT_NONE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (p == MAP_FAILED) {
arena_err("mmap");
goto mmap_failed;
}
int ok = mprotect(p, size, PROT_READ | PROT_WRITE);
if (ok == -1) {
arena_err("mprotect");
goto mprotect_failed;
}
struct arena a = {
.data = p,
.size = 0,
.cap = size,
.flags = ARENA_GROW,
};
return a;
mprotect_failed:
ok = munmap(p, KNOB_MMAP_SIZE);
if (ok == -1) {
arena_err("munmap");
}
mmap_failed:
sysconf_failed:
return (arena_t) { 0 };
}
void* arena_alloc(arena_t *a, size_t size)
{
// align
if (!(a->flags & ARENA_DONTALIGN)) {
size = (size + KNOB_ALIGNMENT - 1) & ~(KNOB_ALIGNMENT - 1);
}
void *p = a->data + a->size;
if (a->size + size > a->cap) {
if (!arena_grow(a, a->size))
return NULL;
}
a->size += size;
return p; return p;
} }
void* arena_calloc(arena_t *a, size_t nmemb, size_t size)
{
void *p = arena_alloc(a, nmemb * size);
if (p == NULL)
return p;
memset(p, 0, nmemb * size);
return p;
}
int arena_delete(struct arena *a)
{
if (!(a->flags & ARENA_GROW)) {
return -1;
}
int ok = munmap(a->data, KNOB_MMAP_SIZE);
if (ok == -1) {
arena_err("munmap");
return -1;
}
a->cap = -1;
a->size = -1;
return 0;
}

View File

@@ -1,19 +0,0 @@
#ifndef ARENA_H
#define ARENA_H
#include <stddef.h> // ptrdiff_t
struct arena {
unsigned char *begin,
*next;
ptrdiff_t cap;
};
struct arena* arena_new();
void arena_reset(struct arena *a);
void* arena_alloc(struct arena *a, size_t len);
#endif

View File

@@ -1,44 +0,0 @@
// _start test_arena_alloc.c
#include "arena.h"
#include <err.h>
#include <stdio.h>
#include <errno.h>
static struct arena *default_arena = NULL;
int main()
{
default_arena = arena_new();
if (default_arena == NULL) {
err(errno, "failed to allocate arena");
}
printf("\n%p\n", default_arena->next);
char *ten_A = arena_alloc(default_arena, 11 * sizeof *ten_A);
char *ten_B = arena_alloc(default_arena, 11 * sizeof *ten_B);
char *ten_C = arena_alloc(default_arena, 11 * sizeof *ten_C);
for (size_t i=0; i<10; ++i) {
ten_A[i] = 'A';
ten_B[i] = 'B';
ten_C[i] = 'C';
}
ten_A[10] = '\0';
ten_B[10] = '\0';
ten_C[10] = '\0';
printf("\n%s %s %s", ten_A, ten_B, ten_C);
printf("\n%p\n", default_arena->next);
arena_reset(default_arena);
printf("\n%p\n", default_arena->next);
return 0;
}

6
test/CMakeLists.txt Normal file
View File

@@ -0,0 +1,6 @@
include_directories(${CMAKE_SOURCE_DIR}/include)
add_executable(test_arena test_arena.c)
target_link_libraries(test_arena arena)

6
test/README Normal file
View File

@@ -0,0 +1,6 @@
run compiled test in conjunction with other tools, e.g.
/usr/bin/time strace -e trace=\!write ./test/test_arena

153
test/test_arena.c Normal file
View File

@@ -0,0 +1,153 @@
#include "arena.h"
#include "knob.h"
#include <signal.h>
#include <stdio.h> // fprintf
#include <stdlib.h> // exit, EXIT_FAILURE
#include <string.h> // memset
#include <sys/wait.h> // waitpid
#include <unistd.h> // sysconf
int main()
{
size_t page_size = sysconf(_SC_PAGE_SIZE);
if (page_size == -1) {
perror("sysconf");
exit(EXIT_FAILURE);
}
/*
* test arena from arena_new();
* ===============================
*/
{
fprintf(stderr, "creating new arena with arena_new()\n ");
arena_t a = arena_new();
if (arena_new_failed(&a)) {
fprintf(stderr, "arena_new failed\n");
exit(EXIT_FAILURE);
}
fprintf(stderr, "OK\n");
fprintf(stderr, "attempting many small allocations\n ");
for (int i = 0; i < page_size * 8; i++) {
char* s = arena_alloc(&a, 4 * sizeof *s);
if (!s) {
fprintf(stderr, "arena_alloc failed\n");
exit(EXIT_FAILURE);
}
memset(s, 'a', 4);
}
fprintf(stderr, "OK\n");
fprintf(stderr, "testing allocations of cap * 3 + 123\n ");
for (int i = 0; i < 2; i++) {
size_t n = a.cap * 3 + 123;
volatile char* s = arena_alloc(&a, n);
if (!s) {
fprintf(stderr, "arena_alloc failed\n");
exit(EXIT_FAILURE);
}
}
fprintf(stderr, "OK\n");
fprintf(stderr, "calling arena_delete() on arena from arena_new()\n ");
int ok = arena_delete(&a);
if (ok == -1) {
fprintf(stderr, "arena_delete failed\n");
exit(EXIT_FAILURE);
}
fprintf(stderr, "OK\n");
}
/*
* test arena made by arena_attach();
* ===============================
*/
{
fprintf(stderr, "creating new arena with arena_attach() and malloc()\n ");
char* p = malloc(page_size);
if (!p) {
perror("malloc");
exit(EXIT_FAILURE);
}
arena_t a = arena_attach(p, page_size);
fprintf(stderr, "OK\n");
bool failed = false;
fprintf(stderr, "try to grow more than the buffer size (should fail)\n ");
int i;
for (i=0; i < page_size + 1; i++) {
char* s = arena_alloc(&a, 8);
if (!s) {
failed = true;
break;
}
}
if (!failed) {
fprintf(stderr, "allocation was supposed to fail, but didn't\n");
exit(EXIT_FAILURE);
} else if (i != page_size / KNOB_ALIGNMENT ) {
fprintf(stderr, "allocation failed after %d allocations, but should fail after %d!\n", i, page_size/sizeof(void*));
exit(EXIT_FAILURE);
}
fprintf(stderr, "OK\n");
fprintf(stderr, "deleting an arena not made with arena_new (should fail)\n ");
int ok = arena_delete(&a);
if (ok != -1) {
fprintf(stderr, "arena_delete was supposed to fail, but didn't\n");
exit(EXIT_FAILURE);
}
fprintf(stderr, "OK\n");
free(arena_detatch(a));
}
/*
* test the memory protection
* ===============================
*/
{
fprintf(stderr, "attempting to access memory beyond arena cap (should sigsegv)\n ");
pid_t p;
switch (p = fork()) {
case -1: /* error */
perror("fork()");
exit(EXIT_FAILURE);
case 0: /* child */
arena_t a = arena_new();
if (arena_new_failed(&a)) {
fprintf(stderr, "arena_new failed\n");
exit(EXIT_FAILURE);
}
char* s = arena_alloc(&a, 12345);
if (!s) {
fprintf(stderr, "arena_alloc failed\n");
exit(EXIT_FAILURE);
}
// should sigsegv
a.data[a.cap] = 1;
exit(EXIT_SUCCESS);
}
int status;
int ok = waitpid(p, &status, 0);
if (!ok) {
perror("waitpid");
}
if (!( WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV)) {
fprintf(stderr, "allocating beyond arena cap succeeded when it shouldn't!\n");
exit(EXIT_FAILURE);
}
fprintf(stderr, "OK\n");
}
return EXIT_SUCCESS;
}