L4Re Operating System Framework
Interface and Usage Documentation
Loading...
Searching...
No Matches
tree_alloc
1// vim:set ft=cpp: -*- Mode: C++ -*-
2/*
3 * (c) 2025 Viktor Reusch
4 *
5 * based on the list_alloc by
6 * (c) 2008-2009 Alexander Warg <warg@os.inf.tu-dresden.de>,
7 * Torsten Frenzel <frenzel@os.inf.tu-dresden.de>
8 * economic rights: Technische Universität Dresden (Germany)
9 *
10 * License: see LICENSE.spdx (in this directory or the directories above)
11 */
12
13#pragma once
14
15#include <l4/cxx/arith>
16#include <l4/cxx/avl_tree>
17#include <l4/cxx/iostream>
18#include <l4/cxx/minmax>
19#include <l4/cxx/type_traits>
20#include <l4/sys/consts.h>
21
22namespace cxx
23{
24
29{
30private:
31 friend class Tree_alloc_sanity_guard;
32
33 struct Mem_block : public Avl_tree_node
34 {
35 unsigned long size;
36
37 unsigned long addr() { return reinterpret_cast<unsigned long>(this); }
38 unsigned long end() { return addr() + size; }
39 };
40
41 struct Mem_block_get_key
42 {
43 typedef unsigned long Key_type;
44 static Key_type key_of(Mem_block const *e)
45 {
46 return reinterpret_cast<unsigned long>(e);
47 }
48 };
49
51
52 inline void check_overlap(void *, unsigned long) const;
53 inline void sanity_check_list(char const *, char const *,
54 bool unmerged = true) const;
55 inline void merge(Mem_block *, Mem_block *);
56
57public:
70 inline void free(void *block, unsigned long size, bool initial_free = false);
71
86 inline void *alloc(unsigned long size, unsigned long align,
87 unsigned long lower = 0, unsigned long upper = ~0UL);
88
109 inline void *alloc_max(unsigned long min, unsigned long *max,
110 unsigned long align, unsigned granularity,
111 unsigned long lower = 0, unsigned long upper = ~0UL);
112
118 inline unsigned long avail() const;
119
120 template<typename DBG>
121 void dump_free_list(DBG &out) const;
122};
123
124#if !defined(CXX_TREE_ALLOC_SANITY)
125class Tree_alloc_sanity_guard
126{
127public:
128 Tree_alloc_sanity_guard(Tree_alloc const *, char const *, bool = true) {}
129};
130
131void
132Tree_alloc::check_overlap(void *, unsigned long) const
133{}
134
135void
136Tree_alloc::sanity_check_list(char const *, char const *, bool) const
137{}
138
139#else
140
141class Tree_alloc_sanity_guard
142{
143private:
144 Tree_alloc const *a;
145 char const *func;
146
147public:
148 Tree_alloc_sanity_guard(Tree_alloc const *a, char const *func,
149 bool unmerged = false)
150 : a(a), func(func)
151 {
152 a->sanity_check_list(func, "entry", unmerged);
153 }
154
155 ~Tree_alloc_sanity_guard() { a->sanity_check_list(func, "exit"); }
156};
157
158void
159Tree_alloc::check_overlap(void *b, unsigned long s) const
160{
161 unsigned long const mb_align =
162 (1UL << arith::Ld<sizeof(Mem_block)>::value) - 1;
163 if (reinterpret_cast<unsigned long>(b) & mb_align)
164 {
165 L4::cerr << "Tree_alloc(FATAL): trying to free unaligned memory: " << b
166 << " align=" << arith::Ld<sizeof(Mem_block)>::value << "\n";
167 }
168
169 for (auto const &c : tree)
170 {
171 unsigned long x_s = (unsigned long)b;
172 unsigned long x_e = x_s + s;
173 unsigned long b_s = (unsigned long)&c;
174 unsigned long b_e = b_s + c.size;
175
176 if ((x_s >= b_s && x_s < b_e) || (x_e > b_s && x_e <= b_e)
177 || (b_s >= x_s && b_s < x_e) || (b_e > x_s && b_e <= x_e))
178 {
179 L4::cerr << "Tree_alloc(FATAL): trying to free memory that "
180 "is already free: \n ["
181 << (void *)x_s << '-' << (void *)x_e << ") overlaps ["
182 << (void *)b_s << '-' << (void *)b_e << ")\n";
183 }
184 }
185}
186
187void
188Tree_alloc::sanity_check_list(char const *func, char const *info, bool unmerged) const
189{
190 for (auto c = tree.begin(); c != tree.end(); ++c)
191 {
192 auto next = c + 1;
193
194 if (next != tree.end())
195 {
196 if (c->addr() >= next->addr())
197 {
198 L4::cerr << "Tree_alloc(FATAL): " << func << '(' << info
199 << "): list order violation\n";
200 }
201
202 auto cmp = +[](unsigned long a, unsigned long b) { return a >= b; };
203 if (unmerged)
204 cmp = +[](unsigned long a, unsigned long b) { return a > b; };
205 if (cmp(c->addr() + c->size, next->addr()))
206 {
207 L4::cerr << "Tree_alloc(FATAL): " << func << '(' << info
208 << "): list order violation\n";
209 }
210 }
211 }
212}
213
214#endif
215
216void
217Tree_alloc::free(void *block, unsigned long size, bool initial_free)
218{
219 [[maybe_unused]] Tree_alloc_sanity_guard guard(this, __func__);
220
221 unsigned long const mb_align =
222 (1UL << arith::Ld<sizeof(Mem_block)>::value) - 1;
223
224 if (initial_free)
225 {
226 // enforce alignment constraint on initial memory
227 unsigned long nblock =
228 (reinterpret_cast<unsigned long>(block) + mb_align) & ~mb_align;
229 size =
230 (size - (nblock - reinterpret_cast<unsigned long>(block))) & ~mb_align;
231 block = reinterpret_cast<void *>(nblock);
232 }
233 else
234 // blow up size to the minimum aligned size
235 size = (size + mb_align) & ~mb_align;
236
237 check_overlap(block, size);
238
239 Mem_block *m;
240 auto addr = reinterpret_cast<unsigned long>(block);
241 // Extend preceding region if it is adjacent.
242 auto lower = _tree.last_less_equal_node(addr);
243 if (lower && lower->end() == addr)
244 {
245 lower->size += size;
246 m = lower;
247 }
248 else
249 {
250 m = reinterpret_cast<Mem_block *>(block);
251 m->size = size;
252 _tree.insert(m);
253 }
254
255 // Merge with following region if it is adjacent.
256 auto next = _tree.remove(m->end());
257 if (next)
258 m->size += next->size;
259}
260
261void *
262Tree_alloc::alloc_max(unsigned long min, unsigned long *max,
263 unsigned long align, unsigned granularity,
264 unsigned long lower, unsigned long upper)
265{
266 [[maybe_unused]] Tree_alloc_sanity_guard guard(this, __func__);
267
268 unsigned char const mb_bits = arith::Ld<sizeof(Mem_block)>::value;
269 unsigned long const mb_align = (1UL << mb_bits) - 1;
270
271 // blow minimum up to at least the minimum aligned size of a Mem_block
272 min = l4_round_size(min, mb_bits);
273 // truncate maximum to at least the size of a Mem_block
274 *max = l4_trunc_size(*max, mb_bits);
275 // truncate maximum size according to granularity
276 *max = *max & ~(granularity - 1UL);
277
278 if (min > *max)
279 return nullptr;
280
281 unsigned long almask = align ? (align - 1UL) : 0;
282
283 // minimum alignment is given by the size of a Mem_block
284 if (almask < mb_align)
285 almask = mb_align;
286
287 Mem_block *fit = nullptr;
288 unsigned long max_fit = 0;
289 unsigned long a_lower = (lower + almask) & ~almask;
290
291 for (auto &c : _tree)
292 {
293 // address of free memory block
294 unsigned long n_start = c.addr();
295
296 // block too small, next
297 // XXX: maybe we can skip this and just do the test below
298 if (c.size < min)
299 continue;
300
301 // block outside region, next
302 if (upper < n_start || a_lower > n_start + c.size)
303 continue;
304
305 // aligned start address within the free block
306 unsigned long a_start = (n_start + almask) & ~almask;
307
308 // check if aligned start address is behind the block, next
309 if (a_start - n_start >= c.size)
310 continue;
311
312 a_start = a_start < a_lower ? a_lower : a_start;
313
314 // end address would overflow, next
315 if (min > ~0UL - a_start)
316 continue;
317
318 // block outside region, next
319 if (a_start + min - 1UL > upper)
320 continue;
321
322 // remaining size after subtracting the padding for the alignment
323 unsigned long r_size = c.size - a_start + n_start;
324
325 // upper limit can limit maximum size
326 if (a_start + r_size - 1UL > upper)
327 r_size = upper - a_start + 1UL;
328
329 // round down according to granularity
330 r_size &= ~(granularity - 1UL);
331
332 // block too small
333 if (r_size < min)
334 continue;
335
336 if (r_size >= *max)
337 {
338 fit = &c;
339 max_fit = *max;
340 break;
341 }
342
343 if (r_size > max_fit)
344 {
345 max_fit = r_size;
346 fit = &c;
347 }
348 }
349
350 if (fit)
351 {
352 unsigned long n_start = fit->addr();
353 unsigned long a_lower = (lower + almask) & ~almask;
354 unsigned long a_start = (n_start + almask) & ~almask;
355 a_start = a_start < a_lower ? a_lower : a_start;
356 unsigned long r_size = fit->size - a_start + n_start;
357
358 if (a_start > n_start)
359 fit->size -= r_size;
360 else
361 _tree.remove(fit->addr());
362
363 *max = max_fit;
364 if (r_size == max_fit)
365 return reinterpret_cast<void *>(a_start);
366
367 Mem_block *m = reinterpret_cast<Mem_block *>(a_start + max_fit);
368 m->size = r_size - max_fit;
369 _tree.insert(m);
370 return reinterpret_cast<void *>(a_start);
371 }
372
373 return 0;
374}
375
376void *
377Tree_alloc::alloc(unsigned long size, unsigned long align, unsigned long lower,
378 unsigned long upper)
379{
380 [[maybe_unused]] Tree_alloc_sanity_guard guard(this, __func__);
381
382 unsigned long const mb_align =
383 (1UL << arith::Ld<sizeof(Mem_block)>::value) - 1;
384
385 // blow up size to the minimum aligned size
386 size = (size + mb_align) & ~mb_align;
387
388 unsigned long almask = align ? (align - 1UL) : 0;
389
390 // minimum alignment is given by the size of a Mem_block
391 if (almask < mb_align)
392 almask = mb_align;
393
394 unsigned long a_lower = (lower + almask) & ~almask;
395
396 for (auto &c : _tree)
397 {
398 // address of free memory block
399 unsigned long n_start = c.addr();
400
401 // block too small, next
402 // XXX: maybe we can skip this and just do the test below
403 if (c.size < size)
404 continue;
405
406 // block outside region, next
407 if (upper < n_start || a_lower > n_start + c.size)
408 continue;
409
410 // aligned start address within the free block
411 unsigned long a_start = (n_start + almask) & ~almask;
412
413 // block too small after alignment, next
414 if (a_start - n_start >= c.size)
415 continue;
416
417 a_start = a_start < a_lower ? a_lower : a_start;
418
419 // end address would overflow, next
420 if (size > ~0UL - a_start)
421 continue;
422
423 // block outside region, next
424 if (a_start + size - 1UL > upper)
425 continue;
426
427 // remaining size after subtracting the padding
428 // for the alignment
429 unsigned long r_size = c.size - a_start + n_start;
430
431 // block too small
432 if (r_size < size)
433 continue;
434
435 if (a_start > n_start)
436 // have free space before the allocated block
437 // shrink the block
438 c.size -= r_size;
439 else
440 // drop the block
441 _tree.remove(c.addr());
442
443 // allocated the whole remaining space
444 if (r_size == size)
445 return reinterpret_cast<void *>(a_start);
446
447 // add a new free block behind the allocated block
448 Mem_block *m = reinterpret_cast<Mem_block *>(a_start + size);
449 m->size = r_size - size;
450 _tree.insert(m);
451 return reinterpret_cast<void *>(a_start);
452 }
453
454 return 0;
455}
456
457unsigned long
459{
460 [[maybe_unused]] Tree_alloc_sanity_guard guard(this, __func__);
461 unsigned long a = 0;
462 for (auto const &c : _tree)
463 a += c.size;
464
465 return a;
466}
467
468template<typename DBG>
469void
470Tree_alloc::dump_free_list(DBG &out) const
471{
472 for (auto const &c : _tree)
473 {
474 static constexpr char const *const unitstr[4] =
475 { "Byte", "KiB", "MiB", "GiB" };
476
477 unsigned sz = c.size;
478 unsigned i;
479 for (i = 0; i < cxx::array_size(unitstr) && sz > 8 << 10; ++i)
480 sz >>= 10;
481
482 out.printf("%12p - %12p (%u %s)\n",
483 &c, reinterpret_cast<char const *>(&c) + c.size - 1, sz, unitstr[i]);
484 }
485}
486
487} // namespace cxx
AVL tree.
Node of an AVL tree.
Definition avl_tree:30
A generic AVL tree.
Definition avl_tree:101
Standard tree-based allocator.
Definition tree_alloc:29
void * alloc_max(unsigned long min, unsigned long *max, unsigned long align, unsigned granularity, unsigned long lower=0, unsigned long upper=~0UL)
Allocate a memory block of min <= size <= max.
Definition tree_alloc:262
void * alloc(unsigned long size, unsigned long align, unsigned long lower=0, unsigned long upper=~0UL)
Allocate a memory block.
Definition tree_alloc:377
void free(void *block, unsigned long size, bool initial_free=false)
Return a free memory block to the allocator.
Definition tree_alloc:217
unsigned long avail() const
Get the amount of available memory.
Definition tree_alloc:458
l4_addr_t l4_trunc_size(l4_addr_t address, unsigned char bits) L4_NOTHROW
Round an address down to the next lower flexpage with size bits.
Definition consts.h:470
l4_addr_t l4_round_size(l4_addr_t value, unsigned char bits) L4_NOTHROW
Round value up to the next alignment with bits size.
Definition consts.h:495
IO Stream.
Common constants.
BasicOStream cerr
Standard error stream.
Our C++ library.
Definition arith:11
Computes the binary logarithm of the given number at compile time.
Definition arith:49