Compare commits

...

4 commits

2 changed files with 226 additions and 17 deletions

View file

@ -2,11 +2,13 @@
#define GC_MALLOC_H
#include <stdint.h>
#include <stdio.h>
#include <unistd.h>
// Generally, inline functions are preferable to macros resembling functions.
// https://www.kernel.org/doc/html/v4.10/process/coding-style.html#data-structures
#define ALIGN 8 /* Number of bytes to which chunk size is aligned */
#define OVERHEAD 1 /* Number of 8-byte segments for chunk size & flags */
#define MAGIC 2 /* Number of 8-byte segments from top of chunk to fields */
#define MIN 3 /* Minimum number of 8-byte segments for data in a chunk */
@ -14,38 +16,54 @@
/*
* Metadata and user data for allocated memory
*
* footer: user data for previous chunk
* footer: size of previous block or user data of previous block if allocated
* size: size of chunk in bytes
* merge: flag to identify when the chunk can be coalesced
* free: flag to identify when the chunk is free
* merge: flag to identify when the chunk can be coalesced,
* possible values 0 (false, don't merge) or non-zero (true, do merge)
* free: flag to identify when the chunk is free,
* possible values 0 (false, allocated) or non-zero (true, chunk is free)
* next: pointer to next chunk in the free list
* prev: pointer to previous chunk in the free list
*/
struct gc_chunk {
uint64_t footer;
uint32_t size;
uint16_t merge;
uint16_t free;
uint16_t merge; /* 1 = do merge, initialize to 0 */
uint16_t free; /* 0 = allocated, initialize to 1 */
struct gc_chunk *next;
struct gc_chunk *prev;
};
// Gets a pointer to the next chunk
struct gc_chunk *get_next_chunk(struct gc_chunk *curr);
struct gc_chunk *find_next_chunk(struct gc_chunk *curr);
// Gets a pointer to the previous chunk, ensuring that current can be coalesced
struct gc_chunk *get_prev_chunk(struct gc_chunk *curr);
struct gc_chunk *find_prev_chunk(struct gc_chunk *curr);
// Updates chunk footer information
// Updates chunk footer information (remember that the current chunk's footer
// data is actually part of the next chunk)
void set_footer(struct gc_chunk *curr);
// Gets footer information for the current chunk
int get_footer(struct gc_chunk *curr);
// Gets footer information for the current chunk - useful for debugging
uint64_t get_footer(struct gc_chunk *curr);
// Creates a new chunk with sentinel
struct gc_chunk *init_gc_chunk(void);
// Gets a pointer to a suitable chunk (first fit) from the free list, otherwise
// creates a new chunk and returns it's pointer
struct gc_chunk *find_free_chunk(size_t size);
// Coalesces chunks when merge flag is set
void merge_chunk(struct gc_chunk *curr);
// Performs allocations of SIZE bytes
void *gc_malloc(size_t size);
// Frees blocks of memory allocated by 'gc_alloc'
// Frees allocations for re-use
void gc_free(void *ptr);
// Completely de-allocates the heap (break glass in case of emergency)
void destroy_heap(struct gc_chunk *freelist);
#endif /* GC_MALLOC_H */

View file

