slab allocator:

* Implemented a more elaborated raw memory allocation backend (MemoryManager).
  We allocate 8 MB areas whose pages we allocate and map when needed. An area is
  divided into equally-sized chunks which form the basic units of allocation. We
  have areas with three possible chunk sizes (small, medium, large), which is
  basically what the ObjectCache implementations were using anyway.
* Added "uint32 flags" parameter to several of the slab allocator's object
  cache and object depot functions. E.g. object_depot_store() potentially wants
  to allocate memory for a magazine. But also in pure freeing functions it
  might eventually become useful to have those flags, since they could end up
  deleting an area, which might not be allowable in all situations. We should
  introduce specific flags to indicate that.
* Reworked the block allocator. Since the MemoryManager allocates block-aligned
  areas, maintains a hash table for lookup, and maps chunks to object caches,
  we can quickly find out which object cache a to be freed allocation belongs
  to and thus don't need the boundary tags anymore.
* Reworked the slab boot strap process. We allocate from the initial area only
  when really necessary, i.e. when the object cache for the respective
  allocation size has not been created yet. A single page is thus sufficient.

other:
* vm_allocate_early(): Added boolean "blockAlign" parameter. If true, the
  semantics is the same as for B_ANY_KERNEL_BLOCK_ADDRESS.
* Use an object cache for page mappings. This significantly reduces the
  contention on the heap bin locks.


git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@35232 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Ingo Weinhold
2010-01-21 23:10:52 +00:00
parent 5f679d1cd3
commit 86c794e5c1
28 changed files with 1111 additions and 420 deletions
+668
View File
@@ -0,0 +1,668 @@
/*
* Copyright 2010, Ingo Weinhold <[email protected]>.
* Distributed under the terms of the MIT License.
*/
#include "MemoryManager.h"
#include <algorithm>
#include <debug.h>
#include <kernel.h>
#include <util/AutoLock.h>
#include <vm/vm.h>
#include <vm/vm_page.h>
#include <vm/vm_priv.h>
#include <vm/VMAddressSpace.h>
#include <vm/VMArea.h>
#include <vm/VMCache.h>
#include <vm/VMTranslationMap.h>
#include "ObjectCache.h"
#include "slab_private.h"
//#define TRACE_MEMORY_MANAGER
#ifdef TRACE_MEMORY_MANAGER
# define TRACE(x...) dprintf(x)
#else
# define TRACE(x...) do {} while (false)
#endif
static const char* const kSlabAreaName = "slab area";
static void* sAreaTableBuffer[1024];
mutex MemoryManager::sLock;
rw_lock MemoryManager::sAreaTableLock;
kernel_args* MemoryManager::sKernelArgs;
MemoryManager::AreaPool MemoryManager::sSmallChunkAreas;
MemoryManager::AreaPool MemoryManager::sMiddleChunkAreas;
MemoryManager::AreaPool MemoryManager::sLargeChunkAreas;
MemoryManager::AreaTable MemoryManager::sAreaTable;
MemoryManager::Area* MemoryManager::sFreeAreas;
MemoryManager::AllocationEntry* MemoryManager::sAllocationEntryCanWait;
MemoryManager::AllocationEntry* MemoryManager::sAllocationEntryDontWait;
/*static*/ void
MemoryManager::Init(kernel_args* args)
{
mutex_init(&sLock, "slab memory manager");
rw_lock_init(&sAreaTableLock, "slab memory manager area table");
sKernelArgs = args;
new(&sSmallChunkAreas) AreaPool;
new(&sMiddleChunkAreas) AreaPool;
new(&sLargeChunkAreas) AreaPool;
sSmallChunkAreas.chunkSize = SLAB_CHUNK_SIZE_SMALL;
sMiddleChunkAreas.chunkSize = SLAB_CHUNK_SIZE_MIDDLE;
sLargeChunkAreas.chunkSize = SLAB_CHUNK_SIZE_LARGE;
new(&sAreaTable) AreaTable;
sAreaTable.Resize(sAreaTableBuffer, sizeof(sAreaTableBuffer), true);
// A bit hacky: The table now owns the memory. Since we never resize or
// free it, that's not a problem, though.
sFreeAreas = NULL;
}
/*static*/ void
MemoryManager::InitPostArea()
{
sKernelArgs = NULL;
// Convert all areas to actual areas. This loop might look a bit weird, but
// is necessary since creating the actual area involves memory allocations,
// which in turn can change the situation.
while (true) {
if (!_ConvertEarlyAreas(sSmallChunkAreas.partialAreas)
&& !_ConvertEarlyAreas(sSmallChunkAreas.fullAreas)
&& !_ConvertEarlyAreas(sMiddleChunkAreas.partialAreas)
&& !_ConvertEarlyAreas(sMiddleChunkAreas.fullAreas)
&& !_ConvertEarlyAreas(sLargeChunkAreas.partialAreas)
&& !_ConvertEarlyAreas(sLargeChunkAreas.fullAreas)) {
break;
}
}
// just "leak" the free areas -- the VM will automatically free all
// unclaimed memory
sFreeAreas = NULL;
add_debugger_command_etc("slab_area", &_DumpArea,
"Dump information on a given slab area",
"<area>\n"
"Dump information on a given slab area specified by its base "
"address.\n", 0);
add_debugger_command_etc("slab_areas", &_DumpAreas,
"List all slab areas",
"\n"
"Lists all slab areas.\n", 0);
}
/*static*/ status_t
MemoryManager::Allocate(ObjectCache* cache, uint32 flags, void*& _pages)
{
// TODO: Support CACHE_UNLOCKED_PAGES!
size_t chunkSize = cache->slab_size;
TRACE("MemoryManager::Allocate(%p, %#" B_PRIx32 "): chunkSize: %"
B_PRIuSIZE "\n", cache, flags, chunkSize);
// get the right area pool
AreaPool* areaPool = _AreaPoolFor(chunkSize);
if (areaPool == NULL) {
panic("Invalid slab size: %" B_PRIuSIZE, chunkSize);
return B_BAD_VALUE;
}
MutexLocker locker(sLock);
// get an area
Area* area;
status_t error = _GetPartialArea(areaPool, flags, area);
if (error != B_OK)
return error;
// allocate a chunk
bool chunkMapped = false;
Chunk* chunk;
if (area->mappedFreeChunks != NULL) {
chunk = _pop(area->mappedFreeChunks);
chunkMapped = true;
} else
chunk = _pop(area->unmappedFreeChunks);
if (++area->usedChunkCount == area->chunkCount) {
areaPool->partialAreas.Remove(area);
areaPool->fullAreas.Add(area);
}
// If the chunk is not mapped yet, do it now.
addr_t chunkAddress = _ChunkAddress(area, chunk);
if (!chunkMapped) {
locker.Unlock();
error = _MapChunk(area->vmArea, chunkAddress, chunkSize, 0, flags);
locker.Lock();
if (error != B_OK) {
// something failed -- free the chunk
_FreeChunk(areaPool, area, chunk, chunkAddress, true, flags);
return error;
}
}
chunk->cache = cache;
_pages = (void*)chunkAddress;
TRACE("MemoryManager::Allocate() done: %p (chunk %p)\n", _pages, chunk);
return B_OK;
}
/*static*/ void
MemoryManager::Free(void* pages, uint32 flags)
{
TRACE("MemoryManager::Free(%p, %#" B_PRIx32 ")\n", pages, flags);
// get the area and the pool
Area* area = (Area*)ROUNDDOWN((addr_t)pages, SLAB_AREA_SIZE);
AreaPool* areaPool = _AreaPoolFor(area->chunkSize);
if (areaPool == NULL) {
panic("Invalid area: %p", area);
return;
}
// get the chunk
uint16 chunkIndex = _ChunkIndexForAddress(area, (addr_t)pages);
ASSERT(chunkIndex < area->chunkCount);
ASSERT((addr_t)pages % area->chunkSize == 0);
Chunk* chunk = &area->chunks[chunkIndex];
// and free it
MutexLocker locker(sLock);
_FreeChunk(areaPool, area, chunk, (addr_t)pages, false, flags);
}
/*static*/ size_t
MemoryManager::AcceptableChunkSize(size_t size)
{
if (size <= SLAB_CHUNK_SIZE_SMALL)
return SLAB_CHUNK_SIZE_SMALL;
if (size <= SLAB_CHUNK_SIZE_MIDDLE)
return SLAB_CHUNK_SIZE_MIDDLE;
return SLAB_CHUNK_SIZE_LARGE;
}
/*static*/ ObjectCache*
MemoryManager::CacheForAddress(void* address)
{
// get the area
addr_t areaBase = ROUNDDOWN((addr_t)address, SLAB_AREA_SIZE);
ReadLocker readLocker(sAreaTableLock);
Area* area = sAreaTable.Lookup(areaBase);
readLocker.Unlock();
if (area == NULL)
return NULL;
// get the chunk
uint16 chunkIndex = _ChunkIndexForAddress(area, (addr_t)address);
ASSERT(chunkIndex < area->chunkCount);
return area->chunks[chunkIndex].cache;
}
/*static*/ MemoryManager::AreaPool*
MemoryManager::_AreaPoolFor(size_t chunkSize)
{
if (chunkSize == SLAB_CHUNK_SIZE_SMALL)
return &sSmallChunkAreas;
if (chunkSize == SLAB_CHUNK_SIZE_MIDDLE)
return &sMiddleChunkAreas;
if (chunkSize == SLAB_CHUNK_SIZE_LARGE)
return &sLargeChunkAreas;
return NULL;
}
/*static*/ status_t
MemoryManager::_GetPartialArea(AreaPool* areaPool, uint32 flags, Area*& _area)
{
while (true) {
Area* area = areaPool->partialAreas.Head();
if (area != NULL) {
_area = area;
return B_OK;
}
// We need to allocate a new area. Wait, if someone else is trying the
// same.
AllocationEntry* allocationEntry = NULL;
if (sAllocationEntryDontWait != NULL) {
allocationEntry = sAllocationEntryDontWait;
} else if (sAllocationEntryCanWait != NULL
&& (flags & CACHE_DONT_SLEEP) == 0) {
allocationEntry = sAllocationEntryCanWait;
} else
break;
ConditionVariableEntry entry;
allocationEntry->condition.Add(&entry);
mutex_unlock(&sLock);
entry.Wait();
mutex_lock(&sLock);
}
// prepare the allocation entry others can wait on
AllocationEntry*& allocationEntry = (flags & CACHE_DONT_SLEEP) != 0
? sAllocationEntryDontWait : sAllocationEntryCanWait;
AllocationEntry myResizeEntry;
allocationEntry = &myResizeEntry;
allocationEntry->condition.Init(areaPool, "wait for slab area");
allocationEntry->thread = find_thread(NULL);
Area* area;
status_t error = _AllocateArea(areaPool->chunkSize, flags, area);
allocationEntry->condition.NotifyAll();
allocationEntry = NULL;
if (error != B_OK)
return error;
areaPool->partialAreas.Add(area);
// TODO: If something was freed in the meantime, we might rather
// want to delete the area again. Alternatively we could keep
// one global free area.
_area = area;
return B_OK;
}
/*static*/ status_t
MemoryManager::_AllocateArea(size_t chunkSize, uint32 flags, Area*& _area)
{
// TODO: Support reusing free areas!
TRACE("MemoryManager::_AllocateArea(%" B_PRIuSIZE ", %#" B_PRIx32 ")\n",
chunkSize, flags);
if ((flags & CACHE_DONT_SLEEP) != 0)
return B_WOULD_BLOCK;
// TODO: Support CACHE_DONT_SLEEP for real! We already consider it in
// most cases below, but not for vm_create_null_area().
mutex_unlock(&sLock);
uint32 chunkCount = (SLAB_AREA_SIZE - sizeof(Area))
/ (chunkSize + sizeof(Chunk));
size_t adminMemory = sizeof(Area) + chunkCount * sizeof(Chunk);
adminMemory = ROUNDUP(adminMemory, B_PAGE_SIZE);
size_t pagesNeededToMap = 0;
Area* area;
VMArea* vmArea = NULL;
if (sKernelArgs == NULL) {
// create an area
area_id areaID = vm_create_null_area(B_SYSTEM_TEAM, kSlabAreaName,
(void**)&area, B_ANY_KERNEL_BLOCK_ADDRESS, SLAB_AREA_SIZE);
if (areaID < 0) {
mutex_lock(&sLock);
return areaID;
}
// map the memory for the administrative structure
VMAddressSpace* addressSpace = VMAddressSpace::Kernel();
VMTranslationMap* translationMap = addressSpace->TranslationMap();
pagesNeededToMap = translationMap->MaxPagesNeededToMap((addr_t)area,
(addr_t)area + SLAB_AREA_SIZE - 1);
vmArea = VMAreaHash::Lookup(areaID);
status_t error = _MapChunk(vmArea, (addr_t)area, adminMemory,
pagesNeededToMap, flags);
if (error != B_OK) {
delete_area(areaID);
mutex_lock(&sLock);
return error;
}
TRACE("MemoryManager::_AllocateArea(): allocated area %p (%" B_PRId32
")\n", area, areaID);
} else {
// no areas yet -- allocate raw memory
area = (Area*)vm_allocate_early(sKernelArgs, SLAB_AREA_SIZE,
SLAB_AREA_SIZE, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, true);
if (area == NULL) {
mutex_lock(&sLock);
return B_NO_MEMORY;
}
TRACE("MemoryManager::_AllocateArea(): allocated early area %p\n",
area);
}
// init the area structure
area->vmArea = vmArea;
area->chunkSize = chunkSize;
area->reserved_memory_for_mapping = pagesNeededToMap * B_PAGE_SIZE;
area->firstUsableChunk = (addr_t)area + ROUNDUP(adminMemory, chunkSize);
area->chunkCount = chunkCount;
area->usedChunkCount = 0;
area->mappedFreeChunks = NULL;
area->unmappedFreeChunks = NULL;
for (uint32 i = 0; i < chunkCount; i++)
_push(area->unmappedFreeChunks, area->chunks + i);
// in the early boot process everything is mapped already
if (sKernelArgs != NULL)
std::swap(area->mappedFreeChunks, area->unmappedFreeChunks);
// add the area to the hash table
WriteLocker writeLocker(sAreaTableLock);
sAreaTable.InsertUnchecked(area);
writeLocker.Unlock();
mutex_lock(&sLock);
_area = area;
return B_OK;
}
/*static*/ void
MemoryManager::_FreeArea(Area* area, uint32 flags)
{
TRACE("MemoryManager::_FreeArea(%p, %#" B_PRIx32 ")\n", area, flags);
ASSERT(area->usedChunkCount == 0);
// remove the area from the hash table
WriteLocker writeLocker(sAreaTableLock);
sAreaTable.RemoveUnchecked(area);
writeLocker.Unlock();
if (area->vmArea == NULL) {
_push(sFreeAreas, area);
return;
}
// TODO: Do we need to handle CACHE_DONT_SLEEP here? Is delete_area()
// problematic?
mutex_unlock(&sLock);
size_t reservedMemory = area->reserved_memory_for_mapping;
// count mapped free chunks
for (Chunk* chunk = area->mappedFreeChunks; chunk != NULL;
chunk = chunk->next) {
reservedMemory += area->chunkSize;
}
delete_area(area->vmArea->id);
vm_unreserve_memory(reservedMemory);
mutex_lock(&sLock);
}
/*static*/ void
MemoryManager::_FreeChunk(AreaPool* areaPool, Area* area, Chunk* chunk,
addr_t chunkAddress, bool alreadyUnmapped, uint32 flags)
{
// unmap the chunk
if (!alreadyUnmapped) {
mutex_unlock(&sLock);
alreadyUnmapped = _UnmapChunk(area->vmArea, chunkAddress,
area->chunkSize, flags) == B_OK;
mutex_lock(&sLock);
}
if (alreadyUnmapped)
_push(area->unmappedFreeChunks, chunk);
else
_push(area->mappedFreeChunks, chunk);
// free the area, if it is unused now
ASSERT(area->usedChunkCount > 0);
if (--area->usedChunkCount == 0) {
areaPool->partialAreas.Remove(area);
_FreeArea(area, flags);
}
}
/*static*/ status_t
MemoryManager::_MapChunk(VMArea* vmArea, addr_t address, size_t size,
size_t reserveAdditionalMemory, uint32 flags)
{
TRACE("MemoryManager::_MapChunk(%p, %#" B_PRIxADDR ", %#" B_PRIxSIZE
")\n", vmArea, address, size);
VMAddressSpace* addressSpace = VMAddressSpace::Kernel();
VMTranslationMap* translationMap = addressSpace->TranslationMap();
// reserve memory for the chunk
size_t reservedMemory = size + reserveAdditionalMemory;
status_t error = vm_try_reserve_memory(size,
(flags & CACHE_DONT_SLEEP) != 0 ? 0 : 1000000);
if (error != B_OK)
return error;
// reserve the pages we need now
size_t reservedPages = size / B_PAGE_SIZE
+ translationMap->MaxPagesNeededToMap(address, address + size - 1);
if ((flags & CACHE_DONT_SLEEP) != 0) {
if (!vm_page_try_reserve_pages(reservedPages)) {
vm_unreserve_memory(reservedMemory);
return B_WOULD_BLOCK;
}
} else
vm_page_reserve_pages(reservedPages);
VMCache* cache = vm_area_get_locked_cache(vmArea);
// map the pages
translationMap->Lock();
addr_t areaOffset = address - vmArea->Base();
addr_t endAreaOffset = areaOffset + size;
for (size_t offset = areaOffset; offset < endAreaOffset;
offset += B_PAGE_SIZE) {
vm_page* page = vm_page_allocate_page(PAGE_STATE_FREE);
cache->InsertPage(page, offset);
vm_page_set_state(page, PAGE_STATE_WIRED);
page->wired_count++;
atomic_add(&gMappedPagesCount, 1);
DEBUG_PAGE_ACCESS_END(page);
translationMap->Map(vmArea->Base() + offset,
page->physical_page_number * B_PAGE_SIZE,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
}
translationMap->Unlock();
cache->ReleaseRefAndUnlock();
vm_page_unreserve_pages(reservedPages);
return B_OK;
}
/*static*/ status_t
MemoryManager::_UnmapChunk(VMArea* vmArea, addr_t address, size_t size,
uint32 flags)
{
if (vmArea == NULL)
return B_ERROR;
TRACE("MemoryManager::_UnmapChunk(%p, %#" B_PRIxADDR ", %#" B_PRIxSIZE
")\n", vmArea, address, size);
VMAddressSpace* addressSpace = VMAddressSpace::Kernel();
VMTranslationMap* translationMap = addressSpace->TranslationMap();
VMCache* cache = vm_area_get_locked_cache(vmArea);
// unmap the pages
translationMap->Lock();
translationMap->Unmap(address, address + size - 1);
atomic_add(&gMappedPagesCount, -(size / B_PAGE_SIZE));
translationMap->Unlock();
// free the pages
addr_t areaPageOffset = (address - vmArea->Base()) / B_PAGE_SIZE;
addr_t areaPageEndOffset = areaPageOffset + size / B_PAGE_SIZE;
VMCachePagesTree::Iterator it = cache->pages.GetIterator(
areaPageOffset, true, true);
while (vm_page* page = it.Next()) {
if (page->cache_offset >= areaPageEndOffset)
break;
DEBUG_PAGE_ACCESS_START(page);
page->wired_count--;
cache->RemovePage(page);
// the iterator is remove-safe
vm_page_free(cache, page);
}
cache->ReleaseRefAndUnlock();
vm_unreserve_memory(size);
return B_OK;
}
/*static*/ bool
MemoryManager::_ConvertEarlyAreas(AreaList& areas)
{
for (AreaList::Iterator it = areas.GetIterator();
Area* area = it.Next();) {
if (area->vmArea != NULL) {
// unmap mapped chunks
while (area->mappedFreeChunks != NULL) {
Chunk* chunk = _pop(area->mappedFreeChunks);
_UnmapChunk(area->vmArea, _ChunkAddress(area, chunk),
area->chunkSize, 0);
_push(area->unmappedFreeChunks, chunk);
}
continue;
}
void* address = area;
area_id areaID = create_area(kSlabAreaName, &address, B_EXACT_ADDRESS,
SLAB_AREA_SIZE, B_ALREADY_WIRED,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA);
if (areaID < 0)
panic("out of memory");
area->vmArea = VMAreaHash::Lookup(areaID);
return true;
}
return false;
}
/*static*/ int
MemoryManager::_DumpArea(int argc, char** argv)
{
if (argc != 2) {
print_debugger_command_usage(argv[0]);
return 0;
}
uint64 address;
if (!evaluate_debug_expression(argv[1], &address, false))
return 0;
address = ROUNDDOWN(address, SLAB_AREA_SIZE);
Area* area = (Area*)(addr_t)address;
kprintf("chunk base cache object size cache name\n");
// Get the last chunk in each of the free lists. This allows us to easily
// identify free chunks, since besides these two all other free chunks
// have a Chunk::next pointing to another chunk.
Chunk* lastMappedFree = area->mappedFreeChunks;
if (lastMappedFree != NULL) {
while (lastMappedFree->next != NULL)
lastMappedFree = lastMappedFree->next;
}
Chunk* lastUnmappedFree = area->unmappedFreeChunks;
if (lastUnmappedFree != NULL) {
while (lastUnmappedFree->next != NULL)
lastUnmappedFree = lastUnmappedFree->next;
}
for (uint32 i = 0; i < area->chunkCount; i++) {
Chunk* chunk = area->chunks + i;
if (chunk == lastMappedFree || chunk == lastUnmappedFree)
continue;
if (chunk->next >= area->chunks
&& chunk->next < area->chunks + area->chunkCount) {
continue;
}
ObjectCache* cache = chunk->cache;
kprintf("%5" B_PRIu32 " %p %p %11" B_PRIuSIZE " %s\n", i,
(void*)_ChunkAddress(area, chunk), cache,
cache != NULL ? cache->object_size : 0,
cache != NULL ? cache->name : "");
}
return 0;
}
/*static*/ int
MemoryManager::_DumpAreas(int argc, char** argv)
{
kprintf(" base area chunk size count used mapped free\n");
for (AreaTable::Iterator it = sAreaTable.GetIterator();
Area* area = it.Next();) {
// count the mapped free chunks
int mappedFreeChunks = 0;
for (Chunk* chunk = area->mappedFreeChunks; chunk != NULL;
chunk = chunk->next) {
mappedFreeChunks++;
}
kprintf("%p %p %10" B_PRIuSIZE " %5u %5u %11d\n",
area, area->vmArea, area->chunkSize, area->chunkCount,
area->usedChunkCount, mappedFreeChunks);
}
return 0;
}