/* * BIRD Resource Manager * * (c) 1998--2000 Martin Mares * (c) 2021 Maria Matejka * * Can be freely distributed and used under the terms of the GNU GPL. */ #include #include #include #include "nest/bird.h" #include "lib/resource.h" #include "lib/string.h" #include "lib/rcu.h" /** * DOC: Resource pools * * Resource pools (&pool) are just containers holding a list of * other resources. Freeing a pool causes all the listed resources * to be freed as well. Each existing &resource is linked to some pool * except for a root pool which isn't linked anywhere, so all the * resources form a tree structure with internal nodes corresponding * to pools and leaves being the other resources. * * Example: Almost all modules of BIRD have their private pool which * is freed upon shutdown of the module. */ static void pool_dump(resource *, unsigned); static void pool_free(resource *); static resource *pool_lookup(resource *, unsigned long); static struct resmem pool_memsize(resource *P); static struct resclass pool_class = { "Pool", sizeof(pool), pool_free, pool_dump, pool_lookup, pool_memsize }; pool root_pool; static void rp_init(pool *z, struct domain_generic *dom, const char *name) { ASSERT_DIE(DG_IS_LOCKED(dom)); z->name = name; z->domain = dom; z->inside = (TLIST_LIST(resource)) {}; } /** * rp_new - create a resource pool * @p: parent pool * @name: pool name (to be included in debugging dumps) * * rp_new() creates a new resource pool inside the specified * parent pool. */ pool * rp_new(pool *p, struct domain_generic *dom, const char *name) { pool *z = ralloc(p, &pool_class); if (dg_order(p->domain) > dg_order(dom)) bug("Requested reverse order pool creation: %s (%d) can't be a parent of %s (%d)", domain_name(p->domain), dg_order(p->domain), domain_name(dom), dg_order(dom)); if ((dg_order(p->domain) == dg_order(dom)) && (p->domain != dom)) bug("Requested incomparable order pool creation: %s (%d) can't be a parent of %s (%d)", domain_name(p->domain), dg_order(p->domain), domain_name(dom), dg_order(dom)); rp_init(z, dom, name); return z; } pool * rp_vnewf(pool *p, struct domain_generic *dom, const char *fmt, va_list args) { pool *z = rp_new(p, dom, NULL); z->name = mb_vsprintf(p, fmt, args); return z; } pool * rp_newf(pool *p, struct domain_generic *dom, const char *fmt, ...) { va_list args; va_start(args, fmt); pool *z = rp_vnewf(p, dom, fmt, args); va_end(args); return z; } #define POOL_LOCK \ struct domain_generic *dom = p->domain; \ int locking = !DG_IS_LOCKED(dom); \ if (locking) \ DG_LOCK(dom); \ #define POOL_UNLOCK if (locking) DG_UNLOCK(dom);\ void rp_free(pool *p) { ASSERT_DIE(DG_IS_LOCKED(p->domain)); rfree(p); } static void pool_free(resource *P) { pool *p = (pool *) P; POOL_LOCK; WALK_TLIST_DELSAFE(resource, r, &p->inside) { r->class->free(r); xfree(r); } POOL_UNLOCK; } static void pool_dump(resource *P, unsigned indent) { pool *p = (pool *) P; POOL_LOCK; debug("%s\n", p->name); WALK_TLIST_DELSAFE(resource, r, &p->inside) rdump(r, indent + 3); POOL_UNLOCK; } static struct resmem pool_memsize(resource *P) { pool *p = (pool *) P; struct resmem sum = { .effective = 0, .overhead = sizeof(pool) + ALLOC_OVERHEAD, }; POOL_LOCK; WALK_TLIST(resource, r, &p->inside) { struct resmem add = rmemsize(r); sum.effective += add.effective; sum.overhead += add.overhead; } POOL_UNLOCK; return sum; } static resource * pool_lookup(resource *P, unsigned long a) { pool *p = (pool *) P; resource *q = NULL; POOL_LOCK; WALK_TLIST(resource, r, &p->inside) if (r->class->lookup && (q = r->class->lookup(r, a))) break; POOL_UNLOCK; return q; } static pool * resource_parent(resource *r) { return SKIP_BACK(pool, inside, resource_enlisted(r)); } /** * rmove - move a resource * @res: resource * @p: pool to move the resource to * * rmove() moves a resource from one pool to another. */ void rmove(void *res, pool *p) { resource *r = res; pool *orig = resource_parent(r); ASSERT_DIE(DG_IS_LOCKED(orig->domain)); ASSERT_DIE(DG_IS_LOCKED(p->domain)); resource_rem_node(&orig->inside, r); resource_add_tail(&p->inside, r); } /** * rfree - free a resource * @res: resource * * rfree() frees the given resource and all information associated * with it. In case it's a resource pool, it also frees all the objects * living inside the pool. * * It works by calling a class-specific freeing function. */ void rfree(void *res) { resource *r = res; if (!r) return; pool *orig = resource_parent(r); ASSERT_DIE(DG_IS_LOCKED(orig->domain)); resource_rem_node(&orig->inside, r); r->class->free(r); r->class = NULL; xfree(r); } /** * rdump - dump a resource * @res: resource * * This function prints out all available information about the given * resource to the debugging output. * * It works by calling a class-specific dump function. */ void rdump(void *res, unsigned indent) { char x[16]; resource *r = res; bsprintf(x, "%%%ds%%p ", indent); debug(x, "", r); if (r) { debug("%s ", r->class->name); r->class->dump(r, indent); } else debug("NULL\n"); } struct resmem rmemsize(void *res) { resource *r = res; if (!r) return (struct resmem) {}; if (!r->class->memsize) return (struct resmem) { .effective = r->class->size - sizeof(resource), .overhead = ALLOC_OVERHEAD + sizeof(resource), }; return r->class->memsize(r); } /** * ralloc - create a resource * @p: pool to create the resource in * @c: class of the new resource * * This function is called by the resource classes to create a new * resource of the specified class and link it to the given pool. * Allocated memory is zeroed. Size of the resource structure is taken * from the @size field of the &resclass. */ void * ralloc(pool *p, struct resclass *c) { ASSERT_DIE(DG_IS_LOCKED(p->domain)); resource *r = xmalloc(c->size); bzero(r, c->size); r->class = c; resource_add_tail(&p->inside, r); return r; } /** * rlookup - look up a memory location * @a: memory address * * This function examines all existing resources to see whether * the address @a is inside any resource. It's used for debugging * purposes only. * * It works by calling a class-specific lookup function for each * resource. */ void rlookup(unsigned long a) { resource *r; debug("Looking up %08lx\n", a); if (r = pool_lookup(&root_pool.r, a)) rdump(r, 3); else debug("Not found.\n"); } /** * resource_init - initialize the resource manager * * This function is called during BIRD startup. It initializes * all data structures of the resource manager and creates the * root pool. */ void resource_init(void) { rcu_init(); resource_sys_init(); rp_init(&root_pool, the_bird_domain.the_bird, "Root"); tmp_init(&root_pool, the_bird_domain.the_bird); } _Thread_local struct tmp_resources tmp_res; void tmp_init(pool *p, struct domain_generic *dom) { tmp_res.lp = lp_new_default(p); tmp_res.parent = p; tmp_res.pool = rp_new(p, dom, "TMP"); tmp_res.domain = dom; } void tmp_flush(void) { ASSERT_DIE(DG_IS_LOCKED(tmp_res.domain)); lp_flush(tmp_linpool); rp_free(tmp_res.pool); tmp_res.pool = rp_new(tmp_res.parent, tmp_res.domain, "TMP"); } /** * DOC: Memory blocks * * Memory blocks are pieces of contiguous allocated memory. * They are a bit non-standard since they are represented not by a pointer * to &resource, but by a void pointer to the start of data of the * memory block. All memory block functions know how to locate the header * given the data pointer. * * Example: All "unique" data structures such as hash tables are allocated * as memory blocks. */ struct mblock { resource r; unsigned size; uintptr_t data_align[0]; byte data[0]; }; static void mbl_free(resource *r UNUSED) { } static void mbl_debug(resource *r, unsigned indent UNUSED) { struct mblock *m = (struct mblock *) r; debug("(size=%d)\n", m->size); } static resource * mbl_lookup(resource *r, unsigned long a) { struct mblock *m = (struct mblock *) r; if ((unsigned long) m->data <= a && (unsigned long) m->data + m->size > a) return r; return NULL; } static struct resmem mbl_memsize(resource *r) { struct mblock *m = (struct mblock *) r; return (struct resmem) { .effective = m->size, .overhead = ALLOC_OVERHEAD + sizeof(struct mblock), }; } static struct resclass mb_class = { "Memory", 0, mbl_free, mbl_debug, mbl_lookup, mbl_memsize }; /** * mb_alloc - allocate a memory block * @p: pool * @size: size of the block * * mb_alloc() allocates memory of a given size and creates * a memory block resource representing this memory chunk * in the pool @p. * * Please note that mb_alloc() returns a pointer to the memory * chunk, not to the resource, hence you have to free it using * mb_free(), not rfree(). */ void * mb_alloc(pool *p, unsigned size) { ASSERT_DIE(DG_IS_LOCKED(p->domain)); struct mblock *b = xmalloc(sizeof(struct mblock) + size); b->r.class = &mb_class; b->r.n = (struct resource_node) {}; resource_add_tail(&p->inside, &b->r); b->size = size; return b->data; } /** * mb_allocz - allocate and clear a memory block * @p: pool * @size: size of the block * * mb_allocz() allocates memory of a given size, initializes it to * zeroes and creates a memory block resource representing this memory * chunk in the pool @p. * * Please note that mb_allocz() returns a pointer to the memory * chunk, not to the resource, hence you have to free it using * mb_free(), not rfree(). */ void * mb_allocz(pool *p, unsigned size) { void *x = mb_alloc(p, size); bzero(x, size); return x; } /** * mb_realloc - reallocate a memory block * @m: memory block * @size: new size of the block * * mb_realloc() changes the size of the memory block @m to a given size. * The contents will be unchanged to the minimum of the old and new sizes; * newly allocated memory will be uninitialized. Contrary to realloc() * behavior, @m must be non-NULL, because the resource pool is inherited * from it. * * Like mb_alloc(), mb_realloc() also returns a pointer to the memory * chunk, not to the resource, hence you have to free it using * mb_free(), not rfree(). */ void * mb_realloc(void *m, unsigned size) { struct mblock *b = SKIP_BACK(struct mblock, data, m); struct pool *p = resource_parent(&b->r); ASSERT_DIE(DG_IS_LOCKED(p->domain)); b = xrealloc(b, sizeof(struct mblock) + size); b->size = size; resource_update_node(&p->inside, &b->r); return b->data; } /** * mb_free - free a memory block * @m: memory block * * mb_free() frees all memory associated with the block @m. */ void mb_free(void *m) { if (!m) return; struct mblock *b = SKIP_BACK(struct mblock, data, m); rfree(&b->r); } #define STEP_UP(x) ((x) + (x)/2 + 4) void buffer_realloc(void **buf, unsigned *size, unsigned need, unsigned item_size) { unsigned nsize = MIN(*size, need); while (nsize < need) nsize = STEP_UP(nsize); *buf = mb_realloc(*buf, nsize * item_size); *size = nsize; }