@ -1,9 +1,18 @@
#include <assert.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include "gc_malloc.h"
/* Static Variables */
// Doubly linked list to keep track of free chunks
static struct gc_chunk *freelist = NULL;
/* Static Inline Functions */
// Hides chunk header
static inline void *hide(struct gc_chunk *curr)
{
@ -16,27 +25,209 @@ static inline struct gc_chunk *magic(void *mem)
return (struct gc_chunk *)((uint64_t *)mem - MAGIC);
}
// Aligns the amount of memory requested by the user with chunk size
static inline size_t align(size_t size)
/*
* Aligns the amount of memory requested by the user with chunk size
*
* Example: User requests 1 byte
* LHS
* requested size = 0b00000001
* alignment - 1 = 0b00000111
* => size + (alignment - 1) = 0b00001000
* RHS
* ~(alignment - 1) = 0b11111000
* LHS & RHS = 0b00001000
*/
// TODO: should probably be 16? would need to change chunk struct
static inline uint32_t align(uint32_t size)
{
const size_t alignment = 8;
size = (size + (alignment - 1)) & ~(alignment - 1);
size = (size + (ALIGN - 1)) & ~(ALIGN - 1);
// REVIEW: unnecessary with bit-wise math above?
if (size <= MIN)
return MIN;
else
return size;
}
struct gc_chunk *get_next_chunk(struct gc_chunk *curr)
/* Debug Functions */
// Useful for debugging after find or merge are called
static void chk_freelist(void)
{
struct gc_chunk *next = freelist;
struct gc_chunk *prev = NULL;
while (next != NULL) {
assert(next->free);
assert(next->merge == 0);
assert(next->size >= MIN);
assert(get_footer(next) == next->size);
assert(next->prev == prev);
struct gc_chunk *after = find_next_chunk(next);
assert(after->merge == 1);
assert(after->free == 0);
prev = next;
next = next->next;
}
}
/* gc_malloc Functions */
struct gc_chunk *find_next_chunk(struct gc_chunk *curr)
{
unsigned size = curr->size;
return (struct gc_chunk *)((uint64_t *)curr + MAGIC + (size - 1));
}
struct gc_chunk *get_prev_chunk(struct gc_chunk *curr)
struct gc_chunk *find_prev_chunk(struct gc_chunk *curr)
{
assert(curr->merge);
unsigned size = curr->footer;
return (struct gc_chunk *)((uint64_t *)curr - (size - 1) - MAGIC);
}
void set_footer(struct gc_chunk *curr)
{
struct gc_chunk *next = find_next_chunk(curr);
next->footer = curr->size;
}
uint64_t get_footer(struct gc_chunk *curr)
{
struct gc_chunk *next = find_next_chunk(curr);
return next->footer;
}
struct gc_chunk *init_gc_chunk(void)
{
const size_t len = getpagesize();
struct gc_chunk *chunk =
(struct gc_chunk *)mmap(NULL, len, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (chunk == MAP_FAILED)
return NULL;
chunk->size = len - MAGIC - OVERHEAD;
chunk->merge = 0;
chunk->free = 1;
chunk->next = NULL;
chunk->prev = NULL;
struct gc_chunk *sentinel = find_next_chunk(chunk);
sentinel->footer = chunk->size;
sentinel->size = 0;
sentinel->merge = 1;
sentinel->free = 1;
sentinel->next = NULL;
sentinel->prev = chunk;
chk_freelist(); // TODO: Remove this line after debugging
return chunk;
}
struct gc_chunk *find_free_chunk(size_t size)
{
// HACK: The first loop will run forever if we can never get a big enough chunk
const size_t page = getpagesize();
if (size > page) {
fprintf(stderr,
"ERROR: find_free_chunk: Requested size is larger than a page!");
return NULL;
}
struct gc_chunk *found;
while (1) {
found = freelist;
while (found != NULL) {
if (found->size >= size) {
// found, TODO: split when appropriate
// remaining block needs to be at least MIN + OVERHEAD
break;
}
found = found->next;
}
if (found == NULL) {
found = init_gc_chunk();
if (found == NULL) {
return found;
}
freelist = found;
break;
} else {
break;
}
}
return found;
}
// FIXME: need to clean up todos before this will work
void merge_chunk(struct gc_chunk *curr)
{
if (curr->merge) {
struct gc_chunk *prev = find_prev_chunk(curr);
chk_freelist(); // TODO: Remove this line after debugging
struct gc_chunk *next = find_next_chunk(curr);
chk_freelist(); // TODO: Remove this line after debugging
if (next->free) {
// TODO Case 1: merge prev and next
} else {
// TODO Case 2: merge with prev
}
set_footer(prev);
} else {
struct gc_chunk *next = find_next_chunk(curr);
chk_freelist(); // TODO: Remove this line after debugging
if (next->free) {
// TODO Case 3: merge with after
} else {
// TODO Case 4: can't merge anything
}
curr->free = 1;
set_footer(curr);
}
}
void *gc_malloc(size_t size)
{
if (size == 0)
return NULL;
uint32_t bytes = align(size);
struct gc_chunk *curr = find_free_chunk(bytes);
chk_freelist(); // TODO: Remove this line after debugging
if (curr == NULL)
return curr;
return hide(curr);
}
void gc_free(void *ptr)
{
if (ptr != NULL) {
struct gc_chunk *curr = magic(ptr);
merge_chunk(curr);
chk_freelist(); // TODO: Remove this line after debugging
}
}
void destroy_heap(struct gc_chunk *freelist)
{
if (freelist == NULL) {
fprintf(stderr, "WARN: destroy_heap: Free list is empty?");
return;
}
struct gc_chunk *curr = freelist;
while (curr->size != 0) {
struct gc_chunk *next = curr->next;
munmap(curr, curr->size);
curr = next;
}
}