diff --git a/Makefile b/Makefile index 1c88b02..3ff3568 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,18 @@ CFLAGS=-Wall -Wextra -Werror -std=c11 -pedantic -ggdb -heap: main.c heap.c heap.h - $(CC) $(CFLAGS) -o heap main.c heap.c +# Default target +all: clean heap + +# Target for building heap with heap1.c +chunk: main.c heap_using_chunk_lists.c heap.h chunk_lists.h + $(CC) $(CFLAGS) -o heap main.c heap_using_chunk_lists.c logger.c + +# Target for building heap with heap2.c +linkedlist: main.c heap_using_linked_lists.c heap.h logger.c logger.h linked_list.h + $(CC) $(CFLAGS) -o heap main.c heap_using_linked_lists.c logger.c + +# Target for cleaning up +clean: + rm -f heap + +.PHONY: all heap1 heap2 clean diff --git a/README.md b/README.md index d5b01f0..b6f0fda 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,15 @@ ## Quick Start +### To run original implementation ```console -$ make +$ make chunk +$ ./heap +``` + +### Alternative Implementation +```console +$ make linkedlist $ ./heap ``` diff --git a/chunk_lists.h b/chunk_lists.h new file mode 100644 index 0000000..4068cf7 --- /dev/null +++ b/chunk_lists.h @@ -0,0 +1,37 @@ +#ifndef CHUNK_LISTS_H_ +#define CHUNK_LISTS_H_ + +#include +#include +#include + +#define UNIMPLEMENTED \ + do { \ + fprintf(stderr, "%s:%d: %s is not implemented yet\n", \ + __FILE__, __LINE__, __func__); \ + abort(); \ + } while(0) + +#define CHUNK_LIST_CAP 1024 + +typedef struct { + uintptr_t *start; + size_t size; +} Chunk; + +typedef struct { + size_t count; + Chunk chunks[CHUNK_LIST_CAP]; +} Chunk_List; + +extern Chunk_List alloced_chunks; +extern Chunk_List freed_chunks; +extern Chunk_List tmp_chunks; + +void chunk_list_insert(Chunk_List *list, void *start, size_t size); +void chunk_list_merge(Chunk_List *dst, const Chunk_List *src); +void chunk_list_dump(const Chunk_List *list, const char *name); +int chunk_list_find(const Chunk_List *list, uintptr_t *ptr); +void chunk_list_remove(Chunk_List *list, size_t index); + +#endif //CHUNK_LISTS_H_ \ No newline at end of file diff --git a/heap.h b/heap.h index 9aec6d0..daba9fe 100644 --- a/heap.h +++ b/heap.h @@ -5,13 +5,6 @@ #include #include -#define UNIMPLEMENTED \ - do { \ - fprintf(stderr, "%s:%d: %s is not implemented yet\n", \ - __FILE__, __LINE__, __func__); \ - abort(); \ - } while(0) - #define HEAP_CAP_BYTES 640000 static_assert(HEAP_CAP_BYTES % sizeof(uintptr_t) == 0, "The heap capacity is not divisible by " @@ -24,27 +17,6 @@ extern const uintptr_t *stack_base; void *heap_alloc(size_t size_bytes); void heap_free(void *ptr); void heap_collect(); +void run_tests(void); -#define CHUNK_LIST_CAP 1024 - -typedef struct { - uintptr_t *start; - size_t size; -} Chunk; - -typedef struct { - size_t count; - Chunk chunks[CHUNK_LIST_CAP]; -} Chunk_List; - -extern Chunk_List alloced_chunks; -extern Chunk_List freed_chunks; -extern Chunk_List tmp_chunks; - -void chunk_list_insert(Chunk_List *list, void *start, size_t size); -void chunk_list_merge(Chunk_List *dst, const Chunk_List *src); -void chunk_list_dump(const Chunk_List *list, const char *name); -int chunk_list_find(const Chunk_List *list, uintptr_t *ptr); -void chunk_list_remove(Chunk_List *list, size_t index); - -#endif // HEAP_H_ +#endif // HEAP_H_ \ No newline at end of file diff --git a/heap.c b/heap_using_chunk_lists.c similarity index 71% rename from heap.c rename to heap_using_chunk_lists.c index a0fc33a..45c7803 100644 --- a/heap.c +++ b/heap_using_chunk_lists.c @@ -1,7 +1,9 @@ + #include #include #include -#include "./heap.h" +#include "heap.h" +#include "chunk_lists.h" uintptr_t heap[HEAP_CAP_WORDS] = {0}; const uintptr_t *stack_base = 0; @@ -162,3 +164,84 @@ void heap_collect() heap_free(to_free[i]); } } + +/*-------------------------MAIN FUNCTION IMPLEMENTATION-------------------------------*/ + +#define JIM_IMPLEMENTATION +#include "jim.h" + +typedef struct Node Node; + +struct Node { + char x; + Node *left; + Node *right; +}; + +Node *generate_tree(size_t level_cur, size_t level_max) +{ + if (level_cur < level_max) { + Node *root = heap_alloc(sizeof(*root)); + assert((char) level_cur - 'a' <= 'z'); + root->x = level_cur + 'a'; + root->left = generate_tree(level_cur + 1, level_max); + root->right = generate_tree(level_cur + 1, level_max); + return root; + } else { + return NULL; + } +} + +void print_tree(Node *root, Jim *jim) +{ + if (root != NULL) { + jim_object_begin(jim); + + jim_member_key(jim, "value"); + jim_string_sized(jim, &root->x, 1); + + jim_member_key(jim, "left"); + print_tree(root->left, jim); + + jim_member_key(jim, "right"); + print_tree(root->right, jim); + + jim_object_end(jim); + } else { + jim_null(jim); + } +} + +#define N 10 + +void *ptrs[N] = {0}; + +void run_tests(void) +{ + stack_base = (const uintptr_t*)__builtin_frame_address(0); + + for (size_t i = 0; i < 10; ++i) { + heap_alloc(i); + } + + Node *root = generate_tree(0, 3); + + printf("root: %p\n", (void*)root); + + Jim jim = { + .sink = stdout, + .write = (Jim_Write) fwrite, + }; + + print_tree(root, &jim); + + printf("\n------------------------------\n"); + heap_collect(); + chunk_list_dump(&alloced_chunks, "Alloced"); + chunk_list_dump(&freed_chunks, "Freed"); + printf("------------------------------\n"); + root = NULL; + heap_collect(); + chunk_list_dump(&alloced_chunks, "Alloced"); + chunk_list_dump(&freed_chunks, "Freed"); +} diff --git a/heap_using_linked_lists.c b/heap_using_linked_lists.c new file mode 100644 index 0000000..86a9ce3 --- /dev/null +++ b/heap_using_linked_lists.c @@ -0,0 +1,478 @@ +#include +#include +#include +#include "./heap.h" +#include "./linked_list.h" +#include +#include "logger.h" +#include + +uintptr_t heap[HEAP_CAP_WORDS] = {0}; +Node* initializeHeadNode(void); +size_t convertBytesToWords(size_t size_in_byprint_allocationstes); + +/* +The intention is as below: +1) Have a linked list to manage heap allocations - having linked list sorted will make the search +O(log2(n)) - this will keep Allocated Slots. Important note is linked list will hold empty spaces... +2) Important Notes about the linked list: +- Obviously since we are re-writing malloc function - we need to have a way to initialize the +linked list - as a start we will start with an array. As second stage, we would allocate the +linked list in the allocated memory for the heap. +- How are we going to track the allocation of the linked list - since it will be a list +implemented on a memory array. We will need a fixed size array (int/bool) to track the allocation +status of the list. -> by index (again considering this is implemented on the memory array) +3) When allocating memory segments (this would remove/modify a linked list node), +there can be 2 strategies to follow: +- Return first available time slot same or greater than required +- Return the time slot that would optimally fit the request -> this will not be implemented, as +would require O(n) with planned data structures. +4) When free'ing memory slots (this would add/modify a node) +- a new node would be added to the linked list. +- at first stage the node will be added +- as second stage, adjacent nodes would be checked, if possible those will be merged. + +IMPORTANT NOTE: AIM IS TO MOVE THE DATA STRUCTURE ALLOCATION TO PART OF THE HEAP +ARRAY - SO WE HAVE AN ALTOGERHER ALLOCATION MANAGEMENT... +THIS WILL BE DONE AS SECOND STAGE...*/ + +/* ------------------------------ HEAP FUNCTIONS --------------------------------*/ +void *heap_alloc(size_t size_bytes) +{ + //Validation 1 - size should be greater than zero + if(size_bytes <= 0) + { + logToConsole(Information, "Invalid request, size should be greater than or equal to zero.\n"); + + return NULL; + } + + //Validation 2 - we shouldn't have more than Max + if(list.count_allocated >= MAX_CONCURRENT_ALLOCATIONS) + { + logToConsole(Warning, "Currently there are %d allocations - we cannot have more than that\n", list.count_allocated); + return NULL; + } + + size_t size_in_words = convertBytesToWords(size_bytes); + + Node* node = findNode(&list, size_in_words); + + //Validation 3 - check if any node could be allocated + if(node == NULL) + { + logToConsole(Error, "Memory with requested size doesn't exit.\n"); + return NULL; + } + + logToConsole(Debug, "Found memory in node below: \n"); + printNode(node); + + //check for exast match + if(node->size_in_words == size_in_words) + { + logToConsole(Debug, "Node size %d is same as requested %d\n", (int)node->size_in_words, (int)size_in_words); + node->is_allocated = true; + list.count_allocated++; + list.count_de_allocated--; + return node; + } + + //here it's clear that node size is greater than required + //thus we need to split it into two + //we would need to split the node into 2... + + Node* emptyNode = allocateNode(); + + if(emptyNode == NULL) + { + logToConsole(Error, "Error during memory allocation"); + return NULL; + } + + logToConsole(Debug, "Will use following node to accommodate new allocation: \n"); + printNode(emptyNode); + + emptyNode->is_allocated = true; + + emptyNode->next = node->next; + emptyNode->previous = node; + emptyNode->size_in_words = size_in_words; + emptyNode->start = node->start + (node->size_in_words - size_in_words); + + if(emptyNode->next != NULL) + emptyNode->next->previous = emptyNode; + + node->size_in_words = node->size_in_words - size_in_words; + node->next = emptyNode; + list.count_allocated++; + list.count_total++; + + return emptyNode->start; +} + +size_t convertBytesToWords(size_t size_in_bytes) +{ + size_t words = (size_t)size_in_bytes / (sizeof(uintptr_t)); + + if(size_in_bytes % sizeof(uintptr_t) != 0) + words++; + + return words; +} + +void heap_free(void *ptr) +{ + Node* node = findNodeByPointer(&list, ptr); + + if(node == NULL) + { + logToConsole(Error, "Provided pointer is not valid. \n"); + return; + } + + if(deAllocateNode(node) == false) + { + logToConsole(Error, "Failed to de-allocate the node\n"); + return; + } + + logToConsole(Debug, "De-allocating node:\n"); + printNode(node); + logToConsole(Debug, "Previous: \n"); + printNode(node->previous); + logToConsole(Debug, "Next node: \n"); + printNode(node->next); + + //check previous node - if both are free -> can be combined (to prevent de-fragmentation) + Node* prev = node->previous; + Node* next = node->next; + + bool mergedWithPrevious = tryDeFragment(prev, node); + + if(mergedWithPrevious) + tryDeFragment(prev, next); + else + tryDeFragment(node, next); +} + +void heap_collect() {} //at this stage this will not be implemented, not that necessary. + +/* ------------------------------ LINKED LIST FUNCTIONS --------------------------------*/ + +Node* findNode(SortedLinkedList* list, size_t size_in_words) +{ + Node* temp = list->head; + while(temp != NULL) + { + if(!temp->is_allocated && temp->size_in_words >= size_in_words) + return temp; + + temp = temp->next; + } + + return NULL; +} + +void printNode(Node* node) +{ + if(node == NULL) + { + printf("NULL"); + return; + } + + + int start_offset = (int)(node->start - heap); + + int prevIndex = -1; + int nextIndex = -1; + + if(node->previous != NULL) + prevIndex = node->previous->allocated_index; + + if(node->next != NULL) + nextIndex = node->next->allocated_index; + + printf("{start:%d, size_in_words: %d, allocated_index: %d, is_allocated: %d, prevIndex: %d, nextIndex: %d}", + start_offset, (int)node->size_in_words, node->allocated_index, node->is_allocated, prevIndex, nextIndex); +} + +void printLinkedList(SortedLinkedList* list) +{ + printf("\nLinkedList: count(total) = %d, count(all.)=%d, count(de-all.) = %d\n", + list->count_total, list->count_allocated, list->count_de_allocated); + Node* temp = list->head; + while(temp != NULL) + { + printNode(temp); + printf(" -> "); + temp = temp->next; + } + printf("NULL\n"); +} + +Node* findNodeByPointer(SortedLinkedList* list, void* ptr) +{ + uintptr_t* start = (uintptr_t*)ptr; + Node* temp = list->head; + + while(temp != NULL) + { + if(temp->start == start) + return temp; + + temp = temp->next; + } + return NULL; +} + +/*-------------------------FUNCTIONS/VARIABLES TO MANAGE THE HEAP-------------------------------*/ + +SortedLinkedList list = + { + .head = NULL, + .count_allocated = 0, + .count_total = 0, + .count_de_allocated = 0 + }; +int mem_alloc[MAX_NUMBER_OF_NODES] = {0}; +Node node_alloc[MAX_NUMBER_OF_NODES] = +{ + { + .is_allocated=false, + .next = NULL, + .previous = NULL, + .size_in_words = 0, + .start = NULL, + .allocated_index = 0 + } +}; + +void initialize(enum LogLevel minimumLogLevel) +{ + int array_size = sizeof(node_alloc)/sizeof(node_alloc[0]); + for(int i = 0; i < array_size; i++) + { + node_alloc[i].allocated_index = i; + } + list.head = initializeHeadNode(); + list.count_total++; + list.count_de_allocated++; + setMinimumLogLevel(minimumLogLevel); +} + +Node* initializeHeadNode(void) +{ + node_alloc[0].size_in_words = HEAP_CAP_WORDS; + node_alloc[0].start = heap; + mem_alloc[0] = 1; + return &node_alloc[0]; +} + +Node* allocateNode(void) +{ + int array_size = sizeof(node_alloc)/sizeof(node_alloc[0]); + for(int i = 0; i < array_size; i++) + { + if(mem_alloc[i] == 0) //corresponding node is free + { + mem_alloc[i] = 1; //mark node as allocated + return &node_alloc[i]; + } + } + + return NULL; +} + +bool deAllocateNode(Node* node) +{ + int array_size = sizeof(node_alloc)/sizeof(node_alloc[0]); + + if(node->allocated_index < 0 || node->allocated_index >= array_size) + { + printf("Index out of bounds... "); + return false; + } + + if(node->is_allocated == false || mem_alloc[node->allocated_index] == 0) + { + printf("Invalid selection, node is not allocated. "); + return false; + } + + mem_alloc[node->allocated_index] = 0; + node->is_allocated = false; + + list.count_allocated--; + list.count_de_allocated++; + + return true; +} + +void printAllocations(void) +{ + int array_size = sizeof(node_alloc)/sizeof(node_alloc[0]); + printf("\nAllocations Array: "); + for(int i = 0; i < array_size; i++) + { + printf("{%d, %d}", i, mem_alloc[i]); + } + + printf("\nLinked List Allocations: "); + for(int i = 0; i < array_size; i++) + { + printf("\n%d'th node: ", i); + printNode(&node_alloc[i]); + } + printf("\n"); +} + +bool tryDeFragment(Node* prev, Node* current) +{ + //validations + if(prev == NULL || current == NULL) + { + logToConsole(Information, "\nCannot merge NULL nodes...\n"); + return false; + } + + if(prev->is_allocated == true || current->is_allocated == true) + { + logToConsole(Information, "\nOne of the nodes is allocated - cannot merge. prev allocated = %d, current allocated = %d\n", + prev->is_allocated, current->is_allocated); + return false; + } + + if(prev->allocated_index != current->previous->allocated_index || + current->allocated_index != prev->next->allocated_index) + { + logToConsole(Warning, "\nNodes are not consequental: "); + printf("\nPrevious: "); + printNode(prev); + printf("\nCurrent: "); + printNode(current); + printf("\n"); + } + + printf("Requested to merge nodes %d and %d", prev->allocated_index, current->allocated_index); + + int diff = (int)(current->start - prev->start); + + if(diff != (int)prev->size_in_words) + { + logToConsole(Warning, "diff is %d, prev size is : %d", diff, (int)prev->size_in_words); + return false; + } + + //we are ready to merge the nodes. + + //1. move node to previous + prev->next = current->next; + prev->size_in_words += current->size_in_words; + + //2. return node to array + current->next = NULL; + current->previous = NULL; + current->size_in_words = 0; + current->start = NULL; + mem_alloc[current->allocated_index] = 0; + + //3. if next node is not NULL -> need to update the previous flag. + if(prev->next != NULL) + prev->next->previous = prev; + + list.count_total--; + list.count_de_allocated--; + + logToConsole(Debug, "De fragnment successful"); + + return true; +} + +/*-------------------------MAIN FUNCTION IMPLEMENTATION-------------------------------*/ + +#define MIN_LOG_LEVEL Information + +int run(void); +int readInteger(char* command); + +void run_tests(void) +{ + initialize(MIN_LOG_LEVEL); + + int result = 0; + + while(result != -1) + { + result = run(); + logToConsole(Information, "Printing linked list: "); + printLinkedList(&list); + logToConsole(Information, "Printing allocations: "); + } + +} + +int run(void) +{ + printf("\n\n\nChoose one of the options below: \n"); + printf("[c] to clear the screen\n"); + printf("[a]_[size_in_bytes] to allocate...(a_10 will allocate 10 bytes)\n"); + printf("[d]_[index] to de-allocate...(d_0 will de-allocate 0th index)\n"); + printf("[e] to exit\n\n\n"); + + char command[20]; + memset(command, '\0', 20); + scanf("%s", command); + + if(strcmp("e", command) == 0) + return -1; + else if(strcmp("c", command) == 0) + system("clear"); + else if(command[0] == 'a') + { + system("clear"); + int num = readInteger(command); + + printf("Requested to allocate memory of %d bytes \n", num); + + void * pointer = heap_alloc(num); + if(pointer == NULL) + { + printf("Memory could not be allocated...\n"); + return 0; + } + + return 0; + } + else if(command[0] == 'd') + { + system("clear"); + int num = readInteger(command); + + printf("Requested to free node with allocation index %d \n", num); + + heap_free((void*)node_alloc[num].start); + + return 0; + } + else + { + printf("Entered command %s is invalid", command); + } + + return 0; +} + +int readInteger(char* command) +{ + int result = 0; + + for(int i =0; i<= (int)strlen(command); i++) + { + if(command[i] >=48 && command[i] <= 57) + result = result *10 + command[i] - 48; + } + + return result; +} diff --git a/linked_list.h b/linked_list.h new file mode 100644 index 0000000..4d4eba4 --- /dev/null +++ b/linked_list.h @@ -0,0 +1,53 @@ +/* +Although this may seem like an infrastructure header file, actually this is a specific +implementation for the project. The node structure is specifically designed to hold +a start offset for the memory location and size of the memory (could be in bytes... ) +*/ + +#ifndef LINKED_LIST_H_ +#define LINKED_LIST_H_ +#define MAX_CONCURRENT_ALLOCATIONS 20 +#define MAX_MEMORY_TO_SUPPORT_HEAP 64000 +#define MAX_NUMBER_OF_NODES 20 + +#include +#include +#include "logger.h" + +/*-------------------------LINKED LIST DECLARATION AND FUNCTIONS-------------------------------*/ + +typedef struct Node Node; + +struct Node { + uintptr_t* start; + size_t size_in_words; + Node* next; + Node* previous; + int allocated_index; + bool is_allocated; +}; + +typedef struct { + Node* head; + int count_allocated; + int count_total; + int count_de_allocated; +} SortedLinkedList; + +Node* findNode(SortedLinkedList* list, size_t size_in_words); +void printNode(Node* node); +void printLinkedList(SortedLinkedList* list); +Node* findNodeByPointer(SortedLinkedList* list, void* ptr); + +/*-------------------------FUNCTIONS/VARIABLES TO MANAGE THE HEAP-------------------------------*/ + +extern SortedLinkedList list; +extern int mem_alloc[MAX_NUMBER_OF_NODES]; +extern Node node_alloc[MAX_NUMBER_OF_NODES]; +void initialize(enum LogLevel minimumLogLevel); +extern Node* allocateNode(void); +extern bool deAllocateNode(Node* node); +extern void printAllocations(void); +extern bool tryDeFragment(Node* prev, Node* current); //returns 0 if success. + +#endif \ No newline at end of file diff --git a/logger.c b/logger.c new file mode 100644 index 0000000..53de018 --- /dev/null +++ b/logger.c @@ -0,0 +1,50 @@ +#include +#include "logger.h" +#include +#include + +enum LogLevel minimumLogLevel = Debug; + +void setMinimumLogLevel(enum LogLevel logLevel) +{ + minimumLogLevel = logLevel; +} + +void logToConsole(enum LogLevel level, const char *format, ...) +{ + if((int)level < (int)minimumLogLevel) + return; + + va_list argptr; + va_start(argptr, format); + + int size = strlen(format) + 20; + char formatNew[size]; + memset(formatNew, '\0', size); + strcat(formatNew, "\n"); + switch(level) { + case Debug: + strcat(formatNew, "Debug: "); + break; + + case Information: + strcat(formatNew, "Information: "); + break; + + case Warning: + strcat(formatNew, "Warning: "); + break; + + case Error: + strcat(formatNew, "Error: "); + break; + + default: + break; + } + strcat(formatNew, format); + strcat(formatNew, "\0"); + + vfprintf(stdout, formatNew, argptr); + va_end(argptr); +} \ No newline at end of file diff --git a/logger.h b/logger.h new file mode 100644 index 0000000..602ebd6 --- /dev/null +++ b/logger.h @@ -0,0 +1,11 @@ +#ifndef LOGGER_H_ +#define LOGGER_H_ +#include + +enum LogLevel { Debug, Information, Warning, Error}; + +void setMinimumLogLevel(enum LogLevel logLevel); + +void logToConsole(enum LogLevel level, const char *format, ...); + +#endif diff --git a/main.c b/main.c index 6ec6187..49052e8 100644 --- a/main.c +++ b/main.c @@ -1,88 +1,6 @@ -#include -#include -#include -#include -#include - #include "./heap.h" -#define JIM_IMPLEMENTATION -#include "jim.h" - -typedef struct Node Node; - -struct Node { - char x; - Node *left; - Node *right; -}; - -Node *generate_tree(size_t level_cur, size_t level_max) -{ - if (level_cur < level_max) { - Node *root = heap_alloc(sizeof(*root)); - assert((char) level_cur - 'a' <= 'z'); - root->x = level_cur + 'a'; - root->left = generate_tree(level_cur + 1, level_max); - root->right = generate_tree(level_cur + 1, level_max); - return root; - } else { - return NULL; - } -} - -void print_tree(Node *root, Jim *jim) +int main(void) { - if (root != NULL) { - jim_object_begin(jim); - - jim_member_key(jim, "value"); - jim_string_sized(jim, &root->x, 1); - - jim_member_key(jim, "left"); - print_tree(root->left, jim); - - jim_member_key(jim, "right"); - print_tree(root->right, jim); - - jim_object_end(jim); - } else { - jim_null(jim); - } -} - -#define N 10 - -void *ptrs[N] = {0}; - -int main() -{ - stack_base = (const uintptr_t*)__builtin_frame_address(0); - - for (size_t i = 0; i < 10; ++i) { - heap_alloc(i); - } - - Node *root = generate_tree(0, 3); - - printf("root: %p\n", (void*)root); - - Jim jim = { - .sink = stdout, - .write = (Jim_Write) fwrite, - }; - - print_tree(root, &jim); - - printf("\n------------------------------\n"); - heap_collect(); - chunk_list_dump(&alloced_chunks, "Alloced"); - chunk_list_dump(&freed_chunks, "Freed"); - printf("------------------------------\n"); - root = NULL; - heap_collect(); - chunk_list_dump(&alloced_chunks, "Alloced"); - chunk_list_dump(&freed_chunks, "Freed"); - - return 0; + run_tests(); }