|
| 1 | +/* |
| 2 | + * -------------------------------------------------------------------------------------------------- |
| 3 | + * Megha Operating System V2 - App Library - malloc allocator |
| 4 | + * -------------------------------------------------------------------------------------------------- |
| 5 | + */ |
| 6 | +#include <intrusive_list.h> |
| 7 | +#include <cm/err.h> |
| 8 | +#include <kcmlib.h> |
| 9 | +#include <types.h> |
| 10 | +#include <utils.h> |
| 11 | +#include <cm/debug.h> |
| 12 | +#include <cm/cm.h> |
| 13 | +#include <kcmlib.h> |
| 14 | + |
| 15 | +typedef enum FindCriteria { |
| 16 | + FIND_CRIT_NODE_SIZE, |
| 17 | + FIND_CRIT_NODE_ADDRESS |
| 18 | +} FindCriteria; |
| 19 | + |
| 20 | +static CM_MallocHeader* s_createNewNode (void* at, size_t netSize); |
| 21 | +static CM_MallocHeader* s_findFirst (ListNode* head, FindCriteria criteria, PTR value); |
| 22 | +static CM_MallocHeader* s_getMallocHeaderFromList (ListNode* head, ListNode* node); |
| 23 | +static void s_splitFreeNode (size_t bytes, CM_MallocHeader* freeNodeHdr); |
| 24 | +static void s_combineAdjFreeNodes (CM_MallocHeader* currentNode); |
| 25 | + |
| 26 | +extern ListNode s_freeHead, s_allocHead, s_adjHead; |
| 27 | +static void* s_buffer; |
| 28 | + |
| 29 | +#define NET_ALLOCATION_SIZE(sz_bytes) (sz_bytes + sizeof (CM_MallocHeader)) |
| 30 | + |
| 31 | +#ifndef UNITTEST |
| 32 | +ListNode s_freeHead, s_allocHead, s_adjHead; |
| 33 | +#else |
| 34 | +// kmalloc unit test must provide definitions for the list head variables |
| 35 | +#endif |
| 36 | + |
| 37 | +/*************************************************************************************************** |
| 38 | + * Initializes virtual & physical memory for kmalloc. |
| 39 | + * |
| 40 | + * @return None |
| 41 | + **************************************************************************************************/ |
| 42 | +void cm_malloc_init() |
| 43 | +{ |
| 44 | + CM_DBG_FUNC_ENTRY(); |
| 45 | + |
| 46 | + list_init (&s_freeHead); |
| 47 | + list_init (&s_allocHead); |
| 48 | + list_init (&s_adjHead); |
| 49 | + |
| 50 | + s_buffer = cm_process_get_datamem_start(); |
| 51 | + |
| 52 | + CM_MallocHeader* newH = s_createNewNode (s_buffer, CM_MALLOC_MEM_SIZE_BYTES); |
| 53 | + list_add_before (&s_freeHead, &newH->freenode); |
| 54 | + list_add_before (&s_adjHead, &newH->adjnode); |
| 55 | + |
| 56 | + CM_DBG_INFO ("Size of CM_MallocHeader: %lu bytes", sizeof (CM_MallocHeader)); |
| 57 | + CM_DBG_INFO ("Malloc buffer is at: %px", s_buffer); |
| 58 | +} |
| 59 | + |
| 60 | +/*************************************************************************************************** |
| 61 | + * Allocates at least 'bytes' number of bytes from the kmalloc memory. If successful then zeros the |
| 62 | + * memory before returning. |
| 63 | + * |
| 64 | + * @Input bytes Number of bytes to allocate. |
| 65 | + * @return Poiter to the start of the allocated memory. Or NULL on failure. |
| 66 | + **************************************************************************************************/ |
| 67 | +void* cm_calloc (size_t bytes) |
| 68 | +{ |
| 69 | + CM_DBG_FUNC_ENTRY ("Bytes: %x", bytes); |
| 70 | + |
| 71 | + void* addr = cm_malloc (bytes); |
| 72 | + if (addr != NULL) { |
| 73 | + cm_memset (addr, 0, bytes); |
| 74 | + } |
| 75 | + return addr; |
| 76 | +} |
| 77 | + |
| 78 | +/*************************************************************************************************** |
| 79 | + * Allocates at least 'bytes' number of bytes from the kmalloc memory. |
| 80 | + * |
| 81 | + * @Input bytes Number of bytes to allocate. |
| 82 | + * @return Poiter to the start of the allocated memory. Or NULL on failure. |
| 83 | + * @error ERR_OUT_OF_MEM - There is less memory than requested. |
| 84 | + **************************************************************************************************/ |
| 85 | +void* cm_malloc (size_t bytes) |
| 86 | +{ |
| 87 | + CM_DBG_FUNC_ENTRY ("Bytes: %x", bytes); |
| 88 | + |
| 89 | + if (bytes == 0) { |
| 90 | + CM_RETURN_ERROR (CM_ERR_INVALID_INPUT, NULL); |
| 91 | + } |
| 92 | + |
| 93 | + CM_DBG_INFO ("Requested net size of %lu bytes", NET_ALLOCATION_SIZE (bytes)); |
| 94 | + |
| 95 | + // Search for suitable node |
| 96 | + size_t searchAllocSize = NET_ALLOCATION_SIZE (bytes) + sizeof (CM_MallocHeader); |
| 97 | + CM_MallocHeader* node = s_findFirst (&s_freeHead, FIND_CRIT_NODE_SIZE, searchAllocSize); |
| 98 | + |
| 99 | + if (node != NULL) { |
| 100 | + cm_assert (node->netNodeSize >= NET_ALLOCATION_SIZE (bytes)); // Found node too small |
| 101 | + |
| 102 | + // Split the free node into two. |
| 103 | + s_splitFreeNode (bytes, node); |
| 104 | + return (void*)((PTR)node + sizeof (CM_MallocHeader)); |
| 105 | + } |
| 106 | + |
| 107 | + CM_RETURN_ERROR (CM_ERR_OUT_OF_HEAP_MEM, NULL); |
| 108 | +} |
| 109 | + |
| 110 | +/*************************************************************************************************** |
| 111 | + * Marks previously allocated memory starting at 'addr' as free. |
| 112 | + * |
| 113 | + * @Input addr Pointer to start of a kmalloc allocated memory. |
| 114 | + * @return True on success. False otherwise. |
| 115 | + * @error ERR_INVALID_ARGUMENT - If the input address was not found. |
| 116 | + **************************************************************************************************/ |
| 117 | +bool cm_free (void* addr) |
| 118 | +{ |
| 119 | + CM_DBG_FUNC_ENTRY ("Address: %px", addr); |
| 120 | + |
| 121 | + void* headerAddress = (void*)((PTR)addr - sizeof (CM_MallocHeader)); |
| 122 | + CM_MallocHeader* allocHdr = s_findFirst (&s_allocHead, FIND_CRIT_NODE_ADDRESS, |
| 123 | + (PTR)headerAddress); |
| 124 | + if (allocHdr != NULL) { |
| 125 | + CM_DBG_INFO ("Free at %px, Size = %lu", allocHdr, allocHdr->netNodeSize); |
| 126 | + list_remove (&allocHdr->allocnode); |
| 127 | + list_add_after (&s_freeHead, &allocHdr->freenode); |
| 128 | + allocHdr->isAllocated = false; |
| 129 | + |
| 130 | + s_combineAdjFreeNodes (allocHdr); |
| 131 | + return true; |
| 132 | + } |
| 133 | + |
| 134 | + // Could not find allocated node. Either double free or tatal error. |
| 135 | + cm_panic(); |
| 136 | + return false; |
| 137 | +} |
| 138 | + |
| 139 | +static void s_combineAdjFreeNodes (CM_MallocHeader* currentNode) |
| 140 | +{ |
| 141 | + CM_MallocHeader* next = NULL; |
| 142 | + CM_MallocHeader* prev = NULL; |
| 143 | + |
| 144 | + cm_assert (currentNode->isAllocated == false); // Cannot combine allocated node. Invalid input. |
| 145 | + |
| 146 | + // Prev node could be the list head (which is not a CM_MallocHeader). That would mean beginning |
| 147 | + // of list and is not taken into account. |
| 148 | + if (currentNode->adjnode.prev != &s_adjHead) { |
| 149 | + prev = LIST_ITEM (currentNode->adjnode.prev, CM_MallocHeader, adjnode); |
| 150 | + } |
| 151 | + |
| 152 | + // Next node could be the list head (which is not a CM_MallocHeader). That would mean end of |
| 153 | + // list and is not taken into account (Practically this is not possible as there will always be |
| 154 | + // one Free section at the end of kmalloc buffer). |
| 155 | + if (currentNode->adjnode.next != &s_adjHead) { |
| 156 | + next = LIST_ITEM (currentNode->adjnode.next, CM_MallocHeader, adjnode); |
| 157 | + } |
| 158 | + |
| 159 | + if (next && !next->isAllocated) { |
| 160 | + CM_DBG_INFO ("Combining NEXT into CURRENT"); |
| 161 | + currentNode->netNodeSize += next->netNodeSize; |
| 162 | + list_remove (&next->freenode); |
| 163 | + list_remove (&next->adjnode); |
| 164 | + } |
| 165 | + |
| 166 | + if (prev && !prev->isAllocated) { |
| 167 | + CM_DBG_INFO ("Combining CURRENT into PREV"); |
| 168 | + prev->netNodeSize += currentNode->netNodeSize; |
| 169 | + list_remove (¤tNode->freenode); |
| 170 | + list_remove (¤tNode->adjnode); |
| 171 | + } |
| 172 | +} |
| 173 | + |
| 174 | +static CM_MallocHeader* s_createNewNode (void* at, size_t netSize) |
| 175 | +{ |
| 176 | + // Node netSize too large. Not possible. |
| 177 | + cm_assert (((PTR)at + netSize - 1) < ((PTR)s_buffer + ARCH_MEM_LEN_BYTES_KMALLOC)); |
| 178 | + |
| 179 | + CM_MallocHeader* newH = at; |
| 180 | + newH->netNodeSize = netSize; |
| 181 | + newH->isAllocated = false; |
| 182 | + list_init (&newH->freenode); |
| 183 | + list_init (&newH->allocnode); |
| 184 | + list_init (&newH->adjnode); |
| 185 | + return newH; |
| 186 | +} |
| 187 | + |
| 188 | +static CM_MallocHeader* s_getMallocHeaderFromList (ListNode* head, ListNode* node) |
| 189 | +{ |
| 190 | + CM_MallocHeader* ret = NULL; |
| 191 | + if (head == &s_allocHead) { |
| 192 | + ret = LIST_ITEM (node, CM_MallocHeader, allocnode); |
| 193 | + } else if (head == &s_freeHead) { |
| 194 | + ret = LIST_ITEM (node, CM_MallocHeader, freenode); |
| 195 | + } |
| 196 | + |
| 197 | + return ret; |
| 198 | +} |
| 199 | + |
| 200 | +static CM_MallocHeader* s_findFirst (ListNode* head, FindCriteria criteria, PTR value) |
| 201 | +{ |
| 202 | + bool found = false; |
| 203 | + ListNode* node = NULL; |
| 204 | + CM_MallocHeader* header = NULL; |
| 205 | + |
| 206 | + CM_DBG_INFO ("Searching for value %lu (%lx)", value, value); |
| 207 | + |
| 208 | + list_for_each (head, node) |
| 209 | + { |
| 210 | + header = s_getMallocHeaderFromList (head, node); |
| 211 | + CM_DBG_INFO (" (%px) Size = %lu...", header, header->netNodeSize); |
| 212 | + |
| 213 | + switch (criteria) { |
| 214 | + case FIND_CRIT_NODE_ADDRESS: |
| 215 | + found = ((PTR)header == value); |
| 216 | + break; |
| 217 | + case FIND_CRIT_NODE_SIZE: |
| 218 | + found = (header->netNodeSize >= value); |
| 219 | + break; |
| 220 | + } |
| 221 | + |
| 222 | + if (found) { |
| 223 | + CM_DBG_INFO ("Found."); |
| 224 | + break; |
| 225 | + } |
| 226 | + } |
| 227 | + return (found) ? header : NULL; |
| 228 | +} |
| 229 | + |
| 230 | +static void s_splitFreeNode (size_t bytes, CM_MallocHeader* freeNodeHdr) |
| 231 | +{ |
| 232 | + size_t netAllocSize = NET_ALLOCATION_SIZE (bytes); |
| 233 | + PTR splitAt = (PTR)freeNodeHdr + netAllocSize; |
| 234 | + size_t remainingSize = freeNodeHdr->netNodeSize - netAllocSize; |
| 235 | + cm_assert (remainingSize >= sizeof (CM_MallocHeader)); // Not enough space for kmalloc header |
| 236 | + |
| 237 | + CM_MallocHeader* newFreeNodeHdr = s_createNewNode ((void*)splitAt, remainingSize); |
| 238 | + freeNodeHdr->netNodeSize = netAllocSize; |
| 239 | + freeNodeHdr->isAllocated = true; |
| 240 | + |
| 241 | + list_add_after (&freeNodeHdr->freenode, &newFreeNodeHdr->freenode); |
| 242 | + list_add_after (&freeNodeHdr->adjnode, &newFreeNodeHdr->adjnode); |
| 243 | + list_remove (&freeNodeHdr->freenode); |
| 244 | + list_add_before (&s_allocHead, &freeNodeHdr->allocnode); |
| 245 | +} |
0 commit comments