2 * libqos malloc support
7 * John Snow <jsnow@redhat.com>
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.
13 #include "libqos/malloc.h"
14 #include "qemu-common.h"
19 typedef QTAILQ_HEAD(MemList
, MemBlock
) MemList
;
21 typedef struct MemBlock
{
22 QTAILQ_ENTRY(MemBlock
) MLIST_ENTNAME
;
27 struct QGuestAllocator
{
37 #define DEFAULT_PAGE_SIZE 4096
39 static void mlist_delete(MemList
*list
, MemBlock
*node
)
41 g_assert(list
&& node
);
42 QTAILQ_REMOVE(list
, node
, MLIST_ENTNAME
);
46 static MemBlock
*mlist_find_key(MemList
*head
, uint64_t addr
)
49 QTAILQ_FOREACH(node
, head
, MLIST_ENTNAME
) {
50 if (node
->addr
== addr
) {
57 static MemBlock
*mlist_find_space(MemList
*head
, uint64_t size
)
61 QTAILQ_FOREACH(node
, head
, MLIST_ENTNAME
) {
62 if (node
->size
>= size
) {
69 static MemBlock
*mlist_sort_insert(MemList
*head
, MemBlock
*insr
)
72 g_assert(head
&& insr
);
74 QTAILQ_FOREACH(node
, head
, MLIST_ENTNAME
) {
75 if (insr
->addr
< node
->addr
) {
76 QTAILQ_INSERT_BEFORE(node
, insr
, MLIST_ENTNAME
);
81 QTAILQ_INSERT_TAIL(head
, insr
, MLIST_ENTNAME
);
85 static inline uint64_t mlist_boundary(MemBlock
*node
)
87 return node
->size
+ node
->addr
;
90 static MemBlock
*mlist_join(MemList
*head
, MemBlock
*left
, MemBlock
*right
)
92 g_assert(head
&& left
&& right
);
94 left
->size
+= right
->size
;
95 mlist_delete(head
, right
);
99 static void mlist_coalesce(MemList
*head
, MemBlock
*node
)
108 left
= QTAILQ_PREV(node
, MemList
, MLIST_ENTNAME
);
109 right
= QTAILQ_NEXT(node
, MLIST_ENTNAME
);
111 /* clowns to the left of me */
112 if (left
&& mlist_boundary(left
) == node
->addr
) {
113 node
= mlist_join(head
, left
, node
);
117 /* jokers to the right */
118 if (right
&& mlist_boundary(node
) == right
->addr
) {
119 node
= mlist_join(head
, node
, right
);
126 static MemBlock
*mlist_new(uint64_t addr
, uint64_t size
)
133 block
= g_malloc0(sizeof(MemBlock
));
141 static uint64_t mlist_fulfill(QGuestAllocator
*s
, MemBlock
*freenode
,
148 g_assert_cmpint(freenode
->size
, >=, size
);
150 addr
= freenode
->addr
;
151 if (freenode
->size
== size
) {
152 /* re-use this freenode as our used node */
153 QTAILQ_REMOVE(&s
->free
, freenode
, MLIST_ENTNAME
);
156 /* adjust the free node and create a new used node */
157 freenode
->addr
+= size
;
158 freenode
->size
-= size
;
159 usednode
= mlist_new(addr
, size
);
162 mlist_sort_insert(&s
->used
, usednode
);
166 /* To assert the correctness of the list.
167 * Used only if ALLOC_PARANOID is set. */
168 static void mlist_check(QGuestAllocator
*s
)
171 uint64_t addr
= s
->start
> 0 ? s
->start
- 1 : 0;
172 uint64_t next
= s
->start
;
174 QTAILQ_FOREACH(node
, &s
->free
, MLIST_ENTNAME
) {
175 g_assert_cmpint(node
->addr
, >, addr
);
176 g_assert_cmpint(node
->addr
, >=, next
);
178 next
= node
->addr
+ node
->size
;
181 addr
= s
->start
> 0 ? s
->start
- 1 : 0;
183 QTAILQ_FOREACH(node
, &s
->used
, MLIST_ENTNAME
) {
184 g_assert_cmpint(node
->addr
, >, addr
);
185 g_assert_cmpint(node
->addr
, >=, next
);
187 next
= node
->addr
+ node
->size
;
191 static uint64_t mlist_alloc(QGuestAllocator
*s
, uint64_t size
)
195 node
= mlist_find_space(&s
->free
, size
);
197 fprintf(stderr
, "Out of guest memory.\n");
198 g_assert_not_reached();
200 return mlist_fulfill(s
, node
, size
);
203 static void mlist_free(QGuestAllocator
*s
, uint64_t addr
)
211 node
= mlist_find_key(&s
->used
, addr
);
213 fprintf(stderr
, "Error: no record found for an allocation at "
214 "0x%016" PRIx64
".\n",
216 g_assert_not_reached();
219 /* Rip it out of the used list and re-insert back into the free list. */
220 QTAILQ_REMOVE(&s
->used
, node
, MLIST_ENTNAME
);
221 mlist_sort_insert(&s
->free
, node
);
222 mlist_coalesce(&s
->free
, node
);
226 * Mostly for valgrind happiness, but it does offer
227 * a chokepoint for debugging guest memory leaks, too.
229 void alloc_uninit(QGuestAllocator
*allocator
)
235 /* Check for guest leaks, and destroy the list. */
236 QTAILQ_FOREACH_SAFE(node
, &allocator
->used
, MLIST_ENTNAME
, tmp
) {
237 if (allocator
->opts
& (ALLOC_LEAK_WARN
| ALLOC_LEAK_ASSERT
)) {
238 fprintf(stderr
, "guest malloc leak @ 0x%016" PRIx64
"; "
239 "size 0x%016" PRIx64
".\n",
240 node
->addr
, node
->size
);
242 if (allocator
->opts
& (ALLOC_LEAK_ASSERT
)) {
243 g_assert_not_reached();
248 /* If we have previously asserted that there are no leaks, then there
249 * should be only one node here with a specific address and size. */
250 mask
= ALLOC_LEAK_ASSERT
| ALLOC_PARANOID
;
251 QTAILQ_FOREACH_SAFE(node
, &allocator
->free
, MLIST_ENTNAME
, tmp
) {
252 if ((allocator
->opts
& mask
) == mask
) {
253 if ((node
->addr
!= allocator
->start
) ||
254 (node
->size
!= allocator
->end
- allocator
->start
)) {
255 fprintf(stderr
, "Free list is corrupted.\n");
256 g_assert_not_reached();
266 uint64_t guest_alloc(QGuestAllocator
*allocator
, size_t size
)
268 uint64_t rsize
= size
;
271 rsize
+= (allocator
->page_size
- 1);
272 rsize
&= -allocator
->page_size
;
273 g_assert_cmpint((allocator
->start
+ rsize
), <=, allocator
->end
);
274 g_assert_cmpint(rsize
, >=, size
);
276 naddr
= mlist_alloc(allocator
, rsize
);
277 if (allocator
->opts
& ALLOC_PARANOID
) {
278 mlist_check(allocator
);
284 void guest_free(QGuestAllocator
*allocator
, uint64_t addr
)
286 mlist_free(allocator
, addr
);
287 if (allocator
->opts
& ALLOC_PARANOID
) {
288 mlist_check(allocator
);
292 QGuestAllocator
*alloc_init(uint64_t start
, uint64_t end
)
294 QGuestAllocator
*s
= g_malloc0(sizeof(*s
));
300 QTAILQ_INIT(&s
->used
);
301 QTAILQ_INIT(&s
->free
);
303 node
= mlist_new(s
->start
, s
->end
- s
->start
);
304 QTAILQ_INSERT_HEAD(&s
->free
, node
, MLIST_ENTNAME
);
306 s
->page_size
= DEFAULT_PAGE_SIZE
;
311 QGuestAllocator
*alloc_init_flags(QAllocOpts opts
,
312 uint64_t start
, uint64_t end
)
314 QGuestAllocator
*s
= alloc_init(start
, end
);
319 void alloc_set_page_size(QGuestAllocator
*allocator
, size_t page_size
)
321 /* Can't alter the page_size for an allocator in-use */
322 g_assert(QTAILQ_EMPTY(&allocator
->used
));
324 g_assert(is_power_of_2(page_size
));
325 allocator
->page_size
= page_size
;
328 void alloc_set_flags(QGuestAllocator
*allocator
, QAllocOpts opts
)
330 allocator
->opts
|= opts
;