malloc.c (8636B)
1/* 2 * libqos malloc support 3 * 4 * Copyright (c) 2014 5 * 6 * Author: 7 * John Snow <jsnow@redhat.com> 8 * 9 * This work is licensed under the terms of the GNU GPL, version 2 or later. 10 * See the COPYING file in the top-level directory. 11 */ 12 13#include "qemu/osdep.h" 14#include "malloc.h" 15#include "qemu-common.h" 16#include "qemu/host-utils.h" 17 18typedef struct MemBlock { 19 QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME; 20 uint64_t size; 21 uint64_t addr; 22} MemBlock; 23 24#define DEFAULT_PAGE_SIZE 4096 25 26static void mlist_delete(MemList *list, MemBlock *node) 27{ 28 g_assert(list && node); 29 QTAILQ_REMOVE(list, node, MLIST_ENTNAME); 30 g_free(node); 31} 32 33static MemBlock *mlist_find_key(MemList *head, uint64_t addr) 34{ 35 MemBlock *node; 36 QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { 37 if (node->addr == addr) { 38 return node; 39 } 40 } 41 return NULL; 42} 43 44static MemBlock *mlist_find_space(MemList *head, uint64_t size) 45{ 46 MemBlock *node; 47 48 QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { 49 if (node->size >= size) { 50 return node; 51 } 52 } 53 return NULL; 54} 55 56static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr) 57{ 58 MemBlock *node; 59 g_assert(head && insr); 60 61 QTAILQ_FOREACH(node, head, MLIST_ENTNAME) { 62 if (insr->addr < node->addr) { 63 QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME); 64 return insr; 65 } 66 } 67 68 QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME); 69 return insr; 70} 71 72static inline uint64_t mlist_boundary(MemBlock *node) 73{ 74 return node->size + node->addr; 75} 76 77static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right) 78{ 79 g_assert(head && left && right); 80 81 left->size += right->size; 82 mlist_delete(head, right); 83 return left; 84} 85 86static void mlist_coalesce(MemList *head, MemBlock *node) 87{ 88 g_assert(node); 89 MemBlock *left; 90 MemBlock *right; 91 char merge; 92 93 do { 94 merge = 0; 95 left = QTAILQ_PREV(node, MLIST_ENTNAME); 96 right = QTAILQ_NEXT(node, MLIST_ENTNAME); 97 98 /* clowns to the left of me */ 99 if (left && mlist_boundary(left) == node->addr) { 100 node = mlist_join(head, left, node); 101 merge = 1; 102 } 103 104 /* jokers to the right */ 105 if (right && mlist_boundary(node) == right->addr) { 106 node = mlist_join(head, node, right); 107 merge = 1; 108 } 109 110 } while (merge); 111} 112 113static MemBlock *mlist_new(uint64_t addr, uint64_t size) 114{ 115 MemBlock *block; 116 117 if (!size) { 118 return NULL; 119 } 120 block = g_new0(MemBlock, 1); 121 122 block->addr = addr; 123 block->size = size; 124 125 return block; 126} 127 128static uint64_t mlist_fulfill(QGuestAllocator *s, MemBlock *freenode, 129 uint64_t size) 130{ 131 uint64_t addr; 132 MemBlock *usednode; 133 134 g_assert(freenode); 135 g_assert_cmpint(freenode->size, >=, size); 136 137 addr = freenode->addr; 138 if (freenode->size == size) { 139 /* re-use this freenode as our used node */ 140 QTAILQ_REMOVE(s->free, freenode, MLIST_ENTNAME); 141 usednode = freenode; 142 } else { 143 /* adjust the free node and create a new used node */ 144 freenode->addr += size; 145 freenode->size -= size; 146 usednode = mlist_new(addr, size); 147 } 148 149 mlist_sort_insert(s->used, usednode); 150 return addr; 151} 152 153/* To assert the correctness of the list. 154 * Used only if ALLOC_PARANOID is set. */ 155static void mlist_check(QGuestAllocator *s) 156{ 157 MemBlock *node; 158 uint64_t addr = s->start > 0 ? s->start - 1 : 0; 159 uint64_t next = s->start; 160 161 QTAILQ_FOREACH(node, s->free, MLIST_ENTNAME) { 162 g_assert_cmpint(node->addr, >, addr); 163 g_assert_cmpint(node->addr, >=, next); 164 addr = node->addr; 165 next = node->addr + node->size; 166 } 167 168 addr = s->start > 0 ? s->start - 1 : 0; 169 next = s->start; 170 QTAILQ_FOREACH(node, s->used, MLIST_ENTNAME) { 171 g_assert_cmpint(node->addr, >, addr); 172 g_assert_cmpint(node->addr, >=, next); 173 addr = node->addr; 174 next = node->addr + node->size; 175 } 176} 177 178static uint64_t mlist_alloc(QGuestAllocator *s, uint64_t size) 179{ 180 MemBlock *node; 181 182 node = mlist_find_space(s->free, size); 183 if (!node) { 184 fprintf(stderr, "Out of guest memory.\n"); 185 g_assert_not_reached(); 186 } 187 return mlist_fulfill(s, node, size); 188} 189 190static void mlist_free(QGuestAllocator *s, uint64_t addr) 191{ 192 MemBlock *node; 193 194 if (addr == 0) { 195 return; 196 } 197 198 node = mlist_find_key(s->used, addr); 199 if (!node) { 200 fprintf(stderr, "Error: no record found for an allocation at " 201 "0x%016" PRIx64 ".\n", 202 addr); 203 g_assert_not_reached(); 204 } 205 206 /* Rip it out of the used list and re-insert back into the free list. */ 207 QTAILQ_REMOVE(s->used, node, MLIST_ENTNAME); 208 mlist_sort_insert(s->free, node); 209 mlist_coalesce(s->free, node); 210} 211 212/* 213 * Mostly for valgrind happiness, but it does offer 214 * a chokepoint for debugging guest memory leaks, too. 215 */ 216void alloc_destroy(QGuestAllocator *allocator) 217{ 218 MemBlock *node; 219 MemBlock *tmp; 220 QAllocOpts mask; 221 222 /* Check for guest leaks, and destroy the list. */ 223 QTAILQ_FOREACH_SAFE(node, allocator->used, MLIST_ENTNAME, tmp) { 224 if (allocator->opts & (ALLOC_LEAK_WARN | ALLOC_LEAK_ASSERT)) { 225 fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; " 226 "size 0x%016" PRIx64 ".\n", 227 node->addr, node->size); 228 } 229 if (allocator->opts & (ALLOC_LEAK_ASSERT)) { 230 g_assert_not_reached(); 231 } 232 g_free(node); 233 } 234 235 /* If we have previously asserted that there are no leaks, then there 236 * should be only one node here with a specific address and size. */ 237 mask = ALLOC_LEAK_ASSERT | ALLOC_PARANOID; 238 QTAILQ_FOREACH_SAFE(node, allocator->free, MLIST_ENTNAME, tmp) { 239 if ((allocator->opts & mask) == mask) { 240 if ((node->addr != allocator->start) || 241 (node->size != allocator->end - allocator->start)) { 242 fprintf(stderr, "Free list is corrupted.\n"); 243 g_assert_not_reached(); 244 } 245 } 246 247 g_free(node); 248 } 249 250 g_free(allocator->used); 251 g_free(allocator->free); 252} 253 254uint64_t guest_alloc(QGuestAllocator *allocator, size_t size) 255{ 256 uint64_t rsize = size; 257 uint64_t naddr; 258 259 if (!size) { 260 return 0; 261 } 262 263 rsize += (allocator->page_size - 1); 264 rsize &= -allocator->page_size; 265 g_assert_cmpint((allocator->start + rsize), <=, allocator->end); 266 g_assert_cmpint(rsize, >=, size); 267 268 naddr = mlist_alloc(allocator, rsize); 269 if (allocator->opts & ALLOC_PARANOID) { 270 mlist_check(allocator); 271 } 272 273 return naddr; 274} 275 276void guest_free(QGuestAllocator *allocator, uint64_t addr) 277{ 278 if (!addr) { 279 return; 280 } 281 mlist_free(allocator, addr); 282 if (allocator->opts & ALLOC_PARANOID) { 283 mlist_check(allocator); 284 } 285} 286 287void alloc_init(QGuestAllocator *s, QAllocOpts opts, 288 uint64_t start, uint64_t end, 289 size_t page_size) 290{ 291 MemBlock *node; 292 293 s->opts = opts; 294 s->start = start; 295 s->end = end; 296 297 s->used = g_new(MemList, 1); 298 s->free = g_new(MemList, 1); 299 QTAILQ_INIT(s->used); 300 QTAILQ_INIT(s->free); 301 302 node = mlist_new(s->start, s->end - s->start); 303 QTAILQ_INSERT_HEAD(s->free, node, MLIST_ENTNAME); 304 305 s->page_size = page_size; 306} 307 308void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts) 309{ 310 allocator->opts |= opts; 311} 312 313void migrate_allocator(QGuestAllocator *src, 314 QGuestAllocator *dst) 315{ 316 MemBlock *node, *tmp; 317 MemList *tmpused, *tmpfree; 318 319 /* The general memory layout should be equivalent, 320 * though opts can differ. */ 321 g_assert_cmphex(src->start, ==, dst->start); 322 g_assert_cmphex(src->end, ==, dst->end); 323 324 /* Destroy (silently, regardless of options) the dest-list: */ 325 QTAILQ_FOREACH_SAFE(node, dst->used, MLIST_ENTNAME, tmp) { 326 g_free(node); 327 } 328 QTAILQ_FOREACH_SAFE(node, dst->free, MLIST_ENTNAME, tmp) { 329 g_free(node); 330 } 331 332 tmpused = dst->used; 333 tmpfree = dst->free; 334 335 /* Inherit the lists of the source allocator: */ 336 dst->used = src->used; 337 dst->free = src->free; 338 339 /* Source is now re-initialized, the source memory is 'invalid' now: */ 340 src->used = tmpused; 341 src->free = tmpfree; 342 QTAILQ_INIT(src->used); 343 QTAILQ_INIT(src->free); 344 node = mlist_new(src->start, src->end - src->start); 345 QTAILQ_INSERT_HEAD(src->free, node, MLIST_ENTNAME); 346 return; 347}