arena: fix __arena_add() quantum alignment issue
__arena_add() was asserting that the span we were adding was quantum
'aligned.' However, our source gives us allocations according to *its*
quantum, not our quantum. If our source was e.g. bytes and we give out
pages, then our source could give us a span that does not match our
quantum.
This is fine, albeit a source of fragmentation. We just make sure our
arena's bt tracks the object we want (on a quantum boundary and a
multiple of quantum). span_bt will track whatever we actually got from
our source.
Another note: ALIGNED() checks on quantum were currently wrong - there's
nothing in the arena code that forced quantum to be a power of two. At
least, I didn't see it, and don't want to restrict us to that yet.
Though we'll likely have issues with align, non-aligned quantums, and
qcaches. For instance, if you ask for a quantum of 3, with an align of
64, the qcaches were told to do an align of 3 (quantum).
Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
diff --git a/kern/src/arena.c b/kern/src/arena.c
index 2c90628..a9adc33 100644
--- a/kern/src/arena.c
+++ b/kern/src/arena.c
@@ -169,8 +169,22 @@
arena->afunc = afunc;
arena->ffunc = ffunc;
arena->source = source;
- if (source)
+ if (source) {
assert(afunc && ffunc);
+ /* When we import, there may be a quantum mismatch such that our
+ * source hands us spans that are not sufficient for our
+ * quantum. For instance, s->q == 1, a->q = 4096, and they hand
+ * us 4096 bytes at 4095. If any alloc in our source's quantum,
+ * when rounded up to our quantum would change that alloc, we
+ * need to import 2x the size to be sure a span would work.
+ *
+ * All s allocs are divided (% x == 0) by s->q. We want to know
+ * if all s allocs (our spans) are also divided by a->q, in
+ * which case we don't need to import extra. This is true when
+ * a->q divides s->q. (s->q is a multiple of a->q). */
+ if (source->quantum % arena->quantum)
+ arena->import_scale = 1;
+ }
arena->amt_total_segs = 0;
arena->amt_alloc_segs = 0;
arena->nr_allocs_ever = 0;
@@ -641,31 +655,14 @@
return ret;
}
-/* It's probably a kernel bug if we're adding the wrong sized segments, whether
- * via direct add, a source import, or creation. */
-static void assert_quantum_alignment(struct arena *arena, void *base,
- size_t size)
-{
- if (!ALIGNED(base, arena->quantum))
- panic("Unaligned base %p for arena %s, quantum %p, source %s",
- base, arena->name, arena->quantum,
- arena->source ? arena->source->name : "none");
- if (!ALIGNED(size, arena->quantum))
- panic("Unaligned size %p for arena %s, quantum %p, source %s",
- size, arena->name, arena->quantum,
- arena->source ? arena->source->name : "none");
-}
-
/* Adds segment [@base, @base + @size) to @arena. We'll add a span tag if the
* arena had a source. */
static void *__arena_add(struct arena *arena, void *base, size_t size,
int flags)
{
struct btag *bt, *span_bt;
+ uintptr_t limit;
- /* These are just sanity checks. Our client is the kernel, and it could
- * mess with us in other ways, such as adding overlapping spans. */
- assert_quantum_alignment(arena, base, size);
assert(base < base + size);
spin_lock_irqsave(&arena->lock);
/* Make sure there are two, bt and span. */
@@ -674,18 +671,35 @@
return NULL;
}
bt = __get_btag(arena);
+ /* Our source may have a different (and smaller) quantum than us.
+ * span_bt will track the source's allocation, and our bt will track a
+ * subset of those bytes that are multiples our quantum. */
+ limit = (uintptr_t)base + size;
+ bt->start = ROUNDUP((uintptr_t)base, arena->quantum);
+ bt->size = ROUNDDOWN(limit - bt->start, arena->quantum);
+ /* Caller should have been careful about this. get_more_resources()
+ * should have a large enough import_amt / import_scale. */
+ if (bt->start >= limit || !bt->size) {
+ warn("Added segment too small! (a: %s, b:%p, s:%p, q:%p)",
+ arena->name, base, size, arena->quantum);
+ spin_unlock_irqsave(&arena->lock);
+ return NULL;
+ }
if (arena->source) {
span_bt = __get_btag(arena);
span_bt->start = (uintptr_t)base;
span_bt->size = size;
span_bt->status = BTAG_SPAN;
+ /* This is dirty, but it saves 8 bytes in every BT that would
+ * only be used by span BTs. We're not on any list, so
+ * misc-link is available. We also need to keep track of the
+ * size of this arena's BT so we can detect when it is free. */
+ *(uintptr_t *)&span_bt->misc_link = bt->size;
/* Note the btag span is not on any list, but it is in all_segs
*/
__insert_btag(&arena->all_segs, span_bt);
}
- bt->start = (uintptr_t)base;
- bt->size = size;
- arena->amt_total_segs += size;
+ arena->amt_total_segs += bt->size;
__track_free_seg(arena, bt);
__insert_btag(&arena->all_segs, bt);
spin_unlock_irqsave(&arena->lock);
@@ -1136,10 +1150,10 @@
rb_p = rb_prev(&bt->all_link);
if (rb_p) {
bt_p = container_of(rb_p, struct btag, all_link);
+ /* If the prev is a span tag, we know it is ours. We just need
+ * to know if our bt covers the entire import span size. */
if ((bt_p->status == BTAG_SPAN) &&
- (bt_p->start == bt->start) &&
- (bt_p->size == bt->size)) {
-
+ (*(uintptr_t *)&bt_p->misc_link == bt->size)) {
*to_free_addr = (void*)bt_p->start;
*to_free_sz = bt_p->size;
/* Note the span was not on a free list */