Skip to content

Instantly share code, notes, and snippets.

@lemnik
Created November 17, 2021 07:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save lemnik/874d313aeef37017961ae8e0c5124669 to your computer and use it in GitHub Desktop.
Save lemnik/874d313aeef37017961ae8e0c5124669 to your computer and use it in GitHub Desktop.
mmap with some housekeeping
#include <unistd.h>
#include <sys/mman.h>
// MEMORY_SANITIZER checks shamelessly stolen from Breakpad: src/common/memory_allocator.h
#if defined(MEMORY_SANITIZER)
#include <sanitizer/msan_interface.h>
#endif
#include "sigalloc.h"
/*
* We allocate at minimum 1 page at a time, and every allocation is aligned to a page. We call each group of pages
* allocated together a "chunk".
*/
typedef struct {
size_t page_count;
void *next_chunk;
} chunk_header;
static void *allocate_pages(struct sig_allocator *allocator, size_t page_count) {
void *new_chunk = mmap(NULL, allocator->page_size * page_count, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if(new_chunk == MAP_FAILED) {
return NULL;
}
#if defined(MEMORY_SANITIZER)
// We need to indicate to MSan that memory allocated through sys_mmap is initialized
__msan_unpoison(new_pages, allocator->page_size * page_count);
#endif
chunk_header *new_page_header = new_chunk;
new_page_header->next_chunk = allocator->current_chunk;
new_page_header->page_count = page_count;
allocator->page_count += page_count;
allocator->current_chunk = new_chunk;
return new_chunk;
}
void *sig_alloc(struct sig_allocator *allocator, const size_t size) {
if(!size) {
return NULL;
}
if(allocator->current_chunk) {
chunk_header *current_page_header = allocator->current_chunk;
// do we have space in the current page group?
if(allocator->next_alloc_offset + size + sizeof(chunk_header) < current_page_header->page_count * allocator->page_size) {
void *ptr = allocator->current_chunk + sizeof(chunk_header) + allocator->next_alloc_offset;
allocator->next_alloc_offset += size;
return ptr;
}
}
// we either don't have a valid page, or we don't have space in the current page group - so we ask for more
const size_t requested_page_count = (size + sizeof(chunk_header) + allocator->page_size - 1) / allocator->page_size;
void *new_pages = allocate_pages(allocator, requested_page_count);
if(!new_pages) {
return NULL;
}
allocator->next_alloc_offset = size;
return new_pages + sizeof(chunk_header);
}
void init_sig_allocator(struct sig_allocator *allocator) {
allocator->current_chunk = NULL;
allocator->page_count = 0;
allocator->page_size = sysconf(_SC_PAGESIZE);
allocator->next_alloc_offset = 0;
}
void destroy_sig_allocator(struct sig_allocator *allocator) {
void *current_chunk = allocator->current_chunk;
while(current_chunk) {
chunk_header *header = current_chunk;
// grab the next page *before* we unmap
current_chunk = header->next_chunk;
munmap(header, header->page_count * allocator->page_size);
}
allocator->current_chunk = NULL;
allocator->page_count = 0;
}
int sig_alocator_owns(struct sig_allocator *allocator, void *ptr) {
void *chunk = allocator->current_chunk;
while(chunk) {
chunk_header *header = chunk;
void *base = chunk + sizeof(chunk_header);
if(ptr > base && ptr < base + (header->page_count * allocator->page_size)) {
return 1; // this pointer is on one of our pages!
}
chunk = header->next_chunk;
}
return 0; // looked at all our pages, and couldn't find the pointer - must be someone elses
}
#ifndef SIGALLOC_ALLOCATOR_H
#define SIGALLOC_ALLOCATOR_H
#include <stddef.h>
/**
* A signal-safe allocator structure. None of these fields should be used directly.
*
* Having the allocators as a struct instead of as globals allows several allocators to be "active" in a single process.
* In keeping with the "signal safe" theme, this allows safe allocation when there are "signals on top of signals", or multiple signals being handled
* concurrently by multiple threads.
*/
struct sig_allocator {
/**
* The current chunk used by this allocator. This points to the header of the chunk and should not be used directly.
*/
void *current_chunk;
/**
* The number of kernel-sized pages currently used by this allocator.
*/
size_t page_count;
/**
* The size of the pages requested by this allocator. This defaults to the same size as the kernel, and should
* not be changed by externnal code.
*/
int page_size;
/**
* The offset of the next allocation within the *usable* page. This does *not* take the page header into consideration
* and so cannot be directly used externally.
*/
size_t next_alloc_offset;
};
/**
* Similar to `malloc` but signal-safe and for a specific `sig_allocator`.
*/
void *sig_alloc(struct sig_allocator *allocator, const size_t size);
/**
* Initialize the given sig_allocator. Memory pages are only requested when the first call to [sig_alloc] is made.
*/
void init_sig_allocator(struct sig_allocator *allocator);
/**
* Destroy a given sig_allocator *and all* of it's associated data. This has the same effect as `free`ing every pointer
* allocated by the given allocator, and using any of those pointers further will likely result in a segmentation fault (SIGSEGV).
*/
void destroy_sig_allocator(struct sig_allocator *allocator);
/**
* Returns non-zero if the given pointer is owned by the given sig_allocator. If true: destroying the allocator will invalidate the given pointer.
*/
int sig_alocator_owns(struct sig_allocator *allocator, void *ptr);
#endif // SIGALLOC_ALLOCATOR_H
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment