#define ALLOC_MINBITS 3/* smallest chunk size is 8 bytes */ #define ALLOCSET_NUM_FREELISTS 11 #define ALLOC_CHUNK_LIMIT (1 << (ALLOCSET_NUM_FREELISTS-1+ALLOC_MINBITS)) /* Size of largest chunk that we use a fixed size for */ #define ALLOC_CHUNK_FRACTION 4 /* We allow chunks to be at most 1/4 of maxBlockSize (less overhead) */
/* Validate a freelist index retrieved from a chunk header */ #define FreeListIdxIsValid(fidx) \
((fidx) >= 0 && (fidx) < ALLOCSET_NUM_FREELISTS)
/* Determine the size of the chunk based on the freelist index */ #define GetChunkSizeFromFreeListIdx(fidx) \
((((Size) 1) << ALLOC_MINBITS) << (fidx))
/* *AllocSetContextisourstandardimplementationofMemoryContext. * *Note:header.isResetmeansthereisnothingforAllocSetResettodo. *Thisisdifferentfromtheasetbeingphysicallyempty(emptyblockslist) *becausewewillstillhaveakeeperblock.It'salsodifferentfromtheset *beinglogicallyempty,becausewedon'tattempttodetectpfree'ingthe *lastactivechunk.
*/ typedefstruct AllocSetContext
{
MemoryContextData header; /* Standard memory-context fields */ /* Info about storage allocated in this context: */
AllocBlock blocks; /* head of list of blocks in this set */
MemoryChunk *freelist[ALLOCSET_NUM_FREELISTS]; /* free chunk lists */ /* Allocation parameters for this context: */
uint32 initBlockSize; /* initial block size */
uint32 maxBlockSize; /* maximum block size */
uint32 nextBlockSize; /* next block size to allocate */
uint32 allocChunkLimit; /* effective chunk size limit */ /* freelist this context could be put in, or -1 if not a candidate: */ int freeListIndex; /* index in context_freelists[], or -1 */
} AllocSetContext;
typedef AllocSetContext *AllocSet;
/* *AllocBlock *AnAllocBlockistheunitofmemorythatisobtainedbyaset.c *frommalloc().ItcontainsoneormoreMemoryChunks,whichare *theunitsrequestedbypalloc()andfreedbypfree().MemoryChunks *cannotbereturnedtomalloc()individually,insteadtheyareput *onfreelistsbypfree()andre-usedbythenextpalloc()thathas *amatchingrequestsize. * *AllocBlockDataistheheaderdataforablock---theusablespace *withintheblockbeginsatthenextalignmentboundary.
*/ typedefstruct AllocBlockData
{
AllocSet aset; /* aset that owns this block */
AllocBlock prev; /* prev block in aset's blocks list, if any */
AllocBlock next; /* next block in aset's blocks list, if any */ char *freeptr; /* start of free space in this block */ char *endptr; /* end of space in this block */
} AllocBlockData;
/* Statically assert that we only have a 16-bit input value. */
StaticAssertDecl(ALLOC_CHUNK_LIMIT < (1 << 16), "ALLOC_CHUNK_LIMIT must be less than 64kB");
/* ensure MemoryChunk's size is properly maxaligned */
StaticAssertDecl(ALLOC_CHUNKHDRSZ == MAXALIGN(ALLOC_CHUNKHDRSZ), "sizeof(MemoryChunk) is not maxaligned"); /* check we have enough space to store the freelist link */
StaticAssertDecl(sizeof(AllocFreeListLink) <= (1 << ALLOC_MINBITS), "sizeof(AllocFreeListLink) larger than minimum allocation size");
/* Finally, do the type-independent part of context creation */
MemoryContextCreate((MemoryContext) set,
T_AllocSetContext,
MCTX_ASET_ID,
parent,
name);
/* All that remains is to free the header/initial block */
free(oldset);
}
Assert(freelist->num_free == 0);
}
/* Now add the just-deleted context to the freelist. */
set->header.nextchild = (MemoryContext) freelist->first_free;
freelist->first_free = set;
freelist->num_free++;
return;
}
/* Free all blocks, except the keeper which is part of context header */ while (block != NULL)
{
AllocBlock next = block->next;
if (!IsKeeperBlock(set, block))
context->mem_allocated -= block->endptr - ((char *) block);
/* mark the MemoryChunk as externally managed */
MemoryChunkSetHdrMaskExternal(chunk, MCTX_ASET_ID);
#ifdef MEMORY_CONTEXT_CHECKING
chunk->requested_size = size; /* set mark to catch clobber of "unused" space */
Assert(size < chunk_size);
set_sentinel(MemoryChunkGetPointer(chunk), size); #endif #ifdef RANDOMIZE_ALLOCATED_MEMORY /* fill the allocated space with junk */
randomize_mem((char *) MemoryChunkGetPointer(chunk), size); #endif
/* store the free list index in the value field */
MemoryChunkSetHdrMask(chunk, block, fidx, MCTX_ASET_ID);
#ifdef MEMORY_CONTEXT_CHECKING
chunk->requested_size = size; /* set mark to catch clobber of "unused" space */ if (size < chunk_size)
set_sentinel(MemoryChunkGetPointer(chunk), size); #endif #ifdef RANDOMIZE_ALLOCATED_MEMORY /* fill the allocated space with junk */
randomize_mem((char *) MemoryChunkGetPointer(chunk), size); #endif
/* Ensure any padding bytes are marked NOACCESS. */
VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size,
chunk_size - size);
/* Disallow access to the chunk header. */
VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ);
/* due to the keeper block set->blocks should always be valid */
Assert(set->blocks != NULL);
block = set->blocks;
availspace = block->endptr - block->freeptr;
/* Prepare to initialize the chunk header. */
VALGRIND_MAKE_MEM_UNDEFINED(chunk, ALLOC_CHUNKHDRSZ);
block->freeptr += (availchunk + ALLOC_CHUNKHDRSZ);
availspace -= (availchunk + ALLOC_CHUNKHDRSZ);
/* store the freelist index in the value field */
MemoryChunkSetHdrMask(chunk, block, a_fidx, MCTX_ASET_ID); #ifdef MEMORY_CONTEXT_CHECKING
chunk->requested_size = InvalidAllocSize; /* mark it free */ #endif /* push this chunk onto the free list */
link = GetFreeListLink(chunk);
/* Allow access to the chunk header. */
VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ);
Assert(fidx == MemoryChunkGetValue(chunk));
/* pop this chunk off the freelist */
VALGRIND_MAKE_MEM_DEFINED(link, sizeof(AllocFreeListLink));
set->freelist[fidx] = link->next;
VALGRIND_MAKE_MEM_NOACCESS(link, sizeof(AllocFreeListLink));
#ifdef MEMORY_CONTEXT_CHECKING
chunk->requested_size = size; /* set mark to catch clobber of "unused" space */ if (size < GetChunkSizeFromFreeListIdx(fidx))
set_sentinel(MemoryChunkGetPointer(chunk), size); #endif #ifdef RANDOMIZE_ALLOCATED_MEMORY /* fill the allocated space with junk */
randomize_mem((char *) MemoryChunkGetPointer(chunk), size); #endif
/* Ensure any padding bytes are marked NOACCESS. */
VALGRIND_MAKE_MEM_NOACCESS((char *) MemoryChunkGetPointer(chunk) + size,
GetChunkSizeFromFreeListIdx(fidx) - size);
/* Disallow access to the chunk header. */
VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ);
/* *Trytoverifythatwehaveasaneblockpointer:theblockheader *shouldreferenceanasetandthefreeptrshouldmatchtheendptr.
*/ if (!AllocBlockIsValid(block) || block->freeptr != block->endptr)
elog(ERROR, "could not find block containing chunk %p", chunk);
set = block->aset;
#ifdef MEMORY_CONTEXT_CHECKING
{ /* Test for someone scribbling on unused space in chunk */
Assert(chunk->requested_size < (block->endptr - (char *) pointer)); if (!sentinel_ok(pointer, chunk->requested_size))
elog(WARNING, "detected write past chunk end in %s %p",
set->header.name, chunk);
} #endif
/* OK, remove block from aset's list and free it */ if (block->prev)
block->prev->next = block->next; else
set->blocks = block->next; if (block->next)
block->next->prev = block->prev;
/* *Inthispath,forspeedreasonswejustAssertthatthereferenced *blockisgood.WecanalsoAssertthatthevaluefieldissane. *FuturefieldexperiencemayshowthattheseAssertshadbetter *becomeregularruntimetest-and-elogchecks.
*/
Assert(AllocBlockIsValid(block));
set = block->aset;
fidx = MemoryChunkGetValue(chunk);
Assert(FreeListIdxIsValid(fidx));
link = GetFreeListLink(chunk);
/* *ItmightseemoddthatweuseelevelERRORfordouble-pfreebut *onlyWARNINGforwrite-past-chunk-end.Butthetwoconditionsare *notverycomparable.Inthedouble-pfreecasewecanprevent *corruptionbeforeithappens;whileifweletitgothrough,the *resultwouldbeacorruptedfreelistthatallowsthischunktoget *re-allocatedtwice.Thustheoriginalbugcouldcascadeinto *hard-to-understandmisbehaviorthatmightmanifestfarawayfrom *theactualsourceoftheproblem.Ontheotherhand,awritepast *chunkendcanberelativelybenignifjustafewbytestoomany *werewritten:often,onlypaddingorunusedspacegetsaffected. *Moreover,whateverdamagewasdoneisalreadydone,andwe'rejust *reportingafterthefactwithnoabilitytocleanitup.Sojust *warn,likeAllocSetCheckwoulddoifthechunkdidn'tgetfreed.
*/ #ifdef MEMORY_CONTEXT_CHECKING /* Test for previously-freed chunk */ if (unlikely(chunk->requested_size == InvalidAllocSize))
elog(ERROR, "detected double pfree in %s %p",
set->header.name, chunk); /* Test for someone scribbling on unused space in chunk */ if (chunk->requested_size < GetChunkSizeFromFreeListIdx(fidx)) if (!sentinel_ok(pointer, chunk->requested_size))
elog(WARNING, "detected write past chunk end in %s %p",
set->header.name, chunk); #endif
#ifdef CLOBBER_FREED_MEMORY
wipe_mem(pointer, GetChunkSizeFromFreeListIdx(fidx)); #endif /* push this chunk onto the top of the free list */
VALGRIND_MAKE_MEM_DEFINED(link, sizeof(AllocFreeListLink));
link->next = set->freelist[fidx];
VALGRIND_MAKE_MEM_NOACCESS(link, sizeof(AllocFreeListLink));
set->freelist[fidx] = chunk;
/* *Trytoverifythatwehaveasaneblockpointer:theblockheader *shouldreferenceanasetandthefreeptrshouldmatchtheendptr.
*/ if (!AllocBlockIsValid(block) || block->freeptr != block->endptr)
elog(ERROR, "could not find block containing chunk %p", chunk);
set = block->aset;
/* only check size in paths where the limits could be hit */
MemoryContextCheckSize((MemoryContext) set, size, flags);
oldchksize = block->endptr - (char *) pointer;
#ifdef MEMORY_CONTEXT_CHECKING /* Test for someone scribbling on unused space in chunk */
Assert(chunk->requested_size < oldchksize); if (!sentinel_ok(pointer, chunk->requested_size))
elog(WARNING, "detected write past chunk end in %s %p",
set->header.name, chunk); #endif
#ifdef MEMORY_CONTEXT_CHECKING /* ensure there's always space for the sentinel byte */
chksize = MAXALIGN(size + 1); #else
chksize = MAXALIGN(size); #endif
/* Do the realloc */
blksize = chksize + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
oldblksize = block->endptr - ((char *) block);
block = (AllocBlock) realloc(block, blksize); if (block == NULL)
{ /* Disallow access to the chunk header. */
VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ); return MemoryContextAllocationFailure(&set->header, size, flags);
}
/* updated separately, not to underflow when (oldblksize > blksize) */
set->header.mem_allocated -= oldblksize;
set->header.mem_allocated += blksize;
chunk->requested_size = size; /* set mark to catch clobber of "unused" space */
Assert(size < chksize);
set_sentinel(pointer, size); #else/* !MEMORY_CONTEXT_CHECKING */
#ifdef MEMORY_CONTEXT_CHECKING /* See comments in AllocSetFree about uses of ERROR and WARNING here */ /* Test for previously-freed chunk */ if (unlikely(chunk->requested_size == InvalidAllocSize))
elog(ERROR, "detected realloc of freed chunk in %s %p",
set->header.name, chunk); /* Test for someone scribbling on unused space in chunk */ if (chunk->requested_size < oldchksize) if (!sentinel_ok(pointer, chunk->requested_size))
elog(WARNING, "detected write past chunk end in %s %p",
set->header.name, chunk); #endif
#ifdef RANDOMIZE_ALLOCATED_MEMORY /* We can only fill the extra space if we know the prior request */ if (size > oldrequest)
randomize_mem((char *) pointer + oldrequest,
size - oldrequest); #endif
/* set mark to catch clobber of "unused" space */
if (size < oldchksize)
set_sentinel(pointer, size);
#else /* !MEMORY_CONTEXT_CHECKING */
/*
* We don't have the information to determine whether we're growing
* the old request or shrinking it, so we conservatively mark the
* entire new allocation DEFINED.
*/
VALGRIND_MAKE_MEM_NOACCESS(pointer, oldchksize);
VALGRIND_MAKE_MEM_DEFINED(pointer, size);
#endif
/* Disallow access to the chunk header. */
VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ);
return pointer;
}
else
{
/*
* Enlarge-a-small-chunk case. We just do this by brute force, ie,
* allocate a new chunk and copy the data. Since we know the existing
* data isn't huge, this won't involve any great memcpy expense, so
* it's not worth being smarter. (At one time we tried to avoid
* memcpy when it was possible to enlarge the chunk in-place, but that
* turns out to misbehave unpleasantly for repeated cycles of
* palloc/repalloc/pfree: the eventually freed chunks go into the
* wrong freelist for the next initial palloc request, and so we leak
* memory indefinitely. See pgsql-hackers archives for 2007-08-11.)
*/
AllocPointer newPointer;
Size oldsize;
/* allocate new chunk (this also checks size is valid) */
newPointer = AllocSetAlloc((MemoryContext) set, size, flags);
/* leave immediately if request was not completed */
if (newPointer == NULL)
{
/* Disallow access to the chunk header. */
VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ);
return MemoryContextAllocationFailure((MemoryContext) set, size, flags);
}
/*
* AllocSetAlloc() may have returned a region that is still NOACCESS.
* Change it to UNDEFINED for the moment; memcpy() will then transfer
* definedness from the old allocation to the new. If we know the old
* allocation, copy just that much. Otherwise, make the entire old
* chunk defined to avoid errors as we copy the currently-NOACCESS
* trailing bytes.
*/
VALGRIND_MAKE_MEM_UNDEFINED(newPointer, size);
#ifdef MEMORY_CONTEXT_CHECKING
oldsize = chunk->requested_size;
#else
oldsize = oldchksize;
VALGRIND_MAKE_MEM_DEFINED(pointer, oldsize);
#endif
/* transfer existing data (certain to fit) */
memcpy(newPointer, pointer, oldsize);
/* free old chunk */
AllocSetFree(pointer);
return newPointer;
}
}
/*
* AllocSetGetChunkContext
* Return the MemoryContext that 'pointer' belongs to.
*/
MemoryContext
AllocSetGetChunkContext(void *pointer)
{
MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
AllocBlock block;
AllocSet set;
/* Allow access to the chunk header. */
VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ);
if (MemoryChunkIsExternal(chunk))
block = ExternalChunkGetBlock(chunk);
else
block = (AllocBlock) MemoryChunkGetBlock(chunk);
/* Disallow access to the chunk header. */
VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ);
Assert(AllocBlockIsValid(block));
set = block->aset;
return &set->header;
}
/*
* AllocSetGetChunkSpace
* Given a currently-allocated chunk, determine the total space
* it occupies (including all memory-allocation overhead).
*/
Size
AllocSetGetChunkSpace(void *pointer)
{
MemoryChunk *chunk = PointerGetMemoryChunk(pointer);
int fidx;
/* Allow access to the chunk header. */
VALGRIND_MAKE_MEM_DEFINED(chunk, ALLOC_CHUNKHDRSZ);
if (MemoryChunkIsExternal(chunk))
{
AllocBlock block = ExternalChunkGetBlock(chunk);
/* Disallow access to the chunk header. */
VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ);
/*
* AllocSetIsEmpty
* Is an allocset empty of any allocated space?
*/
bool
AllocSetIsEmpty(MemoryContext context)
{
Assert(AllocSetIsValid(context));
/*
* For now, we say "empty" only if the context is new or just reset. We
* could examine the freelists to determine if all space has been freed,
* but it's not really worth the trouble for present uses of this
* functionality.
*/
if (context->isReset)
return true;
return false;
}
/*
* AllocSetStats
* Compute stats about memory consumption of an allocset.
*
* printfunc: if not NULL, pass a human-readable stats string to this.
* passthru: pass this pointer through to printfunc.
* totals: if not NULL, add stats about this context into *totals.
* print_to_stderr: print stats to stderr if true, elog otherwise.
*/
void
AllocSetStats(MemoryContext context,
MemoryStatsPrintFunc printfunc, void *passthru,
MemoryContextCounters *totals, bool print_to_stderr)
{
AllocSet set = (AllocSet) context;
Size nblocks = 0;
Size freechunks = 0;
Size totalspace;
Size freespace = 0;
AllocBlock block;
int fidx;
Assert(AllocSetIsValid(set));
/* Include context header in totalspace */
totalspace = MAXALIGN(sizeof(AllocSetContext));
/*
* AllocSetCheck
* Walk through chunks and check consistency of memory.
*
* NOTE: report errors as WARNING, *not* ERROR or FATAL. Otherwise you'll
* find yourself in an infinite loop when trouble occurs, because this
* routine will be entered again when elog cleanup tries to release memory!
*/
void
AllocSetCheck(MemoryContext context)
{
AllocSet set = (AllocSet) context;
const char *name = set->header.name;
AllocBlock prevblock;
AllocBlock block;
Size total_allocated = 0;
/*
* Empty block - empty can be keeper-block only
*/
if (!blk_used)
{
if (!IsKeeperBlock(set, block))
elog(WARNING, "problem in alloc set %s: empty block %p",
name, block);
}
/*
* Check block header fields
*/
if (block->aset != set ||
block->prev != prevblock ||
block->freeptr < bpoz ||
block->freeptr > block->endptr)
elog(WARNING, "problem in alloc set %s: corrupt header in block %p",
name, block);
/* make sure this chunk consumes the entire block */
if (chsize + ALLOC_CHUNKHDRSZ != blk_used)
elog(WARNING, "problem in alloc set %s: bad single-chunk %p in block %p",
name, chunk, block);
}
else
{
int fidx = MemoryChunkGetValue(chunk);
if (!FreeListIdxIsValid(fidx))
elog(WARNING, "problem in alloc set %s: bad chunk size for chunk %p in block %p",
name, chunk, block);
/*
* Check the stored block offset correctly references this
* block.
*/
if (block != MemoryChunkGetBlock(chunk))
elog(WARNING, "problem in alloc set %s: bad block offset for chunk %p in block %p",
name, chunk, block);
}
dsize = chunk->requested_size; /* real data */
/* an allocated chunk's requested size must be <= the chsize */
if (dsize != InvalidAllocSize && dsize > chsize)
elog(WARNING, "problem in alloc set %s: req size > alloc size for chunk %p in block %p",
name, chunk, block);
/* chsize must not be smaller than the first freelist's size */
if (chsize < (1 << ALLOC_MINBITS))
elog(WARNING, "problem in alloc set %s: bad size %zu for chunk %p in block %p",
name, chsize, chunk, block);
/*
* Check for overwrite of padding space in an allocated chunk.
*/
if (dsize != InvalidAllocSize && dsize < chsize &&
!sentinel_ok(chunk, ALLOC_CHUNKHDRSZ + dsize))
elog(WARNING, "problem in alloc set %s: detected write past chunk end in block %p, chunk %p",
name, block, chunk);
/* if chunk is allocated, disallow access to the chunk header */
if (dsize != InvalidAllocSize)
VALGRIND_MAKE_MEM_NOACCESS(chunk, ALLOC_CHUNKHDRSZ);
blk_data += chsize;
nchunks++;
bpoz += ALLOC_CHUNKHDRSZ + chsize;
}
if ((blk_data + (nchunks * ALLOC_CHUNKHDRSZ)) != blk_used)
elog(WARNING, "problem in alloc set %s: found inconsistent memory block %p",
name, block);
if (has_external_chunk && nchunks > 1)
elog(WARNING, "problem in alloc set %s: external chunk on non-dedicated block %p",
name, block);
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.