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