/*
* Copyright 2010, Ingo Weinhold <ingo_weinhold@gmx.de>.
* Distributed under the terms of the MIT License.
*/
#include "MemoryManager.h"
#include <algorithm>
#include <debug.h>
#include <tracing.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 "kernel_debug_config.h"
#include "ObjectCache.h"
//#define TRACE_MEMORY_MANAGER
#ifdef TRACE_MEMORY_MANAGER
# define TRACE(x...) dprintf(x)
#else
# define TRACE(x...) do {} while (false)
#endif
#if DEBUG_SLAB_MEMORY_MANAGER_PARANOID_CHECKS
# define PARANOID_CHECKS_ONLY(x) x
#else
# define PARANOID_CHECKS_ONLY(x)
#endif
static const char* const kSlabAreaName = "slab area";
static void* sAreaTableBuffer[1024];
mutex MemoryManager::sLock;
rw_lock MemoryManager::sAreaTableLock;
kernel_args* MemoryManager::sKernelArgs;
MemoryManager::AreaTable MemoryManager::sAreaTable;
MemoryManager::Area* MemoryManager::sFreeAreas;
int MemoryManager::sFreeAreaCount;
MemoryManager::MetaChunkList MemoryManager::sFreeCompleteMetaChunks;
MemoryManager::MetaChunkList MemoryManager::sFreeShortMetaChunks;
MemoryManager::MetaChunkList MemoryManager::sPartialMetaChunksSmall;
MemoryManager::MetaChunkList MemoryManager::sPartialMetaChunksMedium;
MemoryManager::AllocationEntry* MemoryManager::sAllocationEntryCanWait;
MemoryManager::AllocationEntry* MemoryManager::sAllocationEntryDontWait;
bool MemoryManager::sMaintenanceNeeded;
RANGE_MARKER_FUNCTION_BEGIN(SlabMemoryManager)
// #pragma mark - kernel tracing
#if SLAB_MEMORY_MANAGER_TRACING
//namespace SlabMemoryManagerCacheTracing {
struct MemoryManager::Tracing {
class MemoryManagerTraceEntry
: public TRACE_ENTRY_SELECTOR(SLAB_MEMORY_MANAGER_TRACING_STACK_TRACE) {
public:
MemoryManagerTraceEntry()
:
TraceEntryBase(SLAB_MEMORY_MANAGER_TRACING_STACK_TRACE, 0, true)
{
}
};
class Allocate : public MemoryManagerTraceEntry {
public:
Allocate(ObjectCache* cache, uint32 flags)
:
MemoryManagerTraceEntry(),
fCache(cache),
fFlags(flags)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager alloc: cache: %p, flags: %#" B_PRIx32,
fCache, fFlags);
}
private:
ObjectCache* fCache;
uint32 fFlags;
};
class Free : public MemoryManagerTraceEntry {
public:
Free(void* address, uint32 flags)
:
MemoryManagerTraceEntry(),
fAddress(address),
fFlags(flags)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager free: address: %p, flags: %#" B_PRIx32,
fAddress, fFlags);
}
private:
void* fAddress;
uint32 fFlags;
};
class AllocateRaw : public MemoryManagerTraceEntry {
public:
AllocateRaw(size_t size, uint32 flags)
:
MemoryManagerTraceEntry(),
fSize(size),
fFlags(flags)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager alloc raw: size: %" B_PRIuSIZE
", flags: %#" B_PRIx32, fSize, fFlags);
}
private:
size_t fSize;
uint32 fFlags;
};
class FreeRawOrReturnCache : public MemoryManagerTraceEntry {
public:
FreeRawOrReturnCache(void* address, uint32 flags)
:
MemoryManagerTraceEntry(),
fAddress(address),
fFlags(flags)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager free raw/return: address: %p, flags: %#"
B_PRIx32, fAddress, fFlags);
}
private:
void* fAddress;
uint32 fFlags;
};
class AllocateArea : public MemoryManagerTraceEntry {
public:
AllocateArea(Area* area, uint32 flags)
:
MemoryManagerTraceEntry(),
fArea(area),
fFlags(flags)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager alloc area: flags: %#" B_PRIx32
" -> %p", fFlags, fArea);
}
private:
Area* fArea;
uint32 fFlags;
};
class AddArea : public MemoryManagerTraceEntry {
public:
AddArea(Area* area)
:
MemoryManagerTraceEntry(),
fArea(area)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager add area: %p", fArea);
}
private:
Area* fArea;
};
class FreeArea : public MemoryManagerTraceEntry {
public:
FreeArea(Area* area, bool areaRemoved, uint32 flags)
:
MemoryManagerTraceEntry(),
fArea(area),
fFlags(flags),
fRemoved(areaRemoved)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager free area: %p%s, flags: %#" B_PRIx32,
fArea, fRemoved ? " (removed)" : "", fFlags);
}
private:
Area* fArea;
uint32 fFlags;
bool fRemoved;
};
class AllocateMetaChunk : public MemoryManagerTraceEntry {
public:
AllocateMetaChunk(MetaChunk* metaChunk)
:
MemoryManagerTraceEntry(),
fMetaChunk(metaChunk->chunkBase)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager alloc meta chunk: %#" B_PRIxADDR,
fMetaChunk);
}
private:
addr_t fMetaChunk;
};
class FreeMetaChunk : public MemoryManagerTraceEntry {
public:
FreeMetaChunk(MetaChunk* metaChunk)
:
MemoryManagerTraceEntry(),
fMetaChunk(metaChunk->chunkBase)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager free meta chunk: %#" B_PRIxADDR,
fMetaChunk);
}
private:
addr_t fMetaChunk;
};
class AllocateChunk : public MemoryManagerTraceEntry {
public:
AllocateChunk(size_t chunkSize, MetaChunk* metaChunk, Chunk* chunk)
:
MemoryManagerTraceEntry(),
fChunkSize(chunkSize),
fMetaChunk(metaChunk->chunkBase),
fChunk(chunk - metaChunk->chunks)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager alloc chunk: size: %" B_PRIuSIZE
" -> meta chunk: %#" B_PRIxADDR ", chunk: %" B_PRIu32, fChunkSize,
fMetaChunk, fChunk);
}
private:
size_t fChunkSize;
addr_t fMetaChunk;
uint32 fChunk;
};
class AllocateChunks : public MemoryManagerTraceEntry {
public:
AllocateChunks(size_t chunkSize, uint32 chunkCount, MetaChunk* metaChunk,
Chunk* chunk)
:
MemoryManagerTraceEntry(),
fMetaChunk(metaChunk->chunkBase),
fChunkSize(chunkSize),
fChunkCount(chunkCount),
fChunk(chunk - metaChunk->chunks)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager alloc chunks: size: %" B_PRIuSIZE
", count %" B_PRIu32 " -> meta chunk: %#" B_PRIxADDR ", chunk: %"
B_PRIu32, fChunkSize, fChunkCount, fMetaChunk, fChunk);
}
private:
addr_t fMetaChunk;
size_t fChunkSize;
uint32 fChunkCount;
uint32 fChunk;
};
class FreeChunk : public MemoryManagerTraceEntry {
public:
FreeChunk(MetaChunk* metaChunk, Chunk* chunk)
:
MemoryManagerTraceEntry(),
fMetaChunk(metaChunk->chunkBase),
fChunk(chunk - metaChunk->chunks)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager free chunk: meta chunk: %#" B_PRIxADDR
", chunk: %" B_PRIu32, fMetaChunk, fChunk);
}
private:
addr_t fMetaChunk;
uint32 fChunk;
};
class Map : public MemoryManagerTraceEntry {
public:
Map(addr_t address, size_t size, uint32 flags)
:
MemoryManagerTraceEntry(),
fAddress(address),
fSize(size),
fFlags(flags)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager map: %#" B_PRIxADDR ", size: %"
B_PRIuSIZE ", flags: %#" B_PRIx32, fAddress, fSize, fFlags);
}
private:
addr_t fAddress;
size_t fSize;
uint32 fFlags;
};
class Unmap : public MemoryManagerTraceEntry {
public:
Unmap(addr_t address, size_t size, uint32 flags)
:
MemoryManagerTraceEntry(),
fAddress(address),
fSize(size),
fFlags(flags)
{
Initialized();
}
virtual void AddDump(TraceOutput& out)
{
out.Print("slab memory manager unmap: %#" B_PRIxADDR ", size: %"
B_PRIuSIZE ", flags: %#" B_PRIx32, fAddress, fSize, fFlags);
}
private:
addr_t fAddress;
size_t fSize;
uint32 fFlags;
};
//} // namespace SlabMemoryManagerCacheTracing
}; // struct MemoryManager::Tracing
//# define T(x) new(std::nothrow) SlabMemoryManagerCacheTracing::x
# define T(x) new(std::nothrow) MemoryManager::Tracing::x
#else
# define T(x)
#endif // SLAB_MEMORY_MANAGER_TRACING
// #pragma mark - MemoryManager
/*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(&sFreeCompleteMetaChunks) MetaChunkList;
new(&sFreeShortMetaChunks) MetaChunkList;
new(&sPartialMetaChunksSmall) MetaChunkList;
new(&sPartialMetaChunksMedium) MetaChunkList;
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;
sFreeAreaCount = 0;
sMaintenanceNeeded = false;
}
/*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.
bool done;
do {
done = true;
for (AreaTable::Iterator it = sAreaTable.GetIterator();
Area* area = it.Next();) {
if (area->vmArea == NULL) {
_ConvertEarlyArea(area);
done = false;
break;
}
}
} while (!done);
// unmap and free unused pages
if (sFreeAreas != NULL) {
// Just "leak" all but the first of the free areas -- the VM will
// automatically free all unclaimed memory.
sFreeAreas->next = NULL;
sFreeAreaCount = 1;
Area* area = sFreeAreas;
_ConvertEarlyArea(area);
_UnmapFreeChunksEarly(area);
}
for (AreaTable::Iterator it = sAreaTable.GetIterator();
Area* area = it.Next();) {
_UnmapFreeChunksEarly(area);
}
sMaintenanceNeeded = true;
// might not be necessary, but doesn't harm
add_debugger_command_etc("slab_area", &_DumpArea,
"Dump information on a given slab area",
"[ -c ] <area>\n"
"Dump information on a given slab area specified by its base "
"address.\n"
"If \"-c\" is given, the chunks of all meta chunks area printed as "
"well.\n", 0);
add_debugger_command_etc("slab_areas", &_DumpAreas,
"List all slab areas",
"\n"
"Lists all slab areas.\n", 0);
add_debugger_command_etc("slab_meta_chunk", &_DumpMetaChunk,
"Dump information on a given slab meta chunk",
"<meta chunk>\n"
"Dump information on a given slab meta chunk specified by its base "
"or object address.\n", 0);
add_debugger_command_etc("slab_meta_chunks", &_DumpMetaChunks,
"List all non-full slab meta chunks",
"[ -c ]\n"
"Lists all non-full slab meta chunks.\n"
"If \"-c\" is given, the chunks of all meta chunks area printed as "
"well.\n", 0);
add_debugger_command_etc("slab_raw_allocations", &_DumpRawAllocations,
"List all raw allocations in slab areas",
"\n"
"Lists all raw allocations in slab areas.\n", 0);
}
/*static*/ status_t
MemoryManager::Allocate(ObjectCache* cache, uint32 flags, void*& _pages)
{
// TODO: Support CACHE_UNLOCKED_PAGES!
T(Allocate(cache, flags));
size_t chunkSize = cache->slab_size;
TRACE("MemoryManager::Allocate(%p, %#" B_PRIx32 "): chunkSize: %"
B_PRIuSIZE "\n", cache, flags, chunkSize);
MutexLocker locker(sLock);
// allocate a chunk
MetaChunk* metaChunk;
Chunk* chunk;
status_t error = _AllocateChunks(chunkSize, 1, flags, metaChunk, chunk);
if (error != B_OK)
return error;
// map the chunk
Area* area = metaChunk->GetArea();
addr_t chunkAddress = _ChunkAddress(metaChunk, chunk);
locker.Unlock();
error = _MapChunk(area->vmArea, chunkAddress, chunkSize, 0, flags);
locker.Lock();
if (error != B_OK) {
// something failed -- free the chunk
_FreeChunk(area, metaChunk, chunk, chunkAddress, true, flags);
return error;
}
chunk->reference = (addr_t)cache;
_pages = (void*)chunkAddress;
TRACE("MemoryManager::Allocate() done: %p (meta chunk: %d, chunk %d)\n",
_pages, int(metaChunk - area->metaChunks),
int(chunk - metaChunk->chunks));
return B_OK;
}
/*static*/ void
MemoryManager::Free(void* pages, uint32 flags)
{
TRACE("MemoryManager::Free(%p, %#" B_PRIx32 ")\n", pages, flags);
T(Free(pages, flags));
// get the area and the meta chunk
Area* area = _AreaForAddress((addr_t)pages);
MetaChunk* metaChunk = &area->metaChunks[
((addr_t)pages % SLAB_AREA_SIZE) / SLAB_CHUNK_SIZE_LARGE];
ASSERT(metaChunk->chunkSize > 0);
ASSERT((addr_t)pages >= metaChunk->chunkBase);
ASSERT(((addr_t)pages % metaChunk->chunkSize) == 0);
// get the chunk
uint16 chunkIndex = _ChunkIndexForAddress(metaChunk, (addr_t)pages);
Chunk* chunk = &metaChunk->chunks[chunkIndex];
ASSERT(chunk->next != NULL);
ASSERT(chunk->next < metaChunk->chunks
|| chunk->next
>= metaChunk->chunks + SLAB_SMALL_CHUNKS_PER_META_CHUNK);
// and free it
MutexLocker locker(sLock);
_FreeChunk(area, metaChunk, chunk, (addr_t)pages, false, flags);
}
/*static*/ status_t
MemoryManager::AllocateRaw(size_t size, uint32 flags, void*& _pages)
{
#if SLAB_MEMORY_MANAGER_TRACING
#if SLAB_MEMORY_MANAGER_ALLOCATION_TRACKING
AbstractTraceEntryWithStackTrace* traceEntry = T(AllocateRaw(size, flags));
size += sizeof(AllocationTrackingInfo);
#else
T(AllocateRaw(size, flags));
#endif
#endif
size = ROUNDUP(size, SLAB_CHUNK_SIZE_SMALL);
TRACE("MemoryManager::AllocateRaw(%" B_PRIuSIZE ", %#" B_PRIx32 ")\n", size,
flags);
if (size > SLAB_CHUNK_SIZE_LARGE || (flags & CACHE_ALIGN_ON_SIZE) != 0) {
// Requested size greater than a large chunk or an aligned allocation.
// Allocate as an area.
if ((flags & CACHE_DONT_LOCK_KERNEL_SPACE) != 0)
return B_WOULD_BLOCK;
virtual_address_restrictions virtualRestrictions = {};
virtualRestrictions.address_specification
= (flags & CACHE_ALIGN_ON_SIZE) != 0
? B_ANY_KERNEL_BLOCK_ADDRESS : B_ANY_KERNEL_ADDRESS;
physical_address_restrictions physicalRestrictions = {};
area_id area = create_area_etc(VMAddressSpace::KernelID(),
"slab large raw allocation", size, B_FULL_LOCK,
B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
((flags & CACHE_DONT_WAIT_FOR_MEMORY) != 0
? CREATE_AREA_DONT_WAIT : 0)
| CREATE_AREA_DONT_CLEAR, 0,
&virtualRestrictions, &physicalRestrictions, &_pages);
status_t result = area >= 0 ? B_OK : area;
if (result == B_OK) {
fill_allocated_block(_pages, size);
#if SLAB_MEMORY_MANAGER_ALLOCATION_TRACKING
_AddTrackingInfo(_pages, size, traceEntry);
#endif
}
return result;
}
// determine chunk size (small or medium)
size_t chunkSize = SLAB_CHUNK_SIZE_SMALL;
uint32 chunkCount = size / SLAB_CHUNK_SIZE_SMALL;
if (size % SLAB_CHUNK_SIZE_MEDIUM == 0) {
chunkSize = SLAB_CHUNK_SIZE_MEDIUM;
chunkCount = size / SLAB_CHUNK_SIZE_MEDIUM;
}
MutexLocker locker(sLock);
// allocate the chunks
MetaChunk* metaChunk;
Chunk* chunk;
status_t error = _AllocateChunks(chunkSize, chunkCount, flags, metaChunk,
chunk);
if (error != B_OK)
return error;
// map the chunks
Area* area = metaChunk->GetArea();
addr_t chunkAddress = _ChunkAddress(metaChunk, chunk);
locker.Unlock();
error = _MapChunk(area->vmArea, chunkAddress, size, 0, flags);
locker.Lock();
if (error != B_OK) {
// something failed -- free the chunks
for (uint32 i = 0; i < chunkCount; i++)
_FreeChunk(area, metaChunk, chunk + i, chunkAddress, true, flags);
return error;
}
chunk->reference = (addr_t)chunkAddress + size - 1;
_pages = (void*)chunkAddress;
fill_allocated_block(_pages, size);
#if SLAB_MEMORY_MANAGER_ALLOCATION_TRACKING
_AddTrackingInfo(_pages, size, traceEntry);
#endif
TRACE("MemoryManager::AllocateRaw() done: %p (meta chunk: %d, chunk %d)\n",
_pages, int(metaChunk - area->metaChunks),
int(chunk - metaChunk->chunks));
return B_OK;
}
/*static*/ ObjectCache*
MemoryManager::FreeRawOrReturnCache(void* pages, uint32 flags)
{
TRACE("MemoryManager::FreeRawOrReturnCache(%p, %#" B_PRIx32 ")\n", pages,
flags);
T(FreeRawOrReturnCache(pages, flags));
// get the area
addr_t areaBase = _AreaBaseAddressForAddress((addr_t)pages);
ReadLocker readLocker(sAreaTableLock);
Area* area = sAreaTable.Lookup(areaBase);
readLocker.Unlock();
if (area == NULL) {
// Probably a large allocation. Look up the VM area.
VMAddressSpace* addressSpace = VMAddressSpace::Kernel();
addressSpace->ReadLock();
VMArea* area = addressSpace->LookupArea((addr_t)pages);
addressSpace->ReadUnlock();
if (area != NULL && (addr_t)pages == area->Base())
delete_area(area->id);
else
panic("freeing unknown block %p from area %p", pages, area);
return NULL;
}
MetaChunk* metaChunk = &area->metaChunks[
((addr_t)pages % SLAB_AREA_SIZE) / SLAB_CHUNK_SIZE_LARGE];
// get the chunk
ASSERT(metaChunk->chunkSize > 0);
ASSERT((addr_t)pages >= metaChunk->chunkBase);
uint16 chunkIndex = _ChunkIndexForAddress(metaChunk, (addr_t)pages);
Chunk* chunk = &metaChunk->chunks[chunkIndex];
addr_t reference = chunk->reference;
if ((reference & 1) == 0)
return (ObjectCache*)reference;
// Seems we have a raw chunk allocation.
ASSERT((addr_t)pages == _ChunkAddress(metaChunk, chunk));
ASSERT(reference > (addr_t)pages);
ASSERT(reference <= areaBase + SLAB_AREA_SIZE - 1);
size_t size = reference - (addr_t)pages + 1;
ASSERT((size % SLAB_CHUNK_SIZE_SMALL) == 0);
// unmap the chunks
_UnmapChunk(area->vmArea, (addr_t)pages, size, flags);
// and free them
MutexLocker locker(sLock);
uint32 chunkCount = size / metaChunk->chunkSize;
for (uint32 i = 0; i < chunkCount; i++)
_FreeChunk(area, metaChunk, chunk + i, (addr_t)pages, true, flags);
return NULL;
}
/*static*/ size_t
MemoryManager::AcceptableChunkSize(size_t size)
{
if (size <= SLAB_CHUNK_SIZE_SMALL)
return SLAB_CHUNK_SIZE_SMALL;
if (size <= SLAB_CHUNK_SIZE_MEDIUM)
return SLAB_CHUNK_SIZE_MEDIUM;
return SLAB_CHUNK_SIZE_LARGE;
}
/*static*/ ObjectCache*
MemoryManager::GetAllocationInfo(void* address, size_t& _size)
{
// get the area
ReadLocker readLocker(sAreaTableLock);
Area* area = sAreaTable.Lookup(_AreaBaseAddressForAddress((addr_t)address));
readLocker.Unlock();
if (area == NULL) {
VMAddressSpace* addressSpace = VMAddressSpace::Kernel();
addressSpace->ReadLock();
VMArea* area = addressSpace->LookupArea((addr_t)address);
if (area != NULL && (addr_t)address == area->Base())
_size = area->Size();
else
_size = 0;
addressSpace->ReadUnlock();
return NULL;
}
MetaChunk* metaChunk = &area->metaChunks[
((addr_t)address % SLAB_AREA_SIZE) / SLAB_CHUNK_SIZE_LARGE];
// get the chunk
ASSERT(metaChunk->chunkSize > 0);
ASSERT((addr_t)address >= metaChunk->chunkBase);
uint16 chunkIndex = _ChunkIndexForAddress(metaChunk, (addr_t)address);
addr_t reference = metaChunk->chunks[chunkIndex].reference;
if ((reference & 1) == 0) {
ObjectCache* cache = (ObjectCache*)reference;
_size = cache->object_size;
return cache;
}
_size = reference - (addr_t)address + 1;
return NULL;
}
/*static*/ ObjectCache*
MemoryManager::CacheForAddress(void* address)
{
// get the area
ReadLocker readLocker(sAreaTableLock);
Area* area = sAreaTable.Lookup(_AreaBaseAddressForAddress((addr_t)address));
readLocker.Unlock();
if (area == NULL)
return NULL;
MetaChunk* metaChunk = &area->metaChunks[
((addr_t)address % SLAB_AREA_SIZE) / SLAB_CHUNK_SIZE_LARGE];
// get the chunk
ASSERT(metaChunk->chunkSize > 0);
ASSERT((addr_t)address >= metaChunk->chunkBase);
uint16 chunkIndex = _ChunkIndexForAddress(metaChunk, (addr_t)address);
addr_t reference = metaChunk->chunks[chunkIndex].reference;
return (reference & 1) == 0 ? (ObjectCache*)reference : NULL;
}
/*static*/ void
MemoryManager::PerformMaintenance()
{
MutexLocker locker(sLock);
while (sMaintenanceNeeded) {
sMaintenanceNeeded = false;
// We want to keep one or two areas as a reserve. This way we have at
// least one area to use in situations when we aren't allowed to
// allocate one and also avoid ping-pong effects.
if (sFreeAreaCount > 0 && sFreeAreaCount <= 2)
return;
if (sFreeAreaCount == 0) {
// try to allocate one
Area* area;
if (_AllocateArea(0, area) != B_OK)
return;
_PushFreeArea(area);
if (sFreeAreaCount > 2)
sMaintenanceNeeded = true;
} else {
// free until we only have two free ones
while (sFreeAreaCount > 2)
_FreeArea(_PopFreeArea(), true, 0);
if (sFreeAreaCount == 0)
sMaintenanceNeeded = true;
}
}
}
#if SLAB_MEMORY_MANAGER_ALLOCATION_TRACKING
/*static*/ bool
MemoryManager::AnalyzeAllocationCallers(AllocationTrackingCallback& callback)
{
for (AreaTable::Iterator it = sAreaTable.GetIterator();
Area* area = it.Next();) {
for (int32 i = 0; i < SLAB_META_CHUNKS_PER_AREA; i++) {
MetaChunk* metaChunk = area->metaChunks + i;
if (metaChunk->chunkSize == 0)
continue;
for (uint32 k = 0; k < metaChunk->chunkCount; k++) {
Chunk* chunk = metaChunk->chunks + k;
// skip free chunks
if (_IsChunkFree(metaChunk, chunk))
continue;
addr_t reference = chunk->reference;
if ((reference & 1) == 0 || reference == 1)
continue;
addr_t chunkAddress = _ChunkAddress(metaChunk, chunk);
size_t size = reference - chunkAddress + 1;
if (!callback.ProcessTrackingInfo(
_TrackingInfoFor((void*)chunkAddress, size),
(void*)chunkAddress, size)) {
return false;
}
}
}
}
return true;
}
#endif // SLAB_MEMORY_MANAGER_ALLOCATION_TRACKING
/*static*/ ObjectCache*
MemoryManager::DebugObjectCacheForAddress(void* address)
{
// get the area
addr_t areaBase = _AreaBaseAddressForAddress((addr_t)address);
Area* area = sAreaTable.Lookup(areaBase);
if (area == NULL)
return NULL;
MetaChunk* metaChunk = &area->metaChunks[
((addr_t)address % SLAB_AREA_SIZE) / SLAB_CHUNK_SIZE_LARGE];
// get the chunk
if (metaChunk->chunkSize == 0)
return NULL;
if ((addr_t)address < metaChunk->chunkBase)
return NULL;
uint16 chunkIndex = _ChunkIndexForAddress(metaChunk, (addr_t)address);
Chunk* chunk = &metaChunk->chunks[chunkIndex];
addr_t reference = chunk->reference;
if ((reference & 1) == 0)
return (ObjectCache*)reference;
return NULL;
}
/*static*/ status_t
MemoryManager::_AllocateChunks(size_t chunkSize, uint32 chunkCount,
uint32 flags, MetaChunk*& _metaChunk, Chunk*& _chunk)
{
MetaChunkList* metaChunkList = NULL;
if (chunkSize == SLAB_CHUNK_SIZE_SMALL) {
metaChunkList = &sPartialMetaChunksSmall;
} else if (chunkSize == SLAB_CHUNK_SIZE_MEDIUM) {
metaChunkList = &sPartialMetaChunksMedium;
} else if (chunkSize != SLAB_CHUNK_SIZE_LARGE) {
panic("MemoryManager::_AllocateChunks(): Unsupported chunk size: %"
B_PRIuSIZE, chunkSize);
return B_BAD_VALUE;
}
if (_GetChunks(metaChunkList, chunkSize, chunkCount, _metaChunk, _chunk))
return B_OK;
if (sFreeAreas != NULL) {
_AddArea(_PopFreeArea());
_RequestMaintenance();
_GetChunks(metaChunkList, chunkSize, chunkCount, _metaChunk, _chunk);
return B_OK;
}
if ((flags & CACHE_DONT_LOCK_KERNEL_SPACE) != 0) {
// We can't create an area with this limitation and we must not wait for
// someone else doing that.
return B_WOULD_BLOCK;
}
// We need to allocate a new area. Wait, if someone else is trying to do
// the same.
while (true) {
AllocationEntry* allocationEntry = NULL;
if (sAllocationEntryDontWait != NULL) {
allocationEntry = sAllocationEntryDontWait;
} else if (sAllocationEntryCanWait != NULL
&& (flags & CACHE_DONT_WAIT_FOR_MEMORY) == 0) {
allocationEntry = sAllocationEntryCanWait;
} else
break;
ConditionVariableEntry entry;
allocationEntry->condition.Add(&entry);
mutex_unlock(&sLock);
entry.Wait();
mutex_lock(&sLock);
if (_GetChunks(metaChunkList, chunkSize, chunkCount, _metaChunk,
_chunk)) {
return B_OK;
}
}
// prepare the allocation entry others can wait on
AllocationEntry*& allocationEntry
= (flags & CACHE_DONT_WAIT_FOR_MEMORY) != 0
? sAllocationEntryDontWait : sAllocationEntryCanWait;
AllocationEntry myResizeEntry;
allocationEntry = &myResizeEntry;
allocationEntry->condition.Init(metaChunkList, "wait for slab area");
allocationEntry->thread = find_thread(NULL);
Area* area;
status_t error = _AllocateArea(flags, area);
allocationEntry->condition.NotifyAll();
allocationEntry = NULL;
if (error != B_OK)
return error;
// Try again to get a meta chunk. Something might have been freed in the
// meantime. We can free the area in this case.
if (_GetChunks(metaChunkList, chunkSize, chunkCount, _metaChunk, _chunk)) {
_FreeArea(area, true, flags);
return B_OK;
}
_AddArea(area);
_GetChunks(metaChunkList, chunkSize, chunkCount, _metaChunk, _chunk);
return B_OK;
}
/*static*/ bool
MemoryManager::_GetChunks(MetaChunkList* metaChunkList, size_t chunkSize,
uint32 chunkCount, MetaChunk*& _metaChunk, Chunk*& _chunk)
{
// the common and less complicated special case
if (chunkCount == 1)
return _GetChunk(metaChunkList, chunkSize, _metaChunk, _chunk);
ASSERT(metaChunkList != NULL);
// Iterate through the partial meta chunk list and try to find a free
// range that is large enough.
MetaChunk* metaChunk = NULL;
for (MetaChunkList::Iterator it = metaChunkList->GetIterator();
(metaChunk = it.Next()) != NULL;) {
if (metaChunk->firstFreeChunk + chunkCount - 1
<= metaChunk->lastFreeChunk) {
break;
}
}
if (metaChunk == NULL) {
// try to get a free meta chunk
if ((SLAB_CHUNK_SIZE_LARGE - SLAB_AREA_STRUCT_OFFSET - kAreaAdminSize)
/ chunkSize >= chunkCount) {
metaChunk = sFreeShortMetaChunks.RemoveHead();
}
if (metaChunk == NULL)
metaChunk = sFreeCompleteMetaChunks.RemoveHead();
if (metaChunk == NULL)
return false;
metaChunkList->Add(metaChunk);
metaChunk->GetArea()->usedMetaChunkCount++;
_PrepareMetaChunk(metaChunk, chunkSize);
T(AllocateMetaChunk(metaChunk));
}
// pull the chunks out of the free list
Chunk* firstChunk = metaChunk->chunks + metaChunk->firstFreeChunk;
Chunk* lastChunk = firstChunk + (chunkCount - 1);
Chunk** chunkPointer = &metaChunk->freeChunks;
uint32 remainingChunks = chunkCount;
while (remainingChunks > 0) {
ASSERT_PRINT(chunkPointer, "remaining: %" B_PRIu32 "/%" B_PRIu32
", area: %p, meta chunk: %" B_PRIdSSIZE "\n", remainingChunks,
chunkCount, metaChunk->GetArea(),
metaChunk - metaChunk->GetArea()->metaChunks);
Chunk* chunk = *chunkPointer;
if (chunk >= firstChunk && chunk <= lastChunk) {
*chunkPointer = chunk->next;
chunk->reference = 1;
remainingChunks--;
} else
chunkPointer = &chunk->next;
}
// allocate the chunks
metaChunk->usedChunkCount += chunkCount;
if (metaChunk->usedChunkCount == metaChunk->chunkCount) {
// meta chunk is full now -- remove it from its list
if (metaChunkList != NULL)
metaChunkList->Remove(metaChunk);
}
// update the free range
metaChunk->firstFreeChunk += chunkCount;
PARANOID_CHECKS_ONLY(_CheckMetaChunk(metaChunk));
_chunk = firstChunk;
_metaChunk = metaChunk;
T(AllocateChunks(chunkSize, chunkCount, metaChunk, firstChunk));
return true;
}
/*static*/ bool
MemoryManager::_GetChunk(MetaChunkList* metaChunkList, size_t chunkSize,
MetaChunk*& _metaChunk, Chunk*& _chunk)
{
MetaChunk* metaChunk = metaChunkList != NULL
? metaChunkList->Head() : NULL;
if (metaChunk == NULL) {
// no partial meta chunk -- maybe there's a free one
if (chunkSize == SLAB_CHUNK_SIZE_LARGE) {
metaChunk = sFreeCompleteMetaChunks.RemoveHead();
} else {
metaChunk = sFreeShortMetaChunks.RemoveHead();
if (metaChunk == NULL)
metaChunk = sFreeCompleteMetaChunks.RemoveHead();
if (metaChunk != NULL)
metaChunkList->Add(metaChunk);
}
if (metaChunk == NULL)
return false;
metaChunk->GetArea()->usedMetaChunkCount++;
_PrepareMetaChunk(metaChunk, chunkSize);
T(AllocateMetaChunk(metaChunk));
}
// allocate the chunk
if (++metaChunk->usedChunkCount == metaChunk->chunkCount) {
// meta chunk is full now -- remove it from its list
if (metaChunkList != NULL)
metaChunkList->Remove(metaChunk);
}
_chunk = _pop(metaChunk->freeChunks);
_metaChunk = metaChunk;
_chunk->reference = 1;
// update the free range
uint32 chunkIndex = _chunk - metaChunk->chunks;
if (chunkIndex >= metaChunk->firstFreeChunk
&& chunkIndex <= metaChunk->lastFreeChunk) {
if (chunkIndex - metaChunk->firstFreeChunk
<= metaChunk->lastFreeChunk - chunkIndex) {
metaChunk->firstFreeChunk = chunkIndex + 1;
} else
metaChunk->lastFreeChunk = chunkIndex - 1;
}
PARANOID_CHECKS_ONLY(_CheckMetaChunk(metaChunk));
T(AllocateChunk(chunkSize, metaChunk, _chunk));
return true;
}
/*static*/ void
MemoryManager::_FreeChunk(Area* area, MetaChunk* metaChunk, Chunk* chunk,
addr_t chunkAddress, bool alreadyUnmapped, uint32 flags)
{
// unmap the chunk
if (!alreadyUnmapped) {
mutex_unlock(&sLock);
_UnmapChunk(area->vmArea, chunkAddress, metaChunk->chunkSize, flags);
mutex_lock(&sLock);
}
T(FreeChunk(metaChunk, chunk));
_push(metaChunk->freeChunks, chunk);
uint32 chunkIndex = chunk - metaChunk->chunks;
// free the meta chunk, if it is unused now
PARANOID_CHECKS_ONLY(bool areaDeleted = false;)
ASSERT(metaChunk->usedChunkCount > 0);
if (--metaChunk->usedChunkCount == 0) {
T(FreeMetaChunk(metaChunk));
// remove from partial meta chunk list
if (metaChunk->chunkSize == SLAB_CHUNK_SIZE_SMALL)
sPartialMetaChunksSmall.Remove(metaChunk);
else if (metaChunk->chunkSize == SLAB_CHUNK_SIZE_MEDIUM)
sPartialMetaChunksMedium.Remove(metaChunk);
// mark empty
metaChunk->chunkSize = 0;
// add to free list
if (metaChunk == area->metaChunks)
sFreeShortMetaChunks.Add(metaChunk, false);
else
sFreeCompleteMetaChunks.Add(metaChunk, false);
// free the area, if it is unused now
ASSERT(area->usedMetaChunkCount > 0);
if (--area->usedMetaChunkCount == 0) {
_FreeArea(area, false, flags);
PARANOID_CHECKS_ONLY(areaDeleted = true;)
}
} else if (metaChunk->usedChunkCount == metaChunk->chunkCount - 1) {
// the meta chunk was full before -- add it back to its partial chunk
// list
if (metaChunk->chunkSize == SLAB_CHUNK_SIZE_SMALL)
sPartialMetaChunksSmall.Add(metaChunk, false);
else if (metaChunk->chunkSize == SLAB_CHUNK_SIZE_MEDIUM)
sPartialMetaChunksMedium.Add(metaChunk, false);
metaChunk->firstFreeChunk = chunkIndex;
metaChunk->lastFreeChunk = chunkIndex;
} else {
// extend the free range, if the chunk adjoins
if (chunkIndex + 1 == metaChunk->firstFreeChunk) {
uint32 firstFree = chunkIndex;
for (; firstFree > 0; firstFree--) {
Chunk* previousChunk = &metaChunk->chunks[firstFree - 1];
if (!_IsChunkFree(metaChunk, previousChunk))
break;
}
metaChunk->firstFreeChunk = firstFree;
} else if (chunkIndex == (uint32)metaChunk->lastFreeChunk + 1) {
uint32 lastFree = chunkIndex;
for (; lastFree + 1 < metaChunk->chunkCount; lastFree++) {
Chunk* nextChunk = &metaChunk->chunks[lastFree + 1];
if (!_IsChunkFree(metaChunk, nextChunk))
break;
}
metaChunk->lastFreeChunk = lastFree;
}
}
PARANOID_CHECKS_ONLY(
if (!areaDeleted)
_CheckMetaChunk(metaChunk);
)
}
/*static*/ void
MemoryManager::_PrepareMetaChunk(MetaChunk* metaChunk, size_t chunkSize)
{
Area* area = metaChunk->GetArea();
if (metaChunk == area->metaChunks) {
// the first chunk is shorter
size_t unusableSize = ROUNDUP(SLAB_AREA_STRUCT_OFFSET + kAreaAdminSize,
chunkSize);
metaChunk->chunkBase = area->BaseAddress() + unusableSize;
metaChunk->totalSize = SLAB_CHUNK_SIZE_LARGE - unusableSize;
}
metaChunk->chunkSize = chunkSize;
metaChunk->chunkCount = metaChunk->totalSize / chunkSize;
metaChunk->usedChunkCount = 0;
metaChunk->freeChunks = NULL;
for (int32 i = metaChunk->chunkCount - 1; i >= 0; i--)
_push(metaChunk->freeChunks, metaChunk->chunks + i);
metaChunk->firstFreeChunk = 0;
metaChunk->lastFreeChunk = metaChunk->chunkCount - 1;
PARANOID_CHECKS_ONLY(_CheckMetaChunk(metaChunk));
}
/*static*/ void
MemoryManager::_AddArea(Area* area)
{
T(AddArea(area));
// add the area to the hash table
WriteLocker writeLocker(sAreaTableLock);
sAreaTable.InsertUnchecked(area);
writeLocker.Unlock();
// add the area's meta chunks to the free lists
sFreeShortMetaChunks.Add(&area->metaChunks[0]);
for (int32 i = 1; i < SLAB_META_CHUNKS_PER_AREA; i++)
sFreeCompleteMetaChunks.Add(&area->metaChunks[i]);
}
/*static*/ status_t
MemoryManager::_AllocateArea(uint32 flags, Area*& _area)
{
TRACE("MemoryManager::_AllocateArea(%#" B_PRIx32 ")\n", flags);
ASSERT((flags & CACHE_DONT_LOCK_KERNEL_SPACE) == 0);
mutex_unlock(&sLock);
size_t pagesNeededToMap = 0;
void* areaBase;
Area* area;
VMArea* vmArea = NULL;
if (sKernelArgs == NULL) {
// create an area
uint32 areaCreationFlags = (flags & CACHE_PRIORITY_VIP) != 0
? CREATE_AREA_PRIORITY_VIP : 0;
area_id areaID = vm_create_null_area(B_SYSTEM_TEAM, kSlabAreaName,
&areaBase, B_ANY_KERNEL_BLOCK_ADDRESS, SLAB_AREA_SIZE,
areaCreationFlags);
if (areaID < 0) {
mutex_lock(&sLock);
return areaID;
}
area = _AreaForAddress((addr_t)areaBase);
// map the memory for the administrative structure
VMAddressSpace* addressSpace = VMAddressSpace::Kernel();
VMTranslationMap* translationMap = addressSpace->TranslationMap();
pagesNeededToMap = translationMap->MaxPagesNeededToMap(
(addr_t)area, (addr_t)areaBase + SLAB_AREA_SIZE - 1);
vmArea = VMAreaHash::Lookup(areaID);
status_t error = _MapChunk(vmArea, (addr_t)area, kAreaAdminSize,
pagesNeededToMap, flags);
if (error != B_OK) {
delete_area(areaID);
mutex_lock(&sLock);
return error;
}
dprintf("slab memory manager: created area %p (%" B_PRId32 ")\n", area,
areaID);
} else {
// no areas yet -- allocate raw memory
areaBase = (void*)vm_allocate_early(sKernelArgs, SLAB_AREA_SIZE,
SLAB_AREA_SIZE, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA,
SLAB_AREA_SIZE);
if (areaBase == NULL) {
mutex_lock(&sLock);
return B_NO_MEMORY;
}
area = _AreaForAddress((addr_t)areaBase);
TRACE("MemoryManager::_AllocateArea(): allocated early area %p\n",
area);
}
// init the area structure
area->vmArea = vmArea;
area->reserved_memory_for_mapping = pagesNeededToMap * B_PAGE_SIZE;
area->usedMetaChunkCount = 0;
area->fullyMapped = vmArea == NULL;
// init the meta chunks
for (int32 i = 0; i < SLAB_META_CHUNKS_PER_AREA; i++) {
MetaChunk* metaChunk = area->metaChunks + i;
metaChunk->chunkSize = 0;
metaChunk->chunkBase = (addr_t)areaBase + i * SLAB_CHUNK_SIZE_LARGE;
metaChunk->totalSize = SLAB_CHUNK_SIZE_LARGE;
// Note: chunkBase and totalSize aren't correct for the first
// meta chunk. They will be set in _PrepareMetaChunk().
metaChunk->chunkCount = 0;
metaChunk->usedChunkCount = 0;
metaChunk->freeChunks = NULL;
}
mutex_lock(&sLock);
_area = area;
T(AllocateArea(area, flags));
return B_OK;
}
/*static*/ void
MemoryManager::_FreeArea(Area* area, bool areaRemoved, uint32 flags)
{
TRACE("MemoryManager::_FreeArea(%p, %#" B_PRIx32 ")\n", area, flags);
T(FreeArea(area, areaRemoved, flags));
ASSERT(area->usedMetaChunkCount == 0);
if (!areaRemoved) {
// remove the area's meta chunks from the free lists
ASSERT(area->metaChunks[0].usedChunkCount == 0);
sFreeShortMetaChunks.Remove(&area->metaChunks[0]);
for (int32 i = 1; i < SLAB_META_CHUNKS_PER_AREA; i++) {
ASSERT(area->metaChunks[i].usedChunkCount == 0);
sFreeCompleteMetaChunks.Remove(&area->metaChunks[i]);
}
// remove the area from the hash table
WriteLocker writeLocker(sAreaTableLock);
sAreaTable.RemoveUnchecked(area);
writeLocker.Unlock();
}
// We want to keep one or two free areas as a reserve.
if (sFreeAreaCount <= 1) {
_PushFreeArea(area);
return;
}
if (area->vmArea == NULL || (flags & CACHE_DONT_LOCK_KERNEL_SPACE) != 0) {
// This is either early in the boot process or we aren't allowed to
// delete the area now.
_PushFreeArea(area);
_RequestMaintenance();
return;
}
mutex_unlock(&sLock);
dprintf("slab memory manager: deleting area %p (%" B_PRId32 ")\n", area,
area->vmArea->id);
size_t memoryToUnreserve = area->reserved_memory_for_mapping;
delete_area(area->vmArea->id);
vm_unreserve_memory(memoryToUnreserve);
mutex_lock(&sLock);
}
/*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);
T(Map(address, size, flags));
if (vmArea == NULL) {
// everything is mapped anyway
return B_OK;
}
VMAddressSpace* addressSpace = VMAddressSpace::Kernel();
VMTranslationMap* translationMap = addressSpace->TranslationMap();
// reserve memory for the chunk
int priority = (flags & CACHE_PRIORITY_VIP) != 0
? VM_PRIORITY_VIP : VM_PRIORITY_SYSTEM;
size_t reservedMemory = size + reserveAdditionalMemory;
status_t error = vm_try_reserve_memory(size, priority,
(flags & CACHE_DONT_WAIT_FOR_MEMORY) != 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);
vm_page_reservation reservation;
if ((flags & CACHE_DONT_WAIT_FOR_MEMORY) != 0) {
if (!vm_page_try_reserve_pages(&reservation, reservedPages, priority)) {
vm_unreserve_memory(reservedMemory);
return B_WOULD_BLOCK;
}
} else
vm_page_reserve_pages(&reservation, reservedPages, priority);
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(&reservation, PAGE_STATE_WIRED);
cache->InsertPage(page, offset);
page->IncrementWiredCount();
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,
vmArea->MemoryType(), &reservation);
}
translationMap->Unlock();
cache->ReleaseRefAndUnlock();
vm_page_unreserve_pages(&reservation);
return B_OK;
}
/*static*/ status_t
MemoryManager::_UnmapChunk(VMArea* vmArea, addr_t address, size_t size,
uint32 flags)
{
T(Unmap(address, size, 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->DecrementWiredCount();
cache->RemovePage(page);
// the iterator is remove-safe
vm_page_free(cache, page);
}
cache->ReleaseRefAndUnlock();
vm_unreserve_memory(size);
return B_OK;
}
/*static*/ void
MemoryManager::_UnmapFreeChunksEarly(Area* area)
{
if (!area->fullyMapped)
return;
TRACE("MemoryManager::_UnmapFreeChunksEarly(%p)\n", area);
// unmap the space before the Area structure
#if SLAB_AREA_STRUCT_OFFSET > 0
_UnmapChunk(area->vmArea, area->BaseAddress(), SLAB_AREA_STRUCT_OFFSET,
0);
#endif
for (int32 i = 0; i < SLAB_META_CHUNKS_PER_AREA; i++) {
MetaChunk* metaChunk = area->metaChunks + i;
if (metaChunk->chunkSize == 0) {
// meta chunk is free -- unmap it completely
if (i == 0) {
_UnmapChunk(area->vmArea, (addr_t)area + kAreaAdminSize,
SLAB_CHUNK_SIZE_LARGE - kAreaAdminSize, 0);
} else {
_UnmapChunk(area->vmArea,
area->BaseAddress() + i * SLAB_CHUNK_SIZE_LARGE,
SLAB_CHUNK_SIZE_LARGE, 0);
}
} else {
// unmap free chunks
for (Chunk* chunk = metaChunk->freeChunks; chunk != NULL;
chunk = chunk->next) {
_UnmapChunk(area->vmArea, _ChunkAddress(metaChunk, chunk),
metaChunk->chunkSize, 0);
}
// The first meta chunk might have space before its first chunk.
if (i == 0) {
addr_t unusedStart = (addr_t)area + kAreaAdminSize;
if (unusedStart < metaChunk->chunkBase) {
_UnmapChunk(area->vmArea, unusedStart,
metaChunk->chunkBase - unusedStart, 0);
}
}
}
}
area->fullyMapped = false;
}
/*static*/ void
MemoryManager::_ConvertEarlyArea(Area* area)
{
void* address = (void*)area->BaseAddress();
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);
}
/*static*/ void
MemoryManager::_RequestMaintenance()
{
if ((sFreeAreaCount > 0 && sFreeAreaCount <= 2) || sMaintenanceNeeded)
return;
sMaintenanceNeeded = true;
request_memory_manager_maintenance();
}
/*static*/ bool
MemoryManager::_IsChunkInFreeList(const MetaChunk* metaChunk,
const Chunk* chunk)
{
Chunk* freeChunk = metaChunk->freeChunks;
while (freeChunk != NULL) {
if (freeChunk == chunk)
return true;
freeChunk = freeChunk->next;
}
return false;
}
#if DEBUG_SLAB_MEMORY_MANAGER_PARANOID_CHECKS
/*static*/ void
MemoryManager::_CheckMetaChunk(MetaChunk* metaChunk)
{
Area* area = metaChunk->GetArea();
int32 metaChunkIndex = metaChunk - area->metaChunks;
if (metaChunkIndex < 0 || metaChunkIndex >= SLAB_META_CHUNKS_PER_AREA) {
panic("invalid meta chunk %p!", metaChunk);
return;
}
switch (metaChunk->chunkSize) {
case 0:
// unused
return;
case SLAB_CHUNK_SIZE_SMALL:
case SLAB_CHUNK_SIZE_MEDIUM:
case SLAB_CHUNK_SIZE_LARGE:
break;
default:
panic("meta chunk %p has invalid chunk size: %" B_PRIuSIZE,
metaChunk, metaChunk->chunkSize);
return;
}
if (metaChunk->totalSize > SLAB_CHUNK_SIZE_LARGE) {
panic("meta chunk %p has invalid total size: %" B_PRIuSIZE,
metaChunk, metaChunk->totalSize);
return;
}
addr_t expectedBase = area->BaseAddress()
+ metaChunkIndex * SLAB_CHUNK_SIZE_LARGE;
if (metaChunk->chunkBase < expectedBase
|| metaChunk->chunkBase - expectedBase + metaChunk->totalSize
> SLAB_CHUNK_SIZE_LARGE) {
panic("meta chunk %p has invalid base address: %" B_PRIxADDR, metaChunk,
metaChunk->chunkBase);
return;
}
if (metaChunk->chunkCount != metaChunk->totalSize / metaChunk->chunkSize) {
panic("meta chunk %p has invalid chunk count: %u", metaChunk,
metaChunk->chunkCount);
return;
}
if (metaChunk->usedChunkCount > metaChunk->chunkCount) {
panic("meta chunk %p has invalid unused chunk count: %u", metaChunk,
metaChunk->usedChunkCount);
return;
}
if (metaChunk->firstFreeChunk > metaChunk->chunkCount) {
panic("meta chunk %p has invalid first free chunk: %u", metaChunk,
metaChunk->firstFreeChunk);
return;
}
if (metaChunk->lastFreeChunk >= metaChunk->chunkCount) {
panic("meta chunk %p has invalid last free chunk: %u", metaChunk,
metaChunk->lastFreeChunk);
return;
}
// check free list for structural sanity
uint32 freeChunks = 0;
for (Chunk* chunk = metaChunk->freeChunks; chunk != NULL;
chunk = chunk->next) {
if ((addr_t)chunk % sizeof(Chunk) != 0 || chunk < metaChunk->chunks
|| chunk >= metaChunk->chunks + metaChunk->chunkCount) {
panic("meta chunk %p has invalid element in free list, chunk: %p",
metaChunk, chunk);
return;
}
if (++freeChunks > metaChunk->chunkCount) {
panic("meta chunk %p has cyclic free list", metaChunk);
return;
}
}
if (freeChunks + metaChunk->usedChunkCount > metaChunk->chunkCount) {
panic("meta chunk %p has mismatching free/used chunk counts: total: "
"%u, used: %u, free: %" B_PRIu32, metaChunk, metaChunk->chunkCount,
metaChunk->usedChunkCount, freeChunks);
return;
}
// count used chunks by looking at their reference/next field
uint32 usedChunks = 0;
for (uint32 i = 0; i < metaChunk->chunkCount; i++) {
if (!_IsChunkFree(metaChunk, metaChunk->chunks + i))
usedChunks++;
}
if (usedChunks != metaChunk->usedChunkCount) {
panic("meta chunk %p has used chunks that appear free: total: "
"%u, used: %u, appearing used: %" B_PRIu32, metaChunk,
metaChunk->chunkCount, metaChunk->usedChunkCount, usedChunks);
return;
}
// check free range
for (uint32 i = metaChunk->firstFreeChunk; i < metaChunk->lastFreeChunk;
i++) {
if (!_IsChunkFree(metaChunk, metaChunk->chunks + i)) {
panic("meta chunk %p has used chunk in free range, chunk: %p (%"
B_PRIu32 ", free range: %u - %u)", metaChunk,
metaChunk->chunks + i, i, metaChunk->firstFreeChunk,
metaChunk->lastFreeChunk);
return;
}
}
}
#endif // DEBUG_SLAB_MEMORY_MANAGER_PARANOID_CHECKS
/*static*/ int
MemoryManager::_DumpRawAllocations(int argc, char** argv)
{
kprintf("%-*s meta chunk chunk %-*s size (KB)\n",
B_PRINTF_POINTER_WIDTH, "area", B_PRINTF_POINTER_WIDTH, "base");
size_t totalSize = 0;
for (AreaTable::Iterator it = sAreaTable.GetIterator();
Area* area = it.Next();) {
for (int32 i = 0; i < SLAB_META_CHUNKS_PER_AREA; i++) {
MetaChunk* metaChunk = area->metaChunks + i;
if (metaChunk->chunkSize == 0)
continue;
for (uint32 k = 0; k < metaChunk->chunkCount; k++) {
Chunk* chunk = metaChunk->chunks + k;
// skip free chunks
if (_IsChunkFree(metaChunk, chunk))
continue;
addr_t reference = chunk->reference;
if ((reference & 1) == 0 || reference == 1)
continue;
addr_t chunkAddress = _ChunkAddress(metaChunk, chunk);
size_t size = reference - chunkAddress + 1;
totalSize += size;
kprintf("%p %10" B_PRId32 " %5" B_PRIu32 " %p %9"
B_PRIuSIZE "\n", area, i, k, (void*)chunkAddress,
size / 1024);
}
}
}
kprintf("total:%*s%9" B_PRIuSIZE "\n", (2 * B_PRINTF_POINTER_WIDTH) + 21,
"", totalSize / 1024);
return 0;
}
/*static*/ void
MemoryManager::_PrintMetaChunkTableHeader(bool printChunks)
{
if (printChunks)
kprintf("chunk base cache object size cache name\n");
else
kprintf("chunk base\n");
}
/*static*/ void
MemoryManager::_DumpMetaChunk(MetaChunk* metaChunk, bool printChunks,
bool printHeader)
{
if (printHeader)
_PrintMetaChunkTableHeader(printChunks);
const char* type = "empty";
if (metaChunk->chunkSize != 0) {
switch (metaChunk->chunkSize) {
case SLAB_CHUNK_SIZE_SMALL:
type = "small";
break;
case SLAB_CHUNK_SIZE_MEDIUM:
type = "medium";
break;
case SLAB_CHUNK_SIZE_LARGE:
type = "large";
break;
}
}
int metaChunkIndex = metaChunk - metaChunk->GetArea()->metaChunks;
kprintf("%5d %p --- %6s meta chunk", metaChunkIndex,
(void*)metaChunk->chunkBase, type);
if (metaChunk->chunkSize != 0) {
kprintf(": %4u/%4u used, %-4u-%4u free ------------\n",
metaChunk->usedChunkCount, metaChunk->chunkCount,
metaChunk->firstFreeChunk, metaChunk->lastFreeChunk);
} else
kprintf(" --------------------------------------------\n");
if (metaChunk->chunkSize == 0 || !printChunks)
return;
for (uint32 i = 0; i < metaChunk->chunkCount; i++) {
Chunk* chunk = metaChunk->chunks + i;
// skip free chunks
if (_IsChunkFree(metaChunk, chunk)) {
if (!_IsChunkInFreeList(metaChunk, chunk)) {
kprintf("%5" B_PRIu32 " %p appears free, but isn't in free "
"list!\n", i, (void*)_ChunkAddress(metaChunk, chunk));
}
continue;
}
addr_t reference = chunk->reference;
if ((reference & 1) == 0) {
ObjectCache* cache = (ObjectCache*)reference;
kprintf("%5" B_PRIu32 " %p %p %11" B_PRIuSIZE " %s\n", i,
(void*)_ChunkAddress(metaChunk, chunk), cache,
cache != NULL ? cache->object_size : 0,
cache != NULL ? cache->name : "");
} else if (reference != 1) {
kprintf("%5" B_PRIu32 " %p raw allocation up to %p\n", i,
(void*)_ChunkAddress(metaChunk, chunk), (void*)reference);
}
}
}
/*static*/ int
MemoryManager::_DumpMetaChunk(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;
Area* area = _AreaForAddress(address);
MetaChunk* metaChunk;
if ((addr_t)address >= (addr_t)area->metaChunks
&& (addr_t)address
< (addr_t)(area->metaChunks + SLAB_META_CHUNKS_PER_AREA)) {
metaChunk = (MetaChunk*)(addr_t)address;
} else {
metaChunk = area->metaChunks
+ (address % SLAB_AREA_SIZE) / SLAB_CHUNK_SIZE_LARGE;
}
_DumpMetaChunk(metaChunk, true, true);
return 0;
}
/*static*/ void
MemoryManager::_DumpMetaChunks(const char* name, MetaChunkList& metaChunkList,
bool printChunks)
{
kprintf("%s:\n", name);
for (MetaChunkList::Iterator it = metaChunkList.GetIterator();
MetaChunk* metaChunk = it.Next();) {
_DumpMetaChunk(metaChunk, printChunks, false);
}
}
/*static*/ int
MemoryManager::_DumpMetaChunks(int argc, char** argv)
{
bool printChunks = argc > 1 && strcmp(argv[1], "-c") == 0;
_PrintMetaChunkTableHeader(printChunks);
_DumpMetaChunks("free complete", sFreeCompleteMetaChunks, printChunks);
_DumpMetaChunks("free short", sFreeShortMetaChunks, printChunks);
_DumpMetaChunks("partial small", sPartialMetaChunksSmall, printChunks);
_DumpMetaChunks("partial medium", sPartialMetaChunksMedium, printChunks);
return 0;
}
/*static*/ int
MemoryManager::_DumpArea(int argc, char** argv)
{
bool printChunks = false;
int argi = 1;
while (argi < argc) {
if (argv[argi][0] != '-')
break;
const char* arg = argv[argi++];
if (strcmp(arg, "-c") == 0) {
printChunks = true;
} else {
print_debugger_command_usage(argv[0]);
return 0;
}
}
if (argi + 1 != argc) {
print_debugger_command_usage(argv[0]);
return 0;
}
uint64 address;
if (!evaluate_debug_expression(argv[argi], &address, false))
return 0;
Area* area = _AreaForAddress((addr_t)address);
for (uint32 k = 0; k < SLAB_META_CHUNKS_PER_AREA; k++) {
MetaChunk* metaChunk = area->metaChunks + k;
_DumpMetaChunk(metaChunk, printChunks, k == 0);
}
return 0;
}
/*static*/ int
MemoryManager::_DumpAreas(int argc, char** argv)
{
kprintf(" %*s %*s meta small medium large\n",
B_PRINTF_POINTER_WIDTH, "base", B_PRINTF_POINTER_WIDTH, "area");
size_t totalTotalSmall = 0;
size_t totalUsedSmall = 0;
size_t totalTotalMedium = 0;
size_t totalUsedMedium = 0;
size_t totalUsedLarge = 0;
uint32 areaCount = 0;
for (AreaTable::Iterator it = sAreaTable.GetIterator();
Area* area = it.Next();) {
areaCount++;
// sum up the free/used counts for the chunk sizes
int totalSmall = 0;
int usedSmall = 0;
int totalMedium = 0;
int usedMedium = 0;
int usedLarge = 0;
for (int32 i = 0; i < SLAB_META_CHUNKS_PER_AREA; i++) {
MetaChunk* metaChunk = area->metaChunks + i;
if (metaChunk->chunkSize == 0)
continue;
switch (metaChunk->chunkSize) {
case SLAB_CHUNK_SIZE_SMALL:
totalSmall += metaChunk->chunkCount;
usedSmall += metaChunk->usedChunkCount;
break;
case SLAB_CHUNK_SIZE_MEDIUM:
totalMedium += metaChunk->chunkCount;
usedMedium += metaChunk->usedChunkCount;
break;
case SLAB_CHUNK_SIZE_LARGE:
usedLarge += metaChunk->usedChunkCount;
break;
}
}
kprintf("%p %p %2u/%2u %4d/%4d %3d/%3d %5d\n",
area, area->vmArea, area->usedMetaChunkCount,
SLAB_META_CHUNKS_PER_AREA, usedSmall, totalSmall, usedMedium,
totalMedium, usedLarge);
totalTotalSmall += totalSmall;
totalUsedSmall += usedSmall;
totalTotalMedium += totalMedium;
totalUsedMedium += usedMedium;
totalUsedLarge += usedLarge;
}
kprintf("%d free area%s:\n", sFreeAreaCount,
sFreeAreaCount == 1 ? "" : "s");
for (Area* area = sFreeAreas; area != NULL; area = area->next) {
areaCount++;
kprintf("%p %p\n", area, area->vmArea);
}
kprintf("total usage:\n");
kprintf(" small: %" B_PRIuSIZE "/%" B_PRIuSIZE "\n", totalUsedSmall,
totalTotalSmall);
kprintf(" medium: %" B_PRIuSIZE "/%" B_PRIuSIZE "\n", totalUsedMedium,
totalTotalMedium);
kprintf(" large: %" B_PRIuSIZE "\n", totalUsedLarge);
kprintf(" memory: %" B_PRIuSIZE "/%" B_PRIu32 " KB\n",
(totalUsedSmall * SLAB_CHUNK_SIZE_SMALL
+ totalUsedMedium * SLAB_CHUNK_SIZE_MEDIUM
+ totalUsedLarge * SLAB_CHUNK_SIZE_LARGE) / 1024,
areaCount * SLAB_AREA_SIZE / 1024);
kprintf(" overhead: %" B_PRIuSIZE " KB\n",
areaCount * kAreaAdminSize / 1024);
return 0;
}
#if SLAB_MEMORY_MANAGER_ALLOCATION_TRACKING
void
MemoryManager::_AddTrackingInfo(void* allocation, size_t size,
AbstractTraceEntryWithStackTrace* traceEntry)
{
_TrackingInfoFor(allocation, size)->Init(traceEntry);
}
#endif // SLAB_MEMORY_MANAGER_ALLOCATION_TRACKING
RANGE_MARKER_FUNCTION_END(SlabMemoryManager)
↑ V595 The 'metaChunkList' pointer was utilized before it was verified against nullptr. Check lines: 1122, 1137.
↑ V595 The 'metaChunkList' pointer was utilized before it was verified against nullptr. Check lines: 1059, 1089.