L4Re Operating System Framework
Interface and Usage Documentation
Loading...
Searching...
No Matches
buddy_alloc
1// vim:set ft=cpp: -*- Mode: C++ -*-
2/*
3 * Copyright (C) 2026 Kernkonzept GmbH.
4 * Author(s): Georg Kotheimer <georg.kotheimer@kernkonzept.com>
5 *
6 * License: see LICENSE.spdx (in this directory or the directories above)
7 */
8
9#pragma once
10
11#include <l4/cxx/arith>
12#include <l4/cxx/minmax>
13#include <l4/cxx/type_traits>
14#include <l4/sys/compiler.h>
15#include <l4/sys/consts.h>
16#include <l4/sys/l4int.h>
17
18#include <cassert>
19#include <cstddef>
20#include <cstdio>
21
22namespace cxx
23{
24
57{
58public:
59 static constexpr bool Debug_alloc = false;
60#ifdef NDEBUG
61 static constexpr bool Print_warnings = false;
62#else
63 static constexpr bool Print_warnings = true;
64#endif
65
66public:
67 static constexpr unsigned Min_order = L4_PAGESHIFT;
68 static constexpr size_t Min_size = 1UL << Min_order;
69
70 // OPTIMIZE: Use only exactly as many bits as necessary per level.
71 using Order_free = l4_uint8_t;
72
73 static constexpr unsigned long Invalid_node_index = 0;
74 static constexpr unsigned long Root_node_index = 1;
75
76 struct Node_ptr
77 {
78 unsigned long index; // 1-based index
79 unsigned level;
80
81 Node_ptr() : index(Invalid_node_index), level(0) {}
82 Node_ptr(unsigned long index, unsigned level) : index(index), level(level)
83 {}
84
85 Node_ptr parent() const
86 {
87 assert(!is_root());
88 return Node_ptr{index / 2, level + 1};
89 }
90
91 Node_ptr left_child() const
92 {
93 assert(!is_leaf());
94 return Node_ptr{index * 2, level - 1};
95 }
96
97 Node_ptr right_child() const
98 {
99 assert(!is_leaf());
100 return Node_ptr{(index * 2) + 1, level - 1};
101 }
102
103 bool valid() const { return index != Invalid_node_index; }
104 bool is_left() const { return index % 2 == 0; }
105 bool is_right() const { return index % 2 == 1; }
106 bool is_root() const { return index == Root_node_index; }
107 bool is_leaf() const { return level == 0; }
108
109 friend constexpr bool operator==(Node_ptr const &lhs,
110 Node_ptr const &rhs)
111 { return lhs.index == rhs.index && lhs.level == rhs.level; }
112
113 friend constexpr bool operator!=(Node_ptr const &lhs,
114 Node_ptr const &rhs)
115 { return !(lhs == rhs); }
116 };
117
124 static constexpr size_t metadata_bytes(l4_addr_t min_addr, l4_addr_t max_addr)
125 {
126 return num_tree_nodes(min_addr, max_addr) * sizeof(Order_free);
127 }
128
129 bool initialized() const { return _tree != nullptr; }
130
139 void init(l4_addr_t min_addr, l4_addr_t max_addr,
140 unsigned char *metadata_addr, size_t metadata_size)
141 {
142 assert(metadata_bytes(min_addr, max_addr) <= metadata_size);
143
144 l4_uint64_t max_mem_size;
145 calc_mem_range(min_addr, max_addr, &_base, &max_mem_size);
146 _base = to_min_size_units(_base);
147 _max_mem_size = to_min_size_units(max_mem_size);
148 _max_level = cxx::arith::log2u(_max_mem_size);
149 _tree = metadata_addr;
150 // Initially the entire tree is unavailable.
151 __builtin_memset(_tree, 0, metadata_size);
152
153 if constexpr (Debug_alloc)
154 {
155 printf("Buddy allocator info:\n");
156 printf(" Metadata size: %zu (%zu KiB)\n",
157 metadata_size, metadata_size / 1024);
158 printf(" Base address: %#lx\n", from_min_size_units(_base));
159 printf(" Max mem size: %#llx\n", _max_mem_size * l4_uint64_t{Min_size});
160 printf(" Max mem order: %u\n", _max_level + Min_order);
161 printf(" Range: [%#lx, %#lx]\n", min_addr, max_addr);
162 printf(" Max level: %u\n", _max_level);
163 printf(" Total number of nodes: %lu\n",
164 num_tree_nodes(min_addr, max_addr));
165 printf(" Levels:\n");
166 for (int level = _max_level; level >= 0; level--)
167 {
168 printf(" %2d: size=%#llx start_index=%ld, nodes=%ld\n", level,
169 block_size_for_level(level) * l4_uint64_t{Min_size},
170 level_start_index(level), nodes_in_level(level));
171 }
172 }
173 }
174
183 inline void add_mem(void *mem, size_t size);
184
201 inline void *alloc(size_t size, size_t align, l4_addr_t lower = 0,
202 l4_addr_t upper = ~0UL);
203
231 inline void *alloc_max(size_t min, size_t *max, size_t align,
232 size_t granularity, l4_addr_t lower = 0,
233 l4_addr_t upper = ~0UL);
234
244 inline void free(void *block, size_t size);
245
251 inline size_t avail() const;
252
253 template<typename DBG>
254 void dump_free(DBG &out) const;
255
256private:
257 struct Mem_range
258 {
259 l4_addr_t start;
260 l4_addr_t end; // inclusive
261
262 Mem_range() : start(static_cast<l4_addr_t>(-1)), end(0) {}
263 Mem_range(l4_addr_t start, l4_addr_t end) : start(start), end(end)
264 { assert(valid()); }
265
266 bool valid() const
267 { return start <= end; }
268
269 size_t size() const
270 { return end - start + 1; }
271
272 friend bool operator < (Mem_range const &lhs, Mem_range const &rhs)
273 { return lhs.end < rhs.start; }
274
275 bool overlaps(Mem_range const &o) const
276 { return !(*this < o) && !(o < *this); }
277
278 bool contains(Mem_range const &o) const
279 { return start <= o.start && end >= o.end; }
280
281 Mem_range intersect(Mem_range const &o) const
282 {
283 if (!overlaps(o))
284 return Mem_range();
285
286 return Mem_range{start > o.start ? start : o.start,
287 end < o.end ? end : o.end};
288 }
289 };
290
292 template<typename T>
293 static constexpr T ceil_power_of_two(T val)
294 {
295 return T{1} << cxx::arith::log2u_ceil(val);
296 }
297
298 template<typename T>
299 static constexpr T trunc_power_of_two(T val)
300 {
301 return T{1} << cxx::arith::log2u(val);
302 }
303
304 template<typename T>
305 static constexpr bool is_power_of_two(T val)
306 {
307 return val > 0 && (val & (val - 1)) == 0;
308 }
309
310 template<typename T, typename B>
311 static constexpr T ceil_to(T val, B boundary)
312 {
313 assert(is_power_of_two(boundary));
314 return (val + boundary - 1) & ~T{boundary - 1};
315 }
316
317 template<typename T, typename B>
318 static constexpr T trunc_to(T val, B boundary)
319 {
320 assert(is_power_of_two(boundary));
321 return val & ~T{boundary - 1};
322 }
323
324 template<typename T, typename B>
325 static constexpr bool is_aligned_to(T val, B boundary)
326 {
327 return trunc_to(val, boundary) == val;
328 }
329
330 static constexpr unsigned trailing_zeroes(unsigned val)
331 {
332 return val ? __builtin_ctz(val) : sizeof(unsigned) * 8;
333 }
334
335 static constexpr unsigned trailing_zeroes(unsigned long val)
336 {
337 return val ? __builtin_ctzl(val) : sizeof(unsigned long) * 8;
338 }
339
341 template<typename T>
342 static constexpr T to_min_size_units(T val)
343 {
344 assert(is_aligned_to(val, Min_size));
345 return val / Min_size;
346 }
347
349 template<typename T>
350 static constexpr T from_min_size_units(T val)
351 {
352 assert((val * Min_size) / Min_size == val);
353 return val * Min_size;
354 }
355
367 static constexpr void calc_mem_range(l4_addr_t min_addr, l4_addr_t max_addr,
368 l4_addr_t *base_addr,
369 l4_uint64_t *max_size)
370 {
371 assert(min_addr < max_addr);
372 l4_uint64_t raw_size = max_addr - min_addr + l4_uint64_t{1};
373 assert(raw_size >= Min_size);
374
375 *max_size = ceil_power_of_two(raw_size);
376 *base_addr = trunc_to(l4_uint64_t{min_addr}, *max_size);
377 // Adjust max block size and base address, until they cover the entire
378 // requested memory area.
379 // The covered range is [base_addr, base_addr + max_size - 1], thus
380 // `max_addr` is only included if `max_size > max_addr - base_addr`.
381 while (*max_size <= max_addr - *base_addr)
382 {
383 // Max address not included after alignment, use next power of two.
384 assert(*max_size < (l4_uint64_t{1} << 63));
385 *max_size *= 2;
386 *base_addr = trunc_to(l4_uint64_t{min_addr}, *max_size);
387 }
388 }
389
390 static constexpr unsigned long num_tree_nodes(l4_addr_t min_addr,
391 l4_addr_t max_addr)
392 {
393 l4_addr_t base_addr = 0;
394 l4_uint64_t max_mem_size = 0;
395 calc_mem_range(min_addr, max_addr, &base_addr, &max_mem_size);
396 // OPTIMIZE: Only allocate tree storage for the memory between min_range and
397 // max_range.
398 auto actual_nodes = ((2 * max_mem_size) - 1) / Min_size;
399 // The tree uses 1-based indexes for efficient parent/child navigation. For
400 // simplicity the node at index 0 is unused.
401 return actual_nodes + 1;
402 }
403
404 unsigned long nodes_in_level(unsigned level) const
405 {
406 return 1UL << (_max_level - level);
407 }
408
409 unsigned long level_start_index(unsigned level) const
410 {
411 return nodes_in_level(level);
412 }
413
414 static unsigned level_for_block_size(size_t size)
415 {
416 assert(is_power_of_two(size)); // power of two, so a block size.
417 return cxx::arith::log2u(size);
418 }
419
428 static l4_size_t block_size_for_level(unsigned level)
429 {
430 return l4_size_t{1} << level;
431 }
432
433 Node_ptr node_for_block_level(l4_addr_t addr, unsigned level) const
434 {
435 size_t node_size = block_size_for_level(level);
436
437 assert(addr >= _base);
438 assert(addr + node_size > addr);
439 assert(addr + node_size <= _base + _max_mem_size);
440 assert(is_aligned_to(addr, node_size));
441
442 unsigned long index = level_start_index(level) + ((addr - _base) / node_size);
443 return Node_ptr{index, level};
444 }
445
446 Node_ptr node_for_block(l4_addr_t addr, size_t node_size) const
447 {
448 assert(addr >= _base);
449 assert(addr + node_size > addr);
450 assert(addr + node_size <= _base + _max_mem_size);
451 assert(is_aligned_to(addr, node_size));
452
453 unsigned level = level_for_block_size(node_size);
454 unsigned long index = level_start_index(level) + ((addr - _base) / node_size);
455 return Node_ptr{index, level};
456 }
457
458 l4_addr_t block_start_addr(Node_ptr node) const
459 {
460 size_t size = block_size_for_level(node.level);
461 size_t offset = (node.index - level_start_index(node.level)) * size;
462 return _base + offset;
463 }
464
465 l4_addr_t block_end_addr(Node_ptr node) const
466 {
467 l4_addr_t block_start = block_start_addr(node);
468 size_t block_size = block_size_for_level(node.level);
469 return block_start + block_size - 1;
470 }
471
472 Mem_range block_range(Node_ptr node) const
473 {
474 return Mem_range{block_start_addr(node), block_end_addr(node)};
475 }
476
477 static bool fits_level(Order_free node_order, unsigned level)
478 {
479 // Because 0 marks allocated nodes, the largest free order of node is stored
480 // as level+1.
481 return node_order > level;
482 }
483
489 static unsigned min_level_required_for_size(size_t size)
490 {
491 // The intuition for this computation comes from looking at the worst case.
492 // In the following example 1 unit corresponds to Min_size.
493 // The "worst" placement that avoids an aligned block of size B is:
494 // - Start 1 unit after a block boundary.
495 // - End 1 unit before the block boundary after the next block.
496 // The interval spanned by these two almost complete blocks is `B - 1` each,
497 // so `2B - 2` in total. From that we can derive that each span with a
498 // length `n > 2B - 2` or `n >= 2B - 1` must contain an aligned block of
499 // size B. Solve for B, and arrive at: `min_B = (n + 1) / 2`
500 return cxx::arith::log2u((size + 1) / 2);
501 }
502
503 Order_free node_get(Node_ptr node) const
504 {
505 assert(node.valid());
506 return _tree[node.index];
507 }
508
509 void node_set(Node_ptr node, Order_free largest_order)
510 {
511 _tree[node.index] = largest_order;
512 }
513
520 bool node_is_free(Node_ptr node) const
521 {
522 return node_get(node) == node.level + 1;
523 }
524
526 bool node_is_used(Node_ptr node) const { return node_get(node) == 0; }
527
528 void node_set_free(Node_ptr node) { node_set(node, node.level + 1); }
529 void node_set_used(Node_ptr node) { node_set(node, 0); }
530
531 Node_ptr root_node() const { return Node_ptr{Root_node_index, _max_level}; }
532
533 bool node_is_seperately_allocated(Node_ptr node)
534 {
535 // If both children of the node are marked as used, the node was not
536 // allocated as a whole, but rather both child nodes separately by
537 // two independent allocations. Freeing it as a single node would leave
538 // the children behind as used and thus corrupt the tree.
539 return !node.is_leaf() && node_is_used(node.left_child())
540 && node_is_used(node.right_child());
541 }
542
543 enum class Walk_dir
544 {
545 Descend, // Descend into the children of the node.
546 Next, // Go to next node (sibling, or up through parent chain).
547 Stop, // Stop tree walk.
548 };
549
559 template<typename CB>
560 bool tree_walk(CB const &cb, Node_ptr root) const
561 {
562 // Invokes callback and returns whether tree walk should descend further.
563 auto handle_node = [&cb](Node_ptr &node)
564 {
565 Walk_dir dir = cb(node);
566 if (dir == Walk_dir::Descend && node.is_leaf())
567 dir = Walk_dir::Next;
568 return dir;
569 };
570
571 if (handle_node(root) != Walk_dir::Descend)
572 return true;
573
574 Node_ptr cur = root.left_child();
575 for (;;)
576 {
577 auto dir = handle_node(cur);
578 if (dir == Walk_dir::Stop)
579 return false;
580
581 if (dir == Walk_dir::Descend)
582 {
583 cur = cur.left_child();
584 continue;
585 }
586
587 // Walk_dir::Next
588 if (cur.is_left())
589 cur = cur.parent().right_child();
590 else if (cur.is_right())
591 {
592 // Bubble up chain of parents to determine next node.
593 Node_ptr parent = cur.parent();
594 while (parent != root && parent.is_right())
595 parent = parent.parent();
596 if (parent == root)
597 return true; // walk done
598 cur = parent.parent().right_child();
599 }
600 }
601 }
602
606 template<typename CB>
607 void tree_walk_free(CB const &cb) const
608 {
609 tree_walk([this, &cb](Node_ptr const &node)
610 {
611 if (node_is_free(node))
612 {
613 cb(node);
614 return Walk_dir::Next; // encountered free node, bubble up
615 }
616
617 // Descend only if node is split.
618 return node_is_used(node) ? Walk_dir::Next : Walk_dir::Descend;
619 }, root_node());
620 }
621
631 Node_ptr find_bounded_root(unsigned desired_level,
632 Mem_range const &alloc_range)
633 {
634 Node_ptr node = root_node();
635 while (node.level > desired_level)
636 {
637 // Must not descend into subtree shadowed by an allocated node.
638 if (node_is_used(node))
639 break;
640
641 auto left = node.left_child();
642 auto right = node.right_child();
643 if (block_range(left).contains(alloc_range))
644 node = left; // Left child contains range.
645 else if (block_range(right).contains(alloc_range))
646 node = right; // Rights child contains range.
647 else
648 break; // Parent node contains range (or is the root of the tree).
649 }
650 return node;
651 }
652
653 static Node_ptr next_node(Node_ptr node, Node_ptr root)
654 {
655 assert(node.valid());
656 if (L4_UNLIKELY(node == root))
657 return Node_ptr();
658
659 if (node.is_left())
660 node = node.parent().right_child();
661 else if (node.is_right())
662 {
663 // Bubble up chain of parents to determine next node.
664 Node_ptr parent = node.parent();
665 while (parent != root && parent.is_right())
666 parent = parent.parent();
667 if (parent == root)
668 return Node_ptr();
669 node = parent.parent().right_child();
670 }
671 return node;
672 }
673
674 static Node_ptr prev_node(Node_ptr node, Node_ptr root)
675 {
676 assert(node.valid());
677 if (L4_UNLIKELY(node == root))
678 return Node_ptr();
679
680 if (node.is_right())
681 node = node.parent().left_child();
682 else if (node.is_left())
683 {
684 // Bubble up chain of parents to determine next node.
685 Node_ptr parent = node.parent();
686 while (parent != root && parent.is_left())
687 parent = parent.parent();
688 if (parent == root)
689 return Node_ptr();
690 node = parent.parent().left_child();
691 }
692 return node;
693 }
694
695 Node_ptr next_adjacent_free(Node_ptr node, Node_ptr root)
696 {
697 Node_ptr next = next_node(node, root);
698 if (!next.valid())
699 return Node_ptr();
700
701 while (!node_is_free(next))
702 {
703 if (next.is_leaf() || node_is_used(next))
704 return Node_ptr();
705 next = next.left_child();
706 }
707 return next;
708 }
709
710 Node_ptr prev_adjacent_free(Node_ptr node, Node_ptr root)
711 {
712 Node_ptr prev = prev_node(node, root);
713 if (!prev.valid())
714 return Node_ptr();
715
716 while (!node_is_free(prev))
717 {
718 if (prev.is_leaf() || node_is_used(prev))
719 return Node_ptr();
720 prev = prev.right_child();
721 }
722 return prev;
723 }
724
730 Node_ptr next_node_in_range(l4_addr_t cur_addr, l4_size_t remaining_size)
731 {
732 unsigned next_level = cxx::min(
733 trailing_zeroes(cur_addr),
734 trailing_zeroes(trunc_power_of_two(remaining_size)));
735 return node_for_block_level(cur_addr, next_level);
736 }
737
738 [[nodiscard]] static bool normalize_size_align(size_t &size, size_t &align)
739 {
740 size = ceil_to(size, Min_size);
741 if (L4_UNLIKELY(size == 0))
742 return false; // Either size was zero, or too large and ceil overflowed.
743
744 if (L4_UNLIKELY(!is_power_of_two(align) || align > size))
745 return false;
746 align = ceil_to(align, Min_size);
747 return true;
748 }
749
750 [[nodiscard]] static bool normalize_bounds(l4_addr_t &lower, l4_addr_t &upper,
751 size_t size, size_t align)
752 {
753 // Ceil lower bound to alignment.
754 l4_addr_t aligned_lower = ceil_to(lower, align);
755 if (L4_UNLIKELY(aligned_lower < lower))
756 return false; // Overflow, no aligned address within the bounds.
757 lower = aligned_lower;
758
759 // Truncate upper bound to alignment.
760 if (L4_UNLIKELY(upper < Min_size - 1))
761 return false;
762 // Note that upper + 1 can wrap to zero, still the result is correct.
763 upper = trunc_to(upper + 1, Min_size) - 1;
764
765 if (L4_UNLIKELY(lower > upper))
766 return false;
767
768 return upper - lower >= size - 1;
769 }
770
771 class Alloc_walk
772 {
773 public:
774 Alloc_walk(Buddy_alloc &a, l4_size_t min_size, l4_size_t max_size,
775 l4_size_t align, Mem_range alloc_bounds, Node_ptr base_node)
776 : a(a),
777 min_size(min_size),
778 max_size(max_size),
779 align(align),
780 alloc_bounds(alloc_bounds),
781 base_node(base_node),
782 min_anchor_level(min_level_required_for_size(min_size))
783 {}
784
785 Buddy_alloc &a;
786 l4_size_t const min_size;
787 l4_size_t const max_size;
788 l4_size_t const align;
789 Mem_range const alloc_bounds;
790 Node_ptr const base_node;
794 unsigned min_anchor_level;
795 Node_ptr start_node;
796 // The following members are only valid if start_node is valid.
797 Node_ptr end_node;
798 unsigned max_level = 0;
799 Mem_range free_range;
800
801 [[nodiscard]] bool add_free_node(Node_ptr const &node,
802 Mem_range const &intersect)
803 {
804 if (!start_node.valid())
805 {
806 // Apply alignment constraints.
807 auto aligned_start = ceil_to(intersect.start, align);
808 if (aligned_start > intersect.end)
809 // Aligned start after end of node.
810 return false;
811
812 start_node = node;
813 end_node = Node_ptr();
814 free_range.start = aligned_start;
815 free_range.end = intersect.end;
816 max_level = node.level;
817 }
818 else
819 {
820 end_node = node;
821 assert(free_range.end + 1 == intersect.start);
822 free_range.end = intersect.end;
823 max_level = cxx::max(max_level, node.level);
824 }
825 return true;
826 }
827
828 void reset_free_range()
829 {
830 start_node = Node_ptr();
831 max_level = 0;
832 }
833
846 template<typename CB>
847 void search(CB const &eval_alloc);
848
858 inline bool try_alloc();
859
860 private:
869 static bool may_align(unsigned cur_level, l4_size_t align)
870 {
871 return block_size_for_level(cur_level) > align;
872 }
873
874 inline void expand_start();
875 inline void expand_back();
876 };
877
878
888 inline void *alloc_size_aligned(Node_ptr const &root, unsigned alloc_level);
889
890 [[nodiscard]] void *alloc_single_node(Node_ptr node)
891 {
892 assert(node_is_free(node));
893 node_set_used(node);
894 update_parents_after_alloc(node);
895 return reinterpret_cast<void *>(from_min_size_units(block_start_addr(node)));
896 }
897
899 void *alloc_partial(Node_ptr node, l4_size_t partial_size)
900 {
901 assert(node_is_free(node));
902 Node_ptr cur_node = node;
903 l4_size_t remaining_size = partial_size;
904 for (;;)
905 {
906 l4_size_t node_size = block_size_for_level(cur_node.level);
907 // Covers entire node.
908 if (remaining_size == node_size)
909 {
910 node_set_used(cur_node);
911 remaining_size -= node_size;
912 update_parents_after_alloc(cur_node);
913 break;
914 }
915 // Covers entire left child of node.
916 else if (remaining_size > node_size / 2)
917 {
918 node_set_used(cur_node.left_child());
919 remaining_size -= node_size / 2;
920 cur_node = cur_node.right_child();
921 }
922 // Covers part of left child of node.
923 else
924 cur_node = cur_node.left_child();
925 }
926 return reinterpret_cast<void *>(from_min_size_units(block_start_addr(node)));
927 }
928
930 [[nodiscard]] void *alloc_node_range(Node_ptr node, l4_size_t size)
931 {
932 void *result = nullptr;
933 l4_addr_t cur_addr = block_start_addr(node);
934 l4_size_t remaining_size = size;
935 while (remaining_size > 0)
936 {
937 assert(node_is_free(node));
938 assert(cur_addr == block_start_addr(node));
939 l4_size_t node_size = block_size_for_level(node.level);
940 assert(node_size <= remaining_size);
941
942 void *node_ptr = alloc_single_node(node);
943 if (result == nullptr)
944 result = node_ptr;
945
946 cur_addr += node_size;
947 remaining_size -= node_size;
948 if (remaining_size == 0)
949 break;
950
951 node = next_node_in_range(cur_addr, remaining_size);
952 }
953 return result;
954 }
955
970 inline void *alloc_from_search_range(Node_ptr start_node, Node_ptr end_node,
971 Mem_range free_range, l4_size_t size);
972
973 [[nodiscard]] bool free_single_node(Node_ptr node)
974 {
975 // Double-free or free at different level than alloc.
976 if (L4_UNLIKELY(!node_is_used(node) || node_is_seperately_allocated(node)))
977 return false;
978
979 // Mark node as free.
980 node_set_free(node);
981 update_parents_after_free(node);
982 return true;
983 }
984
986 void update_parents_after_alloc(Node_ptr node)
987 {
988 Node_ptr cur = node;
989 while (!cur.is_root())
990 {
991 Node_ptr parent = cur.parent();
992 Order_free largest_order_left = node_get(parent.left_child());
993 Order_free largest_order_right = node_get(parent.right_child());
994 Order_free new_largest_order = cxx::max(largest_order_left,
995 largest_order_right);
996 if (new_largest_order == node_get(parent))
997 // Parent largest order unchanged, we can stop.
998 break;
999 node_set(parent, new_largest_order);
1000 cur = parent;
1001 }
1002 }
1003
1005 void update_parents_after_free(Node_ptr node)
1006 {
1007 Node_ptr cur = node;
1008 // Update parents in tree.
1009 while (!cur.is_root())
1010 {
1011 Node_ptr parent = cur.parent();
1012 Order_free largest_order_left = node_get(parent.left_child());
1013 Order_free largest_order_right = node_get(parent.right_child());
1014 bool left_free = largest_order_left == cur.level + 1;
1015 if (largest_order_left == largest_order_right && left_free)
1016 {
1017 // Both children are free, so mark the parent as free.
1018 node_set_free(parent);
1019 }
1020 else
1021 {
1022 Order_free new_largest_order = cxx::max(largest_order_left,
1023 largest_order_right);
1024 if (new_largest_order == node_get(parent))
1025 // Parent largest order unchanged, we can stop.
1026 break;
1027 node_set(parent, new_largest_order);
1028 }
1029 cur = parent;
1030 }
1031 }
1032
1039 void mark_subtree_free(Node_ptr node)
1040 {
1041 // The nodes of a level are stored consecutively, thus the descendants of a
1042 // node are stored as contiguous range on every level.
1043 unsigned long first_index = node.index;
1044 unsigned long nodes_in_level = 1;
1045 for (unsigned level = node.level;; --level)
1046 {
1047 __builtin_memset(&_tree[first_index], level + 1, nodes_in_level);
1048 if (level == 0)
1049 break;
1050 first_index *= 2;
1051 nodes_in_level *= 2;
1052 }
1053
1054 update_parents_after_free(node);
1055 }
1056
1057 l4_addr_t _base; // in Min_size units
1058 l4_size_t _max_mem_size; // in Min_size units
1059 unsigned _max_level;
1060 l4_uint8_t *_tree = nullptr;
1061};
1062
1063template<typename CB>
1064void
1065Buddy_alloc::Alloc_walk::search(CB const &eval_alloc)
1066{
1067 bool completed = a.tree_walk([this, &eval_alloc](Node_ptr &node)
1068 {
1069 if (!a.node_is_free(node))
1070 {
1071 // Encountered non-free node, evaluate alloc.
1072 if (start_node.valid())
1073 {
1074 if (eval_alloc())
1075 return Walk_dir::Stop;
1076
1077 if (end_node.valid())
1078 {
1079 // Skip free nodes discovered during try_alloc().
1080 node = end_node;
1081 return Walk_dir::Next;
1082 }
1083 }
1084
1085 if (!fits_level(a.node_get(node), min_anchor_level))
1086 return Walk_dir::Next;
1087
1088 return Walk_dir::Descend;
1089 }
1090
1091 Mem_range node_range = a.block_range(node);
1092 Mem_range intersect = alloc_bounds.intersect(node_range);
1093 if (!intersect.valid()
1094 || intersect.size() < block_size_for_level(min_anchor_level))
1095 return Walk_dir::Next; // out of bounds
1096
1097 if (!add_free_node(node, intersect))
1098 return Walk_dir::Next;
1099
1100 if (intersect.end < node_range.end)
1101 {
1102 // The end of the node lies outside the alloc range, we reached the end.
1103 eval_alloc();
1104 return Walk_dir::Stop;
1105 }
1106
1107 if (free_range.size() >= max_size)
1108 {
1109 if (eval_alloc())
1110 return Walk_dir::Stop;
1111
1112 if (end_node.valid())
1113 // Skip free nodes discovered during try_alloc().
1114 node = end_node;
1115 }
1116
1117 return Walk_dir::Next;
1118
1119 // OPTIMIZE:
1120 // Try to not fragment too much, only x-levels below anchor_level.
1121 // Take remaining size in relation to min_anchor size into consideration.
1122 // Introduce a fragmentation / wasted memory threshold that decides
1123 // whether to try size align alloc, or descend further.
1124 }, base_node);
1125
1126 // In case the walk stopped because it reached the end of the tree, we need to
1127 // evaluate a pending free range.
1128 if (completed && start_node.valid())
1129 eval_alloc();
1130}
1131
1132bool
1133Buddy_alloc::Alloc_walk::try_alloc()
1134{
1135 assert(start_node.valid());
1136
1137 // OPTIMIZE:
1138 // How to best position the allocation?
1139 // Idea could be to try size-aligned allocation for start and end node to avoid unnecessary splitting.
1140 // Or some limits relative to max_level / min_anchor_level.
1141 // So in other words, expand start_node and end_node in multiple phases, with different conditions.
1142 // 1. Only nodes greater/equal min_anchor_level, front and then back if necessary.
1143 // 2. Only nodes greater/equal min_anchor_level - 1, front and then back if necessary.
1144 // 3. ...
1145 // (if the level available at the front is larger than at the back, but
1146 // the later is sufficient to serve the allocation, prefer it)
1147
1148 // Need to search before (searches for nodes below min_anchor_level).
1149 expand_start();
1150 assert(is_aligned_to(free_range.start, align));
1151 expand_back();
1152
1153 if (free_range.size() < min_size)
1154 {
1155 // Failed to alloc...
1156 reset_free_range();
1157 return false;
1158 }
1159
1160 return true;
1161}
1162
1163void
1164Buddy_alloc::Alloc_walk::expand_start()
1165{
1166 while (free_range.size() < max_size
1167 && free_range.start == a.block_start_addr(start_node))
1168 {
1169 // Searching further back does not make sense, since we know
1170 // everything we might find will not align.
1171 if (!may_align(start_node.level, align))
1172 break;
1173
1174 // OPTIMIZE: Since we only search levels below min_anchor_level, we
1175 // can do some lower bound calculation to figure out if search even
1176 // makes sense.
1177 Node_ptr prev = a.prev_adjacent_free(start_node, base_node);
1178 if (!prev.valid())
1179 break;
1180
1181 Mem_range intersect = alloc_bounds.intersect(a.block_range(prev));
1182 if (!intersect.valid())
1183 break;
1184
1185 auto aligned_start = ceil_to(intersect.start, align);
1186 if (aligned_start > intersect.end)
1187 // Aligned start after end of node.
1188 break;
1189
1190 if (!end_node.valid())
1191 end_node = start_node;
1192
1193 start_node = prev;
1194 max_level = cxx::max(max_level, prev.level);
1195 free_range.start = aligned_start;
1196 }
1197}
1198
1199void
1200Buddy_alloc::Alloc_walk::expand_back()
1201{
1202 while (free_range.size() < max_size
1203 && (!end_node.valid() || free_range.end == a.block_end_addr(end_node)))
1204 {
1205 Node_ptr next = a.next_adjacent_free(
1206 !end_node.valid() ? start_node : end_node, base_node);
1207 if (!next.valid())
1208 break;
1209
1210 Mem_range intersect = alloc_bounds.intersect(a.block_range(next));
1211 if (!intersect.valid())
1212 break;
1213
1214 end_node = next;
1215 max_level = cxx::max(max_level, next.level);
1216 free_range.end = intersect.end;
1217 }
1218}
1219
1220void *
1221Buddy_alloc::alloc_size_aligned(Node_ptr const &root, unsigned alloc_level)
1222{
1223 // Check the largest free order of the root node, mostly to prevent descending
1224 // to children shadowed by a fully allocated root node, but also as an early
1225 // exit in case it cannot fulfill the allocation.
1226 if (!fits_level(node_get(root), alloc_level))
1227 return nullptr; // out of memory
1228
1229 Node_ptr node = root;
1230 while (node.level > alloc_level)
1231 {
1232 auto left = node.left_child();
1233 auto right = node.right_child();
1234 Order_free largest_order_left = node_get(left);
1235 Order_free largest_order_right = node_get(right);
1236 if (fits_level(largest_order_left, alloc_level))
1237 {
1238 if (largest_order_left > largest_order_right
1239 && fits_level(largest_order_right, alloc_level))
1240 node = right;
1241 else
1242 node = left;
1243 }
1244 else if (largest_order_right > alloc_level)
1245 node = right;
1246 else
1247 break;
1248 }
1249
1250 if (!fits_level(node_get(node), alloc_level))
1251 return nullptr; // out of memory
1252
1253 return alloc_single_node(node);
1254}
1255
1256void *
1257Buddy_alloc::alloc_from_search_range(Node_ptr start_node, Node_ptr end_node,
1258 Mem_range free_range, l4_size_t size)
1259{
1260 if (free_range.start == block_start_addr(start_node) && !end_node.valid())
1261 // Allocate from a single node.
1262 return alloc_partial(start_node, size);
1263
1264
1265 // Find actual start node, taking intersect into account.
1266 while (free_range.start != block_start_addr(start_node))
1267 {
1268 start_node = block_start_addr(start_node.right_child()) > free_range.start
1269 ? start_node.left_child()
1270 : start_node.right_child();
1271 }
1272
1273 // Allocate multiple nodes, which are descendants of the single original
1274 // start_node.
1275 if (!end_node.valid())
1276 {
1277 // Find actual start node, in case the start node is still larger than the
1278 // requested size.
1279 while (block_size_for_level(start_node.level) > size)
1280 start_node = start_node.left_child();
1281
1282 return alloc_node_range(start_node, size);
1283 }
1284
1285 // Find actual end node, taking intersect into account.
1286 while (free_range.end != block_end_addr(end_node))
1287 end_node = block_end_addr(end_node.left_child()) >= free_range.end
1288 ? end_node.left_child()
1289 : end_node.right_child();
1290
1291 assert(free_range.start == block_start_addr(start_node));
1292 // min() in case we decided to only allocate a sub-range of the detected
1293 // range, for example because of granularity constraints.
1294 l4_size_t front_size =
1295 cxx::min<l4_size_t>(block_start_addr(end_node) - free_range.start, size);
1296 void *mem = alloc_node_range(start_node, front_size);
1297
1298 // The above alloc_free_range() could take the entire range, including the
1299 // tail, but alloc_partial is more efficient due to less
1300 // update_parents_after_alloc() calls.
1301 if (front_size != size)
1302 alloc_partial(end_node, size - front_size);
1303 return mem;
1304}
1305
1306void
1307Buddy_alloc::add_mem(void *mem, size_t size)
1308{
1309 assert(initialized());
1310
1311 l4_addr_t start = reinterpret_cast<l4_addr_t>(mem);
1312 l4_addr_t aligned_start = ceil_to(start, Min_size);
1313 // Block at address zero must never be handed out by alloc(), because nullptr
1314 // represents a failed allocation.
1315 if (aligned_start == 0)
1316 aligned_start = Min_size;
1317
1318 size_t skipped_size = aligned_start - start;
1319 if (size <= skipped_size)
1320 return;
1321 size = trunc_to(size - skipped_size, Min_size);
1322
1323 aligned_start = to_min_size_units(aligned_start);
1324 size = to_min_size_units(size);
1325
1326 while (size > 0)
1327 {
1328 // Add the largest size-aligned block that fits the remaining size and
1329 // mark its entire subtree as free. The naive way of adding Min_size
1330 // blocks bottom up, is slow due to the frequent parent updates.
1331 Node_ptr node = next_node_in_range(aligned_start, size);
1332 size_t block_size = block_size_for_level(node.level);
1333
1334 if (L4_UNLIKELY(!node_is_used(node)))
1335 {
1336 // The node is free or split, i.e. at least parts of the memory were
1337 // already added before.
1338 if constexpr (Print_warnings)
1339 printf("Detected addition of already available memory at %#lx (size=%#zx).\n",
1340 from_min_size_units(aligned_start), from_min_size_units(block_size));
1341 return;
1342 }
1343 mark_subtree_free(node);
1344
1345 aligned_start += block_size;
1346 size -= block_size;
1347 }
1348}
1349
1350void *
1351Buddy_alloc::alloc(size_t size, size_t align, l4_addr_t lower, l4_addr_t upper)
1352{
1353 if (L4_UNLIKELY(!initialized()))
1354 return nullptr;
1355
1356 if (L4_UNLIKELY(!normalize_size_align(size, align)))
1357 return nullptr;
1358
1359 if (L4_UNLIKELY(!normalize_bounds(lower, upper, size, align)))
1360 return nullptr;
1361
1362 // Convert arguments to Min_size units.
1363 size = to_min_size_units(size);
1364 align = to_min_size_units(align);
1365 lower = to_min_size_units(lower);
1366 upper = upper / Min_size; // inclusive, thus not aligned, truncation is fine.
1367
1368 unsigned size_aligned_level = level_for_block_size(ceil_power_of_two(size));
1369 Mem_range alloc_range(lower, upper);
1370 Node_ptr base_node = find_bounded_root(size_aligned_level, alloc_range);
1371
1372 // In case the alloc range contains the base node, so either larger then the
1373 // root node or exactly aligning with a subnode, we can use the unbounded
1374 // allocation algorithm on the subtree spanned by the base node.
1375 if (alloc_range.contains(block_range(base_node)) && is_power_of_two(size))
1376 {
1377 if (void *block = alloc_size_aligned(base_node, size_aligned_level);
1378 L4_LIKELY(block != nullptr))
1379 return block;
1380
1381 if (size == align)
1382 return nullptr;
1383
1384 // Search for non-size-aligned allocation opportunity...
1385 }
1386
1387 Alloc_walk walk(*this, size, size, align, alloc_range, base_node);
1388 // OPTIMIZE ideas:
1389 // - Special case for small sizes, where min_anchor_block_size==size.
1390 // - For smaller sizes just try to find a perfect aligned fit. And only if
1391 // that fails try harder.
1392 // - For larger sizes above e.g. 64 MB try harder, with Alloc_walk.
1393 // - If the block we split is much larger than our target size, maybe
1394 // continue searching?
1395 walk.search([&]() { return walk.try_alloc(); });
1396 if (!walk.start_node.valid())
1397 return nullptr;
1398
1399 return alloc_from_search_range(walk.start_node, walk.end_node,
1400 walk.free_range, walk.min_size);
1401}
1402
1403void *
1404Buddy_alloc::alloc_max(size_t min, size_t *max, size_t align,
1405 size_t granularity, l4_addr_t lower, l4_addr_t upper)
1406{
1407 if (L4_UNLIKELY(!initialized()))
1408 return nullptr;
1409
1410 if (L4_UNLIKELY(!is_power_of_two(granularity) || granularity > align))
1411 return nullptr;
1412 granularity = ceil_to(granularity, Min_size);
1413
1414 if (L4_UNLIKELY(!normalize_size_align(min, align)))
1415 return nullptr;
1416
1417 // The allocation size is truncated to a multiple of the granularity in the
1418 // end. Thus also the minimum size must be a multiple of the granularity.
1419 // Otherwise the truncation could yield a size below minimum size.
1420 min = ceil_to(min, granularity);
1421 if (L4_UNLIKELY(min == 0))
1422 return nullptr; // Too large, overflow.
1423
1424 l4_size_t max_ = trunc_to(*max, granularity);
1425 if (L4_UNLIKELY(min > max_))
1426 return nullptr;
1427
1428 if (L4_UNLIKELY(!normalize_bounds(lower, upper, min, align)))
1429 return nullptr;
1430
1431 // Convert arguments to Min_size units.
1432 min = to_min_size_units(min);
1433 max_ = to_min_size_units(max_);
1434 align = to_min_size_units(align);
1435 granularity = to_min_size_units(granularity);
1436 lower = to_min_size_units(lower);
1437 upper = upper / Min_size; // inclusive, thus not aligned, truncation is fine.
1438
1439 unsigned size_aligned_max_level = level_for_block_size(trunc_power_of_two(max_));
1440 Mem_range alloc_range(lower, upper);
1441 Node_ptr base_node = find_bounded_root(size_aligned_max_level, alloc_range);
1442 if (alloc_range.contains(block_range(base_node)) && is_power_of_two(max_))
1443 {
1444 if (void *block = alloc_size_aligned(base_node, size_aligned_max_level);
1445 L4_LIKELY(block != nullptr))
1446 {
1447 *max = from_min_size_units(max_);
1448 return block;
1449 }
1450
1451 // Search for smaller or non-size-aligned allocation opportunity...
1452 }
1453
1454 // Tree walk that only considers nodes within [lower, upper] to search for
1455 // suitable free node.
1456
1457 Node_ptr best_start_node;
1458 Node_ptr best_end_node;
1459 Mem_range best_free_range;
1460
1461 Alloc_walk walk(*this, min, max_, align, alloc_range, base_node);
1462 walk.search([&]()
1463 {
1464 if (!walk.try_alloc())
1465 return false;
1466
1467 // OPTIMIZE: Take into account other parameters, such as wasted memory /
1468 // splitting of larger blocks.
1469 if (!best_start_node.valid()
1470 || walk.free_range.size() > best_free_range.size())
1471 {
1472 best_start_node = walk.start_node;
1473 best_end_node = walk.end_node;
1474 best_free_range = walk.free_range;
1475
1476 if (best_free_range.size() >= max_)
1477 return true; // found area that can provide max
1478
1479 // Raise the min_anchor_level dynamically.
1480 walk.min_anchor_level = min_level_required_for_size(
1481 best_free_range.size());
1482 }
1483
1484 // Continue the scan until we find a range >= max_ or reach the end.
1485 walk.reset_free_range();
1486 return false;
1487 });
1488
1489 if (!best_start_node.valid())
1490 return nullptr;
1491
1492 max_ = cxx::min(walk.max_size, best_free_range.size());
1493 max_ = trunc_to(max_, granularity);
1494 *max = from_min_size_units(max_);
1495 return alloc_from_search_range(best_start_node, best_end_node,
1496 best_free_range, max_);
1497}
1498
1499void
1500Buddy_alloc::free(void *block, size_t size)
1501{
1502 if (L4_UNLIKELY(!initialized()))
1503 return;
1504
1505 size = ceil_to(size, Min_size);
1506 if (L4_UNLIKELY(size == 0))
1507 return; // Either zero size or too large and ceil overflowed.
1508
1509 if (L4_UNLIKELY(block == nullptr))
1510 {
1511 // alloc() never hands out a block address of zero.
1512 if constexpr (Print_warnings)
1513 printf("Invalid free of nullptr (size=%#zx).\n", size);
1514 return;
1515 }
1516
1517 l4_addr_t block_addr = reinterpret_cast<l4_addr_t>(block);
1518 if (L4_UNLIKELY(!is_aligned_to(block_addr, Min_size)))
1519 {
1520 if constexpr (Print_warnings)
1521 printf("Invalid free of misaligned address %#lx (size=%#zx).\n",
1522 block_addr, size);
1523 return;
1524 }
1525
1526 // Convert arguments to Min_size units.
1527 size = to_min_size_units(size);
1528 block_addr = to_min_size_units(block_addr);
1529
1530 // Reject blocks outside of the memory area managed by the.
1531 if (L4_UNLIKELY(block_addr < _base || size > _max_mem_size
1532 || block_addr - _base > _max_mem_size - size))
1533 {
1534 if constexpr (Print_warnings)
1535 printf("Invalid free of %#lx (size=%#zx) outside of the managed area.\n",
1536 from_min_size_units(block_addr), from_min_size_units(size));
1537 return;
1538 }
1539
1540 // Single size-aligned block.
1541 if (is_power_of_two(size) && is_aligned_to(block_addr, size))
1542 {
1543 Node_ptr node = node_for_block(block_addr, size);
1544 if (!free_single_node(node))
1545 if constexpr (Print_warnings)
1546 printf("Double free of %#lx (size=%#zx) or free with different size.\n",
1547 from_min_size_units(block_addr), from_min_size_units(size));
1548 return;
1549 }
1550
1551 // From the alignment of the start address we figure out an upper bound level,
1552 // from which we search left until we find a used block at the given address.
1553 unsigned base_level = cxx::min(trailing_zeroes(block_addr), _max_level);
1554 // Since used nodes never can be shadowed (only free nodes can), it is safe to
1555 // start at an arbitrary level of the tree. If we encounter a used node, we
1556 // can be sure that it is actually used.
1557 Node_ptr node = node_for_block_level(block_addr, base_level);
1558
1559 // Descend from "upper bound level" to node at the same address marked as used.
1560 while (!node_is_used(node) || node_is_seperately_allocated(node))
1561 {
1562 if (L4_UNLIKELY(node.is_leaf()))
1563 {
1564 if constexpr (Print_warnings)
1565 printf("Double free of %#lx (size=%#zx).\n",
1566 from_min_size_units(block_addr), from_min_size_units(size));
1567 return;
1568 }
1569
1570 node = node.left_child();
1571 }
1572
1573 // Now we have the first block of the allocation, now we iterate until we
1574 // reached end of allocation.
1575 l4_addr_t cur_addr = block_addr;
1576 size_t remaining_size = size;
1577 for (;;)
1578 {
1579 l4_size_t block_size = block_size_for_level(node.level);
1580 if (L4_UNLIKELY(block_size > remaining_size))
1581 {
1582 if constexpr (Print_warnings)
1583 printf("Free of %#lx with wrong size (block_size=%#zx vs. remaining_size=%#zx).\n",
1584 from_min_size_units(block_addr),
1585 from_min_size_units(block_size),
1586 from_min_size_units(remaining_size));
1587 return;
1588 }
1589
1590 // OPTIMIZE: Batch the parent update_parents_after_free() calls?
1591 if (!free_single_node(node))
1592 if constexpr (Print_warnings)
1593 printf("Double free of %#lx (size=%#zx) or free with different size.\n",
1594 from_min_size_units(block_addr), from_min_size_units(size));
1595
1596 cur_addr += block_size;
1597 remaining_size -= block_size;
1598 if (remaining_size == 0)
1599 break; // we are done :)
1600
1601 node = next_node_in_range(cur_addr, remaining_size);
1602 }
1603}
1604
1610size_t
1612{
1613 if (L4_UNLIKELY(!initialized()))
1614 return 0;
1615
1616 size_t avail = 0;
1617 tree_walk_free([&avail](Node_ptr const &node)
1618 { avail += block_size_for_level(node.level); });
1619 return from_min_size_units(avail);
1620}
1621
1622template<typename DBG>
1623void
1624Buddy_alloc::dump_free(DBG &out) const
1625{
1626 if (L4_UNLIKELY(!initialized()))
1627 {
1628 out.printf("Buddy_alloc [UNITIALIZED]\n");
1629 return;
1630 }
1631
1632 l4_uint64_t total = 0;
1633 out.printf("Buddy_alloc [%zu,%u]\n", Min_size, _max_level);
1634
1635 // Stack-allocate entries to cover the maximum possible number of levels.
1636 size_t levels_avail[(sizeof(l4_addr_t) * 8) - Min_order + 1] = {};
1637 assert(_max_level < cxx::array_size(levels_avail));
1638 tree_walk_free([&levels_avail](Node_ptr const &node)
1639 { levels_avail[node.level] += 1; });
1640
1641 auto format_size = [](l4_uint64_t *size, unsigned threshold) -> char const *
1642 {
1643 static_assert(Min_size >= 1024); // 2^64 == 16 EiB
1644 static constexpr char const *const unitstr[7] =
1645 { "Byte", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB" };
1646
1647 unsigned i;
1648 for (i = 0; i + 1 < cxx::array_size(unitstr) && *size > (threshold << 10); ++i)
1649 *size >>= 10;
1650 return unitstr[i];
1651 };
1652
1653 for (unsigned level = 0; level <= _max_level; level++)
1654 {
1655 l4_uint64_t level_size = block_size_for_level(level) * l4_uint64_t{Min_size};
1656 l4_uint64_t size = level_size;
1657 char const *unit = format_size(&size, 2);
1658 out.printf(" %2u: [%4llu %s]", level, size, unit);
1659
1660 l4_uint64_t avail = levels_avail[level] * level_size;
1661 size_t avail_blocks = levels_avail[level];
1662 size = avail;
1663 unit = format_size(&size, 8);
1664 out.cprintf(" %zu free blocks == %4llu %-4s (%llu bytes)\n",
1665 avail_blocks, size, unit, avail);
1666
1667 total += avail;
1668 }
1669
1670 l4_uint64_t size = total;
1671 char const *unit = format_size(&size, 8);
1672 out.printf("sum of available memory: %llu %s (%llu bytes)\n",
1673 size, unit, total);
1674}
1675
1676} // namespace cxx
Buddy allocator backed by a perfect binary tree.
Definition buddy_alloc:57
void free(void *block, size_t size)
Return a free memory block to the allocator.
Definition buddy_alloc:1500
void init(l4_addr_t min_addr, l4_addr_t max_addr, unsigned char *metadata_addr, size_t metadata_size)
Initialize the allocator.
Definition buddy_alloc:139
void * alloc_max(size_t min, size_t *max, size_t align, size_t granularity, l4_addr_t lower=0, l4_addr_t upper=~0UL)
Allocate a memory block of min <= size <= max.
Definition buddy_alloc:1404
size_t avail() const
Get the amount of available memory.
Definition buddy_alloc:1611
static constexpr size_t metadata_bytes(l4_addr_t min_addr, l4_addr_t max_addr)
Return metadata size in bytes required by the buddy allocator.
Definition buddy_alloc:124
void * alloc(size_t size, size_t align, l4_addr_t lower=0, l4_addr_t upper=~0UL)
Allocate a memory block.
Definition buddy_alloc:1351
void add_mem(void *mem, size_t size)
Add memory to the allocator.
Definition buddy_alloc:1307
L4 compiler related defines.
unsigned int l4_size_t
Unsigned size type.
Definition l4int.h:22
unsigned long l4_addr_t
Address type.
Definition l4int.h:34
unsigned char l4_uint8_t
Unsigned 8bit value.
Definition l4int.h:25
unsigned long long l4_uint64_t
Unsigned 64bit value.
Definition l4int.h:31
#define L4_PAGESHIFT
Size of a page, log2-based.
Definition consts.h:26
#define L4_UNLIKELY(x)
Expression is unlikely to execute.
Definition compiler.h:295
#define L4_LIKELY(x)
Expression is likely to execute.
Definition compiler.h:294
Common constants.
Fixed sized integer types, generic version.
Our C++ library.
Definition arith:11