genode/repos/base/src/base/allocator/slab.cc

297 lines
6.1 KiB
C++

/*
* \brief Slab allocator implementation
* \author Norman Feske
* \date 2006-05-16
*/
/*
* Copyright (C) 2006-2013 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU General Public License version 2.
*/
#include <base/slab.h>
#include <util/misc_math.h>
using namespace Genode;
/****************
** Slab block **
****************/
/**
* Placement operator - tool for directly calling a constructor
*/
inline void *operator new(size_t, void *at) { return at; }
void Slab_block::slab(Slab *slab)
{
_slab = slab;
_avail = _slab->num_elem();
next = prev = 0;
for (unsigned i = 0; i < _avail; i++)
state(i, FREE);
}
Slab_entry *Slab_block::slab_entry(int idx)
{
/*
* The slab slots start after the state array that consists
* of 'num_elem' bytes. We align the first slot to a four-aligned
* address.
*/
return (Slab_entry *)&_data[align_addr(_slab->num_elem(), 2)
+ _slab->entry_size()*idx];
}
int Slab_block::slab_entry_idx(Slab_entry *e) {
return ((addr_t)e - (addr_t)slab_entry(0))/_slab->entry_size(); }
void *Slab_block::alloc()
{
size_t num_elem = _slab->num_elem();
for (unsigned i = 0; i < num_elem; i++)
if (state(i) == FREE) {
state(i, USED);
Slab_entry *e = slab_entry(i);
e->occupy(this);
return e->addr();
}
return 0;
}
Slab_entry *Slab_block::first_used_entry()
{
size_t num_elem = _slab->num_elem();
for (unsigned i = 0; i < num_elem; i++)
if (state(i) == USED)
return slab_entry(i);
return 0;
}
void Slab_block::inc_avail(Slab_entry *e)
{
/* mark slab entry as free */
int idx = slab_entry_idx(e);
state(idx, FREE);
_avail++;
/* search previous block with higher avail value than this' */
Slab_block *at = prev;
while (at && (at->avail() < _avail))
at = at->prev;
/*
* If we already are the first block or our avail value is lower than the
* previous block, do not reposition the block in the list.
*/
if (prev == 0 || at == prev)
return;
/* reposition block in list after block with higher avail value */
_slab->remove_sb(this);
_slab->insert_sb(this, at);
}
void Slab_block::dec_avail()
{
_avail--;
/* search subsequent block with lower avail value than this' */
Slab_block *at = this;
while (at->next && at->next->avail() > _avail)
at = at->next;
if (at == this) return;
_slab->remove_sb(this);
_slab->insert_sb(this, at);
}
/**********
** Slab **
**********/
Slab::Slab(size_t slab_size, size_t block_size, Slab_block *initial_sb,
Allocator *backing_store)
: _slab_size(slab_size),
_block_size(block_size),
_first_sb(initial_sb),
_initial_sb(initial_sb),
_alloc_state(false),
_backing_store(backing_store)
{
/*
* Calculate number of entries per slab block.
*
* The 'sizeof(umword_t)' is for the alignment of the first slab entry.
* The 1 is for one byte state entry.
*/
_num_elem = (_block_size - sizeof(Slab_block) - sizeof(umword_t))
/ (entry_size() + 1);
/* if no initial slab block was specified, try to get one */
if (!_first_sb && _backing_store)
_first_sb = _new_slab_block();
/* init first slab block */
if (_first_sb)
_first_sb->slab(this);
}
Slab::~Slab()
{
/* free backing store */
while (_first_sb) {
Slab_block *sb = _first_sb;
remove_sb(_first_sb);
/*
* Only free slab blocks that we allocated. This is not the case for
* the '_initial_sb' that we got as constructor argument.
*/
if (_backing_store && (sb != _initial_sb))
_backing_store->free(sb, _block_size);
}
}
Slab_block *Slab::_new_slab_block()
{
void *sb = 0;
if (!_backing_store || !_backing_store->alloc(_block_size, &sb))
return 0;
/* call constructor by using the placement new operator */
return new(sb) Slab_block(this);
}
void Slab::remove_sb(Slab_block *sb)
{
Slab_block *prev = sb->prev;
Slab_block *next = sb->next;
if (prev) prev->next = next;
if (next) next->prev = prev;
if (_first_sb == sb)
_first_sb = next;
sb->prev = sb->next = 0;
}
void Slab::insert_sb(Slab_block *sb, Slab_block *at)
{
/* determine next-pointer where to assign the current sb */
Slab_block **nextptr_to_sb = at ? &at->next : &_first_sb;
/* insert current sb */
sb->next = *nextptr_to_sb;
*nextptr_to_sb = sb;
/* update prev-pointer or succeeding block */
if (sb->next)
sb->next->prev = sb;
sb->prev = at;
}
bool Slab::num_free_entries_higher_than(int n)
{
int cnt = 0;
for (Slab_block *b = _first_sb; b && b->avail() > 0; b = b->next) {
cnt += b->avail();
if (cnt > n)
return true;
}
return false;
}
bool Slab::alloc(size_t size, void **out_addr)
{
/* sanity check if first slab block is gone */
if (!_first_sb) return false;
/*
* If we run out of slab, we need to allocate a new slab block. For the
* special case that this block is allocated using the allocator that by
* itself uses the slab allocator, such an allocation could cause up to
* three new slab_entry allocations. So we need to ensure to allocate the
* new slab block early enough - that is if there are only three free slab
* entries left.
*/
if (_backing_store && !num_free_entries_higher_than(3) && !_alloc_state) {
/* allocate new block for slab */
_alloc_state = true;
Slab_block *sb = _new_slab_block();
_alloc_state = false;
if (!sb) return false;
/*
* The new block has the maximum number of available slots and
* so we can insert it at the beginning of the sorted block
* list.
*/
insert_sb(sb);
}
*out_addr = _first_sb->alloc();
return *out_addr == 0 ? false : true;
}
void Slab::free(void *addr)
{
Slab_entry *e = addr ? Slab_entry::slab_entry(addr) : 0;
if (e) e->free();
}
void *Slab::first_used_elem()
{
for (Slab_block *b = _first_sb; b; b = b->next) {
/* skip completely free slab blocks */
if (b->avail() == _num_elem)
continue;
/* found a block with used elements - return address of the first one */
Slab_entry *e = b->first_used_entry();
if (e) return e->addr();
}
return 0;
}
size_t Slab::consumed() const
{
/* count number of slab blocks */
unsigned sb_cnt = 0;
for (Slab_block *sb = _first_sb; sb; sb = sb->next)
sb_cnt++;
return sb_cnt * _block_size;
}