247 lines
6.6 KiB
C
247 lines
6.6 KiB
C
/**
|
|
* \file
|
|
* \brief Simple slab allocator.
|
|
*
|
|
* This file implements a simple slab allocator. It allocates blocks of a fixed
|
|
* size from a pool of contiguous memory regions ("slabs").
|
|
*/
|
|
|
|
/*
|
|
* Copyright (c) 2008, 2009, 2010, ETH Zurich.
|
|
* All rights reserved.
|
|
*
|
|
* This file is distributed under the terms in the attached LICENSE file.
|
|
* If you do not find this file, copies can be found by writing to:
|
|
* ETH Zurich D-INFK, Haldeneggsteig 4, CH-8092 Zurich. Attn: Systems Group.
|
|
*/
|
|
|
|
#include <aos/aos.h>
|
|
#include <aos/slab.h>
|
|
#include <aos/static_assert.h>
|
|
|
|
struct block_head {
|
|
struct block_head *next;///< Pointer to next block in free list
|
|
};
|
|
|
|
STATIC_ASSERT_SIZEOF(struct block_head, SLAB_BLOCK_HDRSIZE);
|
|
|
|
/**
|
|
* \brief Initialise a new slab allocator
|
|
*
|
|
* \param slabs Pointer to slab allocator instance, to be filled-in
|
|
* \param blocksize Size of blocks to be allocated by this allocator
|
|
* \param refill_func Pointer to function to call when out of memory (or NULL)
|
|
*/
|
|
void slab_init(struct slab_allocator *slabs, size_t blocksize,
|
|
slab_refill_func_t refill_func)
|
|
{
|
|
slabs->slabs = NULL;
|
|
slabs->blocksize = SLAB_REAL_BLOCKSIZE(blocksize);
|
|
slabs->refill_func = refill_func;
|
|
}
|
|
|
|
|
|
/**
|
|
* \brief Add memory (a new slab) to a slab allocator
|
|
*
|
|
* \param slabs Pointer to slab allocator instance
|
|
* \param buf Pointer to start of memory region
|
|
* \param buflen Size of memory region (in bytes)
|
|
*/
|
|
void slab_grow(struct slab_allocator *slabs, void *buf, size_t buflen)
|
|
{
|
|
/* setup slab_head structure at top of buffer */
|
|
assert(buflen > sizeof(struct slab_head));
|
|
struct slab_head *head = buf;
|
|
buflen -= sizeof(struct slab_head);
|
|
buf = (char *)buf + sizeof(struct slab_head);
|
|
|
|
/* calculate number of blocks in buffer */
|
|
size_t blocksize = slabs->blocksize;
|
|
assert(buflen / blocksize <= UINT32_MAX);
|
|
head->free = head->total = buflen / blocksize;
|
|
assert(head->total > 0);
|
|
|
|
/* enqueue blocks in freelist */
|
|
struct block_head *bh = head->blocks = buf;
|
|
for (uint32_t i = head->total; i > 1; i--) {
|
|
buf = (char *)buf + blocksize;
|
|
bh->next = buf;
|
|
bh = buf;
|
|
}
|
|
bh->next = NULL;
|
|
|
|
/* enqueue slab in list of slabs */
|
|
head->next = slabs->slabs;
|
|
slabs->slabs = head;
|
|
}
|
|
|
|
/**
|
|
* \brief Allocate a new block from the slab allocator
|
|
*
|
|
* \param slabs Pointer to slab allocator instance
|
|
*
|
|
* \returns Pointer to block on success, NULL on error (out of memory)
|
|
*/
|
|
void *slab_alloc(struct slab_allocator *slabs)
|
|
{
|
|
errval_t err;
|
|
/* find a slab with free blocks */
|
|
struct slab_head *sh;
|
|
for (sh = slabs->slabs; sh != NULL && sh->free == 0; sh = sh->next);
|
|
|
|
if (sh == NULL) {
|
|
/* out of memory. try refill function if we have one */
|
|
if (!slabs->refill_func) {
|
|
return NULL;
|
|
} else {
|
|
err = slabs->refill_func(slabs);
|
|
if (err_is_fail(err)) {
|
|
DEBUG_ERR(err, "slab refill_func failed");
|
|
return NULL;
|
|
}
|
|
for (sh = slabs->slabs; sh != NULL && sh->free == 0; sh = sh->next);
|
|
if (sh == NULL) {
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* dequeue top block from freelist */
|
|
assert(sh != NULL);
|
|
struct block_head *bh = sh->blocks;
|
|
assert(bh != NULL);
|
|
sh->blocks = bh->next;
|
|
sh->free--;
|
|
|
|
return bh;
|
|
}
|
|
|
|
/**
|
|
* \brief Free a block to the slab allocator
|
|
*
|
|
* \param slabs Pointer to slab allocator instance
|
|
* \param block Pointer to block previously returned by #slab_alloc
|
|
*/
|
|
void slab_free(struct slab_allocator *slabs, void *block)
|
|
{
|
|
if (block == NULL) {
|
|
return;
|
|
}
|
|
|
|
struct block_head *bh = (struct block_head *)block;
|
|
|
|
/* find matching slab */
|
|
struct slab_head *sh;
|
|
size_t blocksize = slabs->blocksize;
|
|
for (sh = slabs->slabs; sh != NULL; sh = sh->next) {
|
|
/* check if block falls inside this slab */
|
|
uintptr_t slab_limit = (uintptr_t)sh + sizeof(struct slab_head)
|
|
+ blocksize * sh->total;
|
|
if ((uintptr_t)bh > (uintptr_t)sh && (uintptr_t)bh < slab_limit) {
|
|
break;
|
|
}
|
|
}
|
|
assert(sh != NULL);
|
|
|
|
/* re-enqueue in slab's free list */
|
|
bh->next = sh->blocks;
|
|
sh->blocks = bh;
|
|
sh->free++;
|
|
assert(sh->free <= sh->total);
|
|
}
|
|
|
|
/**
|
|
* \brief Returns the count of free blocks in the allocator
|
|
*
|
|
* \param slabs Pointer to slab allocator instance
|
|
*
|
|
* \returns Free block count
|
|
*/
|
|
size_t slab_freecount(struct slab_allocator *slabs)
|
|
{
|
|
size_t ret = 0;
|
|
|
|
for (struct slab_head *sh = slabs->slabs; sh != NULL; sh = sh->next) {
|
|
ret += sh->free;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* \brief General-purpose slab refill
|
|
*
|
|
* Allocates and maps a number of memory pages to the slab allocator.
|
|
*
|
|
* \param slabs Pointer to slab allocator instance
|
|
* \param bytes (Minimum) amount of memory to map
|
|
*/
|
|
errval_t slab_refill_pages(struct slab_allocator *slabs, size_t bytes)
|
|
{
|
|
errval_t err;
|
|
struct capref cap;
|
|
|
|
err = slot_alloc(&cap);
|
|
if (err_is_fail(err)) {
|
|
return err_push(err, LIB_ERR_SLOT_ALLOC);
|
|
}
|
|
|
|
err = slab_refill_no_pagefault(slabs, cap, bytes);
|
|
if (err_is_fail(err)) {
|
|
slot_free(cap);
|
|
return err;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief refills the slab allocator without causing a page fault
|
|
*
|
|
* @param slabs the slab allocator to be refilled
|
|
* @param frame_slot an empty capability slot for the frames
|
|
* @param minbytes the minimum number of bytes to allocate
|
|
*
|
|
* @return SYS_ERR_OK on success, error code on failure
|
|
*/
|
|
errval_t slab_refill_no_pagefault(struct slab_allocator *slabs, struct capref frame_slot,
|
|
size_t minbytes)
|
|
{
|
|
// Refill the slot allocator without causing a page-fault
|
|
errval_t err;
|
|
size_t alloc_bytes;
|
|
err = frame_create(frame_slot, minbytes, &alloc_bytes);
|
|
if (err_is_fail(err)) return err_push(err, LIB_ERR_FRAME_ALLOC);
|
|
|
|
struct paging_state *paging_state = get_current_paging_state();
|
|
void *vaddr = NULL;
|
|
err = paging_map_frame_attr(
|
|
paging_state, &vaddr,
|
|
alloc_bytes, frame_slot, VREGION_FLAGS_READ_WRITE
|
|
);
|
|
if (err_is_fail(err)) {
|
|
// TODO: free the RAM, but without freeing the frame slot
|
|
cap_delete(frame_slot);
|
|
return err;
|
|
}
|
|
|
|
slab_grow(slabs, vaddr, alloc_bytes);
|
|
|
|
return SYS_ERR_OK;
|
|
}
|
|
|
|
/**
|
|
* \brief General-purpose implementation of a slab allocate/refill function
|
|
*
|
|
* Allocates and maps a single page (FIXME: make configurable) and adds it
|
|
* to the allocator.
|
|
*
|
|
* \param slabs Pointer to slab allocator instance
|
|
*/
|
|
errval_t slab_default_refill(struct slab_allocator *slabs)
|
|
{
|
|
return slab_refill_pages(slabs, BASE_PAGE_SIZE);
|
|
}
|