diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f0c1d2..d325afd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,9 +38,9 @@ endif () set(CMAKE_C_STANDARD 11) -add_executable(CustomHeapManager main.c) +add_executable(CustomHeapManager + src/CHM.c include/CHM.h + src/main.c +) -if (IS_OS_LINUX) - target_link_options(CustomHeapManager PUBLIC) -elseif (IS_OS_WINDOWS) -endif () \ No newline at end of file +target_include_directories(CustomHeapManager PUBLIC ${PROJECT_SOURCE_DIR}/include) \ No newline at end of file diff --git a/include/CHM.h b/include/CHM.h new file mode 100644 index 0000000..f0111f6 --- /dev/null +++ b/include/CHM.h @@ -0,0 +1,42 @@ +#pragma once + +#include +#include +#include +#include +#include + +#define ALIGN4(s) (((((s)-1)>>2)<<2)+4) +#define ALIGN16(s) (((s) + 15) & ~0x0F) +#define ALIGNMENT 4 +#define BLOCK_SIZE sizeof(struct Block) +#define MINIMUM_BLOCK_SIZE (sizeof(struct Block) + 16) + +enum MemMode : unsigned char +{ + MEM_MODE_SPEED, // Memory will be allocated more quickly but loses memory efficiency. + MEM_MODE_EFFICIENCY, // Memory will be allocated more slowly but gains memory efficiency. + MEM_MODE_BALANCED // Memory will be allocated as quickly as possible but also for the sake of memory efficiency. +}; + +void set_memory_mode(enum MemMode new_mode); + +enum MemMode get_memory_mode(); + +/// Will find or allocate a memory block appropriately. +/// @param [in] size The size of the memory block to request. +/// @returns The allocated memory address on the heap. +void *malloc(size_t size); + +void *realloc(void *ptr, size_t newSize); + +/// Will flag the provided memory as free and will defragment other blocks adjacent to it. +/// @param [in] ptr The memory to flag as free. +void free(void *ptr); + +/// Will retrieve the current real byte size of the heap. +/// @returns The size in bytes. +size_t get_heap_size(); + +/// Will check memory blocks on program exit if there's dangling memory. +void check_memory(); \ No newline at end of file diff --git a/main.c b/main.c deleted file mode 100644 index fba46ac..0000000 --- a/main.c +++ /dev/null @@ -1,280 +0,0 @@ -#include -#include -#include -#include - -#define ALIGN16(s) (((s) + 15) & ~0x0F) -#define BLOCK_SIZE sizeof(struct block) -#define MINIMUM_BLOCK_SIZE (sizeof(struct block) + 16) - -/// The memory block's header. -struct block -{ - size_t size; - struct block *prev; - struct block *next; - int free; -}; - -struct block *first = NULL; -struct block *last = NULL; - -/// Extends heap memory upwards, towards zero. -/// @param [in] s The size of the memory needed aligned by 4 bytes. -/// @returns The new memory block. -struct block *extend_heap(size_t s) -{ - // Ensure the allocated size is at least the minimum block size - if (s < MINIMUM_BLOCK_SIZE) - s = MINIMUM_BLOCK_SIZE; - - struct block *b = (struct block *)sbrk(0); // Get the current break - - // Add the size of the block header to the requested size - s += BLOCK_SIZE; - - if (sbrk(s) == (void *)-1) // Extend the break by s bytes - return NULL; - - b->size = s; - b->prev = last; - b->next = NULL; - b->free = 0; - - if (last) - last->next = b; - - last = b; - - return b; -} - -/// Finds the first block that will fit the given size. -/// @param [in] s The 4 byte aligned size to look for. -/// @returns The matching available memory block. -struct block *find_first(size_t s) -{ - struct block *current = first; - while (current && (!current->free || current->size < s)) - current = current->next; - - return current; -} - -/// Fragments an existing free memory block into the given size. -/// @param [in] in The memory block to fragment. -/// @param [in] s The size of the new memory block. -/// @returns The new memory block. -struct block *fragment_block(struct block *in, size_t s) -{ - // Calculate the size of the new block, including the block header - size_t newBlockSize = s + BLOCK_SIZE; - - // Check if the current block can be split - if (in->size <= newBlockSize) - { - // Cannot split, return the original block - return in; - } - - // Calculate the size of the remainder block - size_t remainderSize = in->size - newBlockSize; - - // Create the new block in the remainder space - struct block *newBlock = (struct block *)((char *)(in + 1) + s); - newBlock->size = remainderSize; - newBlock->prev = in; - newBlock->next = in->next; - newBlock->free = 1; // Set the new block as free - - // Update the current block to reflect the reduced size - in->size = newBlockSize; - in->next = newBlock; - - // Insert the new block into the linked list of blocks - if (newBlock->next) - { - newBlock->next->prev = newBlock; - } - - return newBlock; -} - -struct block *find_best_fit(size_t s) -{ - struct block *current = first; - struct block *best_fit = NULL; - while (current) - { - if (current->free && current->size >= s) - { - if (best_fit && current->size < best_fit->size) // Check for NULL before comparison - { - { - best_fit = current; - } - } - current = current->next; - } - return best_fit; - } -} - -/// Will find or allocate a memory block. -/// @param [in] size The size of the memory block to request. -/// @returns The requested memory on the heap. -/// @todo Fragmenting functionality. -void *malloc(size_t size) -{ - if (size == 0) - { - return NULL; - } - - size = ALIGN16(size); // First align the requested size - size_t total_size = size + BLOCK_SIZE; // Then add the size of the block header - - struct block *b; - - if (first) - { - b = find_best_fit(size); - if (!b) - { - b = extend_heap(total_size); - if (!b) - { - return NULL; // Check if heap extension failed - } - } - else if (b->size > total_size + MINIMUM_BLOCK_SIZE) - { - b = fragment_block(b, size); - } - } - else - { - b = extend_heap(total_size); - if (!b) - { - return NULL; - } - first = b; - } - - b->free = 0; // Mark the block as used - return (char *)b + BLOCK_SIZE; // Return a pointer to the usable memory -} - -void *realloc(void *ptr, size_t new_size) -{ - if (!ptr) - { - return malloc(new_size); - } - - if (new_size == 0) - { - free(ptr); - return NULL; - } - - struct block *b = (struct block *)((char *)ptr - BLOCK_SIZE); - if (b->size >= new_size + BLOCK_SIZE) - { - return ptr; // The block is already big enough - } - - void *new_ptr = malloc(new_size); - if (!new_ptr) - { - return NULL; // Allocation failed - } - memcpy(new_ptr, ptr, b->size - BLOCK_SIZE); // Copy old data to new block, excluding the header size - free(ptr); // Free the old block - - return new_ptr; -} - -/// Will flag the provided memory as free and will defragment other blocks adjacent to it. -/// @param [in] ptr The memory to flag as free. -/// @note If all data after the provided memory is free, it will reduce the heap size. -void my_custom_free(void *ptr) -{ - if (!ptr) - { - return; - } - - struct block *b = (struct block *)((char *)ptr - BLOCK_SIZE); - if (b->free) - { - fprintf(stderr, "Double free detected at block %p.\n", ptr); - abort(); // Terminate the program immediately due to serious error - } - - b->free = 1; - - // Coalesce free blocks - while (b->prev && b->prev->free) - { - // Merge with previous block - b->prev->size += BLOCK_SIZE + b->size; - b->prev->next = b->next; - b = b->prev; - } - - // If there is a next block and it's free, merge with it - if (b->next && b->next->free) - { - b->size += BLOCK_SIZE + b->next->size; - b->next = b->next->next; - if (b->next) - { - b->next->prev = b; - } - } - - // After merging, update the 'last' pointer if necessary - if (!b->next) - { - last = b; - } - - // Check if we can shrink the heap - if (b == last) - { - // Update 'last' to the previous block or NULL if there's no previous block - last = b->prev; - if (last) - { - last->next = NULL; - } - else - { - first = NULL; - } - // Reduce the program break to release the memory - sbrk(0 - (b->size + BLOCK_SIZE)); - } -} - -int main() -{ - int *a = (int *)malloc(sizeof(int)); - int *b = (int *)malloc(sizeof(int)); - - *a = 5; - *b = 12; - - printf("Test 1: %i\n", *a); - printf("Test 2: %i\n", *b); - printf("Heap Size: %zu Bytes\n", get_heap_size()); - - free(a); - free(b); - - int *c = (int *)malloc(sizeof(int)); - - return 0; -} \ No newline at end of file diff --git a/src/CHM.c b/src/CHM.c new file mode 100644 index 0000000..48eb8ac --- /dev/null +++ b/src/CHM.c @@ -0,0 +1,385 @@ +#include "CHM.h" + +#include +#include +#include +#include +#include + +/// The memory block's header. +struct Block +{ + size_t size; + struct Block *prev; + struct Block *next; + int free; +}; + +enum MemMode mode = MEM_MODE_BALANCED; +struct Block *first = NULL; +struct Block *last = NULL; + +/// Sets the memory mode for the custom heap manager. +/// @param [in] new_mode The new mode to use. +void set_memory_mode(const enum MemMode new_mode) +{ + if (new_mode > MEM_MODE_BALANCED) + { + printf("The given memory mode, \"%i\", is invalid.", new_mode); + return; + } + + mode = new_mode; +} + +/// Gets the current memory mode for the custom heap manager. +/// @returns The current mode. +enum MemMode get_memory_mode() +{ + return mode; +} + +/// Extends heap memory upwards, towards zero. +/// @param [in] s The size of the memory needed aligned by 4 bytes. +/// @returns The new memory block. +struct Block *extend_heap(const size_t s) +{ + struct Block* b = (struct Block *)syscall(SYS_brk, NULL); + + if ((void *)syscall(SYS_brk, (char *)(b + 1) + s) == (void *)-1) + return NULL; + + b->size = s; + b->prev = last; + b->next = NULL; + b->free = 0; + + if (last) + last->next = b; + + last = b; + + return b; +} + +struct Block *extend_block(struct Block *in, const size_t newSize) +{ + if (!in->next) + { + void *addr = (struct Block *)syscall(SYS_brk, NULL); + + if ((void *)syscall(SYS_brk, (char *)addr + (newSize - in->size)) == (void *)-1) + return NULL; + + in->size = newSize; + + return in; + } + + size_t totalSize = in->size; + struct Block *start = in->prev; + struct Block *end = in->next; + + while (start && start->free && totalSize < newSize) + { + totalSize += BLOCK_SIZE + in->prev->size; + start = in->prev; + } + + while (end && end->free && totalSize < newSize) + { + totalSize += BLOCK_SIZE + end->size; + end = end->next; + } + + if (totalSize < newSize || (mode == MEM_MODE_EFFICIENCY && totalSize > newSize)) + return NULL; + + if (!start) + start = in; + + if (!end) + end = in; + + struct Block *b = start->next; + while (b) + { + start->size += BLOCK_SIZE + b->size; + b = b->next; + } + + start->next = end->next; + start->free = 0; + + if (!start->next) + last = start; + + return start; +} + +/// Will defragment adjacent memory blocks of the given memory block. +/// @param [in] in The memory block to defragment for. +void defragment_adjacent_blocks(struct Block *in) +{ + // Go in reverse order to find free memory blocks. + while (in->prev && in->prev->free) + in = in->prev; + + // Go in forward order to find memory blocks and merge them. + while (in->next && in->next->free) + { + in->size += BLOCK_SIZE + in->next->size; + in->next = in->next->next; + } +} + +/// Fragments an existing free memory block into the given size. +/// @param [in] in The memory block to fragment. +/// @param [in] s The size of the new memory block. +void fragment_block(struct Block *in, size_t s) +{ + // Create the new block in the remainder space + struct Block *newBlock = (struct Block *)((char *)(in + 1) + s); + newBlock->size = in->size - BLOCK_SIZE - s; + newBlock->prev = in; + newBlock->next = in->next; + newBlock->free = 1; + + // Update the current block to reflect the reduced size + in->size = s; + in->next = newBlock; + in->free = 0; +} + +/// Finds the first block that will fit the given size. +/// @param [in] s The memory aligned size to look for. +/// @returns The matching available memory block. +struct Block *find_first(const size_t s) +{ + const size_t totalSize = BLOCK_SIZE + s; + struct Block *current = first; + while (current) + { + if (!current->free || current->size < s) + { + current = current->next; + continue; + } + + if (current->size >= s) + break; + + current = current->next; + } + + return current; +} + +/// Finds the first memory block with more ore equal to the given size and fragments it if necessary. +/// @param [in] s The memory aligned size to look for. +/// @returns The matching available memory block. +struct Block *find_balanced(const size_t s) +{ + const size_t totalSize = BLOCK_SIZE + s; + struct Block *current = first; + while (current) + { + if (!current->free || current->size < s) + { + current = current->next; + continue; + } + + if (current->size == s) + break; + + const size_t remainder = current->size - totalSize; + if (!(remainder % ALIGNMENT)) + { + fragment_block(current, s); + break; + } + + current = current->next; + } + + return current; +} + +/// Finds the best fitting memory block with the given size and fragments it if necessary. +/// @param [in] s The memory aligned size to look for. +/// @returns The matching available memory block. +struct Block *find_best_fit(const size_t s) +{ + const size_t totalSize = BLOCK_SIZE + s; + unsigned char fragment = 0; + struct Block *best = NULL; + struct Block *current = first; + + while (current) + { + if (!current->free || current->size < s) + { + current = current->next; + continue; + } + + if (current->size == s) + { + fragment = 0; + best = current; + break; + } + + if (best) + { + if (current->size < best->size && !((current->size - totalSize) % ALIGNMENT)) + { + fragment = 1; + best = current; + } + } + else + { + if (!((current->size - totalSize) % ALIGNMENT)) + { + fragment = 1; + best = current; + } + } + + current = current->next; + } + + if (best && fragment) + fragment_block(best, s); + + return best; +} + +void *malloc(size_t size) +{ + if (!size) + return NULL; + + size = ALIGN16(size); + + struct Block *b; + + if (first) + { + switch (mode) + { + case MEM_MODE_SPEED: + b = find_first(size); + break; + case MEM_MODE_EFFICIENCY: + b = find_best_fit(size); + break; + case MEM_MODE_BALANCED: + b = find_balanced(size); + break; + } + + if (!b) + { + b = extend_heap(size); + if (!b) + return NULL; + } + } + else + { + b = extend_heap(size); + if (!b) + return NULL; + + first = b; + last = b; + } + + return b + 1; +} + +void *realloc(void *ptr, size_t newSize) +{ + if (!newSize) + return NULL; + + if (!ptr) + return malloc(newSize); + + newSize = ALIGN16(newSize); + + struct Block *b = ((struct Block *)ptr) - 1; + if (b->free) + return NULL; + + size_t totalSize = BLOCK_SIZE + newSize; + size_t remainder = b->size - totalSize; + + if (b->size < newSize && !extend_block(b, newSize)) + return NULL; + else if (b->size > newSize && !(remainder % ALIGNMENT)) + fragment_block(b, newSize); + else + return ptr; + + return b + 1; +} + +void free(void *ptr) +{ + if (!ptr) + return; + + struct Block *b = (struct Block *)ptr - 1; + if (b->free) + { + fprintf(stderr, "Double free detected for the address %p.\n", ptr); + return; + } + + b->free = 1; + + if (mode != MEM_MODE_SPEED) + defragment_adjacent_blocks(b); + + if (!b->next) + { + if (!b->prev) + { + first = NULL; + last = NULL; + } + else + last = b; + + syscall(SYS_brk, b); + } +} + +size_t get_heap_size() +{ + if (!first || !last) + return 0; + + return (char*)first - (char*)last - last->size; +} + +void check_memory() +{ + if (!first || !last) + return; + + struct Block *current = first; + while (current) + { + if (current->free) + printf("Memory at the address %p has been marked free, but still persists.", current + 1); + else + printf("Memory at the address %p has not been marked free. Possible memory leak?", current + 1); + + current = current->next; + } +} \ No newline at end of file diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..f25db69 --- /dev/null +++ b/src/main.c @@ -0,0 +1,23 @@ +#include "CHM.h" + +int main() +{ + int *a = (int *)malloc(sizeof(int)); + int *b = (int *)malloc(sizeof(int)); + + *a = 5; + *b = 12; + + printf("Test 1: %i\n", *a); + printf("Test 2: %i\n", *b); + printf("Heap Size: %zu Bytes\n", get_heap_size()); + + free(a); + free(b); + + int *c = (int *)malloc(sizeof(int)); + + check_memory(); + + return 0; +} \ No newline at end of file