/* pool.c -- runtime memory pools * * These functions implement an allocation pool. * * - Pool mpd_makepool (name, el_size, el_max, init, re_init) * initializes a pool of descriptors. The name is used only to print * error messages. Each descriptor is of size el_size; a maximum of * el_max descriptors are allowed. * * The functions "init" and "re_init" are supplied to initialize * descriptors. The "init" function is called only the first time * a descriptor is returned; it should not expect any fields to have * predictable values. It must use INIT_LOCK to allocate every * field of type Mutex in the descriptor type. The function "re_init" * is called every time a descriptor is returned, thus it can assume * that values are the same as when the descriptor was released. It * must use RESET_LOCK to initialize every field of type Mutex * in the descriptor type. * * Either "init" or "re_init" may be NULL if no such function is needed. * * - Ptr mpd_addpool (Pool p) allocates a new descriptor from pool p and * returns a pointer to it. * * - void mpd_delpool (Pool p, Ptr el) returns el to the pool. * * - void mpd_eachpool (Pool p, Func f) invokes the function *f on * each currently allocated descriptor in pool p. The argument * to function f is a pointer to a descriptor. *f must NOT * allocate or deallocate items from p. * * - void mpd_killpool (Pool p) deallocates all storage associated with pool p. * * As far as users of a pool are concerned, type Pool is a magic cookie; * there are no useful external operations on it. * * Internally, pool allocates two extra pointers for every element. The * pointers are hidden; they are actually stored just before the address * returned by mpd_addpool (). They are used to keep a doubly linked list of * allocated elements, and one of them is used for linking the free list. * * These pool functions access no mutexes except the pool mutexes. * However, the function passed to mpd_eachpool can potentially access * a mutex. */ #include "rts.h" /* * Links for pool elements */ typedef struct linkage { struct linkage *next; /* next element in list */ struct linkage *prev; /* previous element in list */ } PoolLinks; /* * Representation of a Pool. */ struct pool_str { char *name; /* name of this pool */ char *lockname; /* name of this pool's lock (for debugging) */ PoolLinks *free; /* free list */ PoolLinks *inuse; /* in-use list */ PoolLinks *blk; /* next block allocated */ int el_size; /* element size */ int el_max; /* maximum number of elements allowed */ int el_cur; /* current number of elements allocated */ Func init; /* function to call on new elements */ Func re_init; /* function to call on used elements */ Mutex pmutex; /* protect table access. Access to fields * of individual element are protected by * its own lock if necessary. */ }; /* * translate between pointers the user sees and pointers to PoolLinks */ #define user_addr(el) ((Ptr)(el) + sizeof (PoolLinks)) #define pool_addr(el) ((PoolLinks *)((Ptr)(el) - sizeof (PoolLinks))) /* * initialize a pool */ Pool mpd_makepool (name, el_size, el_max, init, re_init) char *name; /* name of pool */ int el_size; /* size of elements */ int el_max; /* max number of elements */ Func init; /* function to initialize new elements */ Func re_init; /* function to re-initialize elements */ { Pool pl; pl = (Pool) mpd_alloc (sizeof (struct pool_str)); pl->name = name; /* name */ pl->lockname = (char *) mpd_alloc (strlen (name) + 12); sprintf (pl->lockname, "%s->pmutex", pl->name); pl->free = 0; /* free list */ pl->inuse = 0; /* in-use list */ pl->blk = 0; /* allocated block */ pl->el_size = el_size + sizeof (PoolLinks); /* element size */ pl->el_max = el_max; /* max number of elements */ pl->el_cur = 0; /* current # of elements allocated */ pl->init = init; pl->re_init = re_init; INIT_LOCK (pl->pmutex, pl->lockname); return pl; } /* * allocate an element from pool p */ Ptr mpd_addpool (pl) Pool pl; { PoolLinks *res, *el; int i; LOCK (pl->pmutex, "mpd_addpool"); if (pl->free != 0) { res = pl->free; pl->free = res->next; } else if (pl->el_cur < pl->el_max) { PoolLinks *blk; int how_many; /* allocate a block of elements */ if (pl->el_cur + ALCPOOL > pl->el_max) how_many = pl->el_max - pl->el_cur; else how_many = ALCPOOL; blk = (PoolLinks *) mpd_alloc ((how_many + 1) * pl->el_size); /* link blk into block list */ /* first element in blk is reserved for use as a block link */ blk->next = pl->blk; pl->blk = blk; /* call init on each element and link into the free list */ /* (Caution: slightly funky pointer arithmetic) */ el = pl->free = (PoolLinks *) ((char *) blk + pl->el_size); for (i = 0; i < how_many - 1; i += 1) { PoolLinks *next_el; if (pl->init) /* initialize this element */ (*pl->init) (user_addr (el)); /* link it into the free list */ next_el = (PoolLinks *) ((char *) el + pl->el_size); el->next = next_el; el = next_el; } if (pl->init) /* initialize the last element */ (*pl->init) (user_addr (el)); el->next = 0; pl->el_cur += how_many; /* finally, get res from free list */ res = pl->free; pl->free = res->next; } else { char buf[100]; sprintf (buf, "too many %s", pl->name); mpd_abort (buf); } /* link res into inuse chain */ res->prev = 0; res->next = pl->inuse; if (pl->inuse != 0) pl->inuse->prev = res; pl->inuse = res; if (pl->re_init) (*pl->re_init) (user_addr (res)); DEBUG (D_ALLOC, "addpool @%06lX %s", user_addr (res), pl->name, 0); UNLOCK (pl->pmutex, "mpd_addpool"); return user_addr (res); } /* * put el back in pool pl. */ void mpd_delpool (pl, el_ptr) Pool pl; Ptr el_ptr; { PoolLinks * el; LOCK (pl->pmutex, "mpd_delpool"); el = pool_addr (el_ptr); DEBUG (D_ALLOC, "delpool @%06lX %s", el_ptr, pl->name, 0); /* extract el from the inuse list */ if (el->prev == 0) { /* first element */ pl->inuse = el->next; if (pl->inuse) pl->inuse->prev = 0; } else if (el->next == 0) { /* last element, but not only one */ el->prev->next = 0; } else { /* in the middle */ el->prev->next = el->next; el->next->prev = el->prev; } /* put element on the free list, which is singly linked */ if (!DBFLAGS (D_NOFREE)) { /* can be inhibited by debug flag */ el->next = pl->free; pl->free = el; } UNLOCK (pl->pmutex, "mpd_delpool"); } /* * invoke f once for each element in pl->inuse. * f must NOT alter the inuse list. And nothing * that f calls may cause pmutex to be grabbed * again (this problem goes away with a generalized * locking facility that allows nesting, as in * the special case of mpd_queue_mutex). */ void mpd_eachpool (pl, f) Pool pl; Func f; { PoolLinks *el; LOCK (pl->pmutex, "mpd_eachpool"); for (el = pl->inuse; el != 0; el = el->next) (*f) (user_addr (el)); UNLOCK (pl->pmutex, "mpd_eachpool"); } #ifdef NEVER /* can re-enable if this is ever needed */ /* * deallocate all storage associated with a pool */ void mpd_killpool (p) Pool p; { PoolLinks *el; for (el = p->blk; el != 0;) { PoolLinks *next_blk = el->next; UNMALLOC ((Ptr) el); /* we really could use a un_init function here to deallocate * anything that the init function allocated. */ el = next_blk; } FREE_LOCK (pl->pmutex, pl->lockname, 0); UNMALLOC ((Ptr) p->lockname); UNMALLOC ((Ptr) p); } #endif