转载:PostgreSQL源码分析之内存上下文
2014-01-12 23:36
309 查看
转载:http://blog.chinaunix.net/uid-24774106-id-3547274.html
PostgreSQL是我们项目采用的数据库,从来没读过数据库的代码,尽管也曾写过一些应用层的代码,希望今年能粗读一遍PostgreSQL的源码,提高自己对数据库的理解。
本系列以PostgreSQL的最新版本9.2.3源码为例,学习PostgreSQL。
PostgreSQL从7.1开始,引入了内存上下文(MemoryContent)机制,片汤话我不多说,简单的理解,内存上下文提供了一种管理内存的机制。我们通过图表和代码分析来理解PostgreSQL的内存上下文。
内存上下文的主要数据结构都在上图中了,主要是4个基本数据结构之间的关系分别是
MemoryContextData
AllocSetContext
AllocBlockData
AllocChunkData
这四个数据结构中核心的数据结构是AllocSetContext,从上图中也可以清楚的看出来。下面我们详细讲述之
任何一个PostgreSQL进程使用内存上下文之前,都必须首先进行初始化,这句话是一句废话,呵呵。内存上下文的初始化是PostMasterMain函数里面开头调用的MemoryContextInit完成的。这个函数干了两件事情:
创建了内存上下文的根 TopMemoryContext
创建了TopMemoryContext的第一个子节点ErrorContext。
创建内存上下文的工作是由AllocSetContextCreate函数完成的。这个很有意思。MemoryContext明明是上面提到的第一种数据结构,他的创建函数偏偏是AllocSetContextCreate,这个函数顾名思义也知道是创建第二个数据结构的。这其实很好理解,看上面绘制的图片可以看出,MemoryContextData不过是核心数据结构AllocSetContext的第一个成员变量(更严格的说是它的一个指针类型的成员变量指向这个MemoryContextData)。
AllocSetContextCreate这个函数其实是分成两部分的
MemoryContextCreate ,创建MemoryContext
创建AllocSetContxt剩余的部分,主要是确定initBlockSize,nextBlockSize,maxBlockSize和allocChunkLimit的大小。
对于MemoryContextCreate这个函数,TopMemoryContext是没有parent的,所以他的parent指针是NULL;另外一个需注意的点是method,在TopMemoryContext创建methods指针指向了一个结构体,这个结构体内是一系列分配释放相关的函数,都画在了上图的右上角。因为TopMemeoryContext是根,所以他的分配 需要用malloc,其他的MemoryContext创建的时候,就不需要调用系统函数malloc了,直接用method函数指针系列里面AllocSetAlloc函数分配就行了。见如下代码
if (TopMemoryContext != NULL)
{
/* Normal case: allocate
the node in TopMemoryContext */
node = (MemoryContext) MemoryContextAlloc(TopMemoryContext,
needed);
}
else
{
/* Special case for startup: use
good ol' malloc */
node = (MemoryContext) malloc(needed);
Assert(node != NULL);
}
确定initBlockSize,nextBlockSize等的代码比较简单,我就不赘述了。allocChunkLimit这个参数的含义是在这个内存上下文中,大内存块的门限值。比如如果maxBlockSize=8K,那么系统认为1KB是比较大的内存块,低于1KB的这个门限值的,都认为是小块内存。在分配策略和释放策略上,是不同的。我们认为大内存的分配不频繁,所以我们采用直接malloc的方法,如果释放的话,就真的调用free,将内存返还给系统。但是小内存块则不同,我们认为小内存块的分配是频繁的,而且频繁的malloc/free会造成内存碎片,所以当用户调用AllocSetFree的时候,我们并不真正的返还给系统,而是挂在可用chunk列表中。等待下一次的分配。
OK,我们已经透露了一些分配和释放的原则,那么,我们就看下分配和释放部分的代码吧。
在PostgreSQL中,内存的分配,重分配,释放都是在内存上下文中进行的,不再直接调用系统提供的malloc/realloc/free。PostgreSQL提供了一个系列的函数,来管理内存
/*
* This is the virtual function table for AllocSet
contexts.
*/
static MemoryContextMethods AllocSetMethods = {
AllocSetAlloc,
AllocSetFree,
AllocSetRealloc,
AllocSetInit,
AllocSetReset,
AllocSetDelete,
AllocSetGetChunkSpace,
AllocSetIsEmpty,
AllocSetStats
#ifdef MEMORY_CONTEXT_CHECKING
,AllocSetCheck
#endif
};
下面我们重点介绍Alloc Free Realloc 这几个函数。
前面我们提到过,在AllocSetContext这个结构体中有个很重要的成员变量:allocChunkLimit,如下所示:
typedef struct AllocSetContext
{
MemoryContextData header; /* Standard memory-context
fields */
/* Info about storage allocated in this context: */
AllocBlock blocks; /* head of list of blocks in this set */
AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* free
chunk lists */
/* Allocation parameters for this context: */
Size initBlockSize; /* initial block size */
Size maxBlockSize; /* maximum block size */
Size nextBlockSize; /* next block
size to allocate */
Size allocChunkLimit; /* effective chunk size limit */
AllocBlock keeper; /* if not NULL, keep
this block over resets */
} AllocSetContext;
typedef AllocSetContext *AllocSet;
这个allocChunkLimit的是内容上下文中一个很重要的参数,这个参数的含义上面也曾提及到,含义是大小chunk的门限值。
如果PostgreSQL需要在内存上下文分配大于allocChunkLimit的内存区域,那么内存上下文认为这是分配较大的内存,采用malloc的方法,同时将分配出来的block链入内存上下文的block链表中。如果用户释放该内存区域(实际上是chunk),那么内存上下文会真正的free,返还给操作系统。
如果PostgreSQL需要在内存上下文分配小于allocChunkLimit的内存区域,那么行为是不同,往根本上将,这些小块内存当用户选择释放的时候,并不真正的调用free,而是将小块内存作为free chunk,根据大小链接在freelist。freelist的概念和伙伴内存系统有些类似,有11条链表,每条链表的chunk大小是不同的。分别是8/16/32/64/128/256/512/1024/2048/4096/8192。当进程调用AllocSetFree去释放这些小块内存的时候,内存上下文会将这些内存块放到freelist对应的链表中,以待下一次分配。
这么做的好处是防止小块内存的不停malloc/free造成大量的碎片产生。
这么看起来allocChunkLimit这个值很重要,那么这个值是怎么算出来的呢。首先需要说allocChunkLimit,不同的内存上下文,其大小可能是不同的。它的值大小是在AllocSetContextCreate函数里面计算出来的。
#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) */
context->allocChunkLimit = ALLOC_CHUNK_LIMIT;
while ((Size) (context->allocChunkLimit + ALLOC_CHUNKHDRSZ) >
(Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) /ALLOC_CHUNK_FRACTION))
context->allocChunkLimit >>= 1;
ALLOC_CHUNK_LIMIT的值为8K,也就说内存上下文的allocChunkLimit最大就是8K,但是实际的context->allocChunkLimit,还需要根据maxBlockSize来计算。下面这个while的含义是一个最大的block的应该不小于4倍的allocChunkLimit。以TopMemoryContext为例,maxBlockSize
= 8K,那么allocChunkLimit应该是小于2K,所以最终计算的结果是allocChunkLimit = 1K。 TopMemoryContextu作为根内存上下文,从这里分配的内存多是用来存储子内存上下文,而子内存上下文对应的数据结构非常的小,不会超过1K,所以allocChunkLimit=1K 是合理的。而PostmasterContext的 maxBlockSize
= 8M,所以PostmasterContext的allocChunkLimit=8K。
讲完了allocChunkLimit这个参数,nextBlockSize也很重要。block和chunk是这个内存上下文的比较重要的概念。这个概念简单理解就是大公司管理网线(因为内存有申请和释放,网线不用之后,还可以归还回去)。操作系统是个全公司总仓库,它的有点是货源充足(仓库里有大量的内存空间可用),缺点是提货不方便,你可以想想,几万人要1米
2 米的网线都要去千里之外的全公司总仓库,我们有多烦,不光我们烦躁,网线管理员也很烦躁,因为短则1米,长则上千米网线频繁的切割,会造成仓库的混乱。对应操作系统来讲,就是小块内存的频繁申请和释放,会造成内存碎片,仓库空间虽大,但是横七竖八的小网线弄得在也分配不了长网线了。 那么怎么办呢。很简单,成立分仓库。分仓库就是内存上下文。分仓库负责申请一段很长的网线,然后给公司员工用。员工用完了网线,再还给分仓库,就不用归还到全公司总仓库了,直接归还分仓库,分仓库会按照网线长短放在11个地方,存放网线,下次员工来取了,直接向对应的房间(对应的freelist)去取。有时候员工可能会取比较长的网线,比如这个员工要10000米的网线,分仓库去总仓库去取(malloc),然后员工用,员工归还的时候(AllocSetFree),分仓库真的将这10000米网线归还给总仓库(free)。
block就是分仓库批发过来的很长的网线,既然是批发,就要有规则,不可能今天去总仓库取1米,明天去取3米,公司总仓库烦都烦死了。maxBlockSize是分仓库一次最多取的长度,nextBlockSize记录的是下一次我应该去总仓库取多少米。以Postmastercontext为例,刚初始化的时候,nextBlockSize=8K,maxBlockSize=8M。这个分仓库刚开始的时候,他取的是8K,因为员工用完了还会归还,所以,一旦发生货源不足的话,下一次进货,应该是nextBlockSize×2。
请看分仓库去总仓库申请长网线的代码:
if (block == NULL)
{
Size required_size;
/*
* The first such block has size initBlockSize, and we double the
* space in each succeeding
block, but not more than maxBlockSize.
*/
blksize = set->nextBlockSize;
set->nextBlockSize <<= 1;
//下一次去总仓库取网线,要多取1倍
if (set->nextBlockSize > set->maxBlockSize)
set->nextBlockSize = set->maxBlockSize;//取网线最多不能超过maxBlockSize
/*
* If initBlockSize is less than ALLOC_CHUNK_LIMIT, we
could need more
* space... but
try to keep it a power of 2.
*/
required_size = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
while (blksize < required_size)
blksize <<= 1;
//如果当前要网线的员工要的太多,超过了本次应该的取的长度,则double
block = (AllocBlock) malloc(blksize);
.....
block->aset = set;
block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
block->endptr = ((char *) block) + blksize;
....
}
有了很长的网线,就能满足当前员工的需求了。但是去总仓库申请来的很长的网线是不是立刻就截断成1米
2米 4米 8米这种长度呢?答案是否定的。
我们来看下员工申请网线的情况。员工申请14米的网线1根,那么仓库管理员首先干的事情是看下有没有16米的网线。对应freelist的某个chunk。如果有的话,皆大欢喜,员工拿了网线走人。对应的代码如下:
fidx = AllocSetFreeIndex(size);
chunk = set->freelist[fidx];
if (chunk != NULL)
{
Assert(chunk->size >= size);
set->freelist[fidx] = (AllocChunk) chunk->aset;//长度16的网线,是链接在一起的。
chunk->aset = (void *) set;
#ifdef MEMORY_CONTEXT_CHECKING
chunk->requested_size = size;
/* set mark to catch
clobber of "unused" space */
if (size < chunk->size)
((char *) AllocChunkGetPointer(chunk))[size] = 0x7E;
#endif
#ifdef RANDOMIZE_ALLOCATED_MEMORY
/* fill the allocated space with junk */
randomize_mem((char *) AllocChunkGetPointer(chunk), size);
#endif
AllocAllocInfo(set, chunk);
return AllocChunkGetPointer(chunk);
}
很不幸,没有16米的网线,则去看下上次从总仓库那回来的网线还有多长。
1 剩余的网线超过16米 ,则可以在这个剩余的网线上面截取。
2 如果不够长的话,比如从总仓库带回的网线已经只剩下13米了,此时分仓库管理员会将13米的网线截成1米 4米 8米,放入Freelist中,共员工来申请使用。同时去总仓库再次申请,当然不是申请16米,好不容易去一次总仓库,不可能只申请16米,而是申请nextBlocksize,如前所述。
剩余网线长度不够长,被分仓库管理员截断成规整的长度代码如下:
if ((block = set->blocks) != NULL)
{
Size availspace = block->endptr - block->freeptr;
if (availspace < (chunk_size + ALLOC_CHUNKHDRSZ))//剩余长度不够长
{
/*
* The existing active (top) block does not have
enough room for
* the requested allocation, but it might still have a useful
* amount of space in it. Once
we push it down in the block list,
* we'll never try to allocate more space from
it. So, before we
* do that, carve up its free space into
chunks that we can put on
* the set's freelists.
*
* Because we can only get here when there's less than
* ALLOC_CHUNK_LIMIT left in the block, this loop cannot
iterate
* more than ALLOCSET_NUM_FREELISTS-1 times.
*/
while (availspace >= ((1 << ALLOC_MINBITS) + ALLOC_CHUNKHDRSZ))
{
Size availchunk = availspace - ALLOC_CHUNKHDRSZ;
int a_fidx = AllocSetFreeIndex(availchunk);
/*
* In most cases, we'll get back
the index of the next larger
* freelist than the one we need to put this chunk on. The
* exception is when availchunk is exactly a power of 2.
*/
if (availchunk != ((Size) 1 << (a_fidx + ALLOC_MINBITS)))
{
a_fidx--;
Assert(a_fidx >= 0);
availchunk = ((Size) 1 << (a_fidx + ALLOC_MINBITS));
}
chunk = (AllocChunk) (block->freeptr);
block->freeptr += (availchunk + ALLOC_CHUNKHDRSZ);
availspace -= (availchunk + ALLOC_CHUNKHDRSZ);
chunk->size = availchunk;
#ifdef MEMORY_CONTEXT_CHECKING
chunk->requested_size = 0; /* mark
it free */
#endif
chunk->aset = (void *) set->freelist[a_fidx];
set->freelist[a_fidx] = chunk;
}
/* Mark that we need to create a new block */
block = NULL; //block = NULL,需要分仓库去总仓库申请一卷长网线回来。
}
}
freelist上面可供分配的chunk是从哪里来的呢?上面的代码是一个途径即剩余长度不能满足员工本次需求的时候,分仓库管理员会将剩余的网线截断成8米4米这种和freelist匹配的长度,放入对应的freelist中。另外一个途径是员工归还,即AllocSetFree.
AllocSetFree和AllocSetAlloc一样,也是分情况的。如果超过allocChunkLimit,表明员工要归还长网线,那么分仓库会将长网线亲自归还到总仓库(free)。如果员工归还的网线是16米的网线1根,直接放到16米对应的freelist中去。
else
{
/* Normal case, put the chunk into appropriate freelist */
int fidx = AllocSetFreeIndex(chunk->size);
chunk->aset = (void *) set->freelist[fidx];
#ifdef CLOBBER_FREED_MEMORY
/* Wipe freed memory for debugging purposes */
memset(pointer, 0x7F, chunk->size);
#endif
#ifdef MEMORY_CONTEXT_CHECKING
/* Reset requested_size to 0 in chunks that are on freelist */
chunk->requested_size = 0;
#endif
set->freelist[fidx] = chunk;
}
AllocSetRealloc部分的代码也很好理解,只要用这个网线申请理论去理解,这个内存上下文其实是比较简单的。
参考文献:
1 PostgreSQL数据库内核分析
前言
PostgreSQL是我们项目采用的数据库,从来没读过数据库的代码,尽管也曾写过一些应用层的代码,希望今年能粗读一遍PostgreSQL的源码,提高自己对数据库的理解。本系列以PostgreSQL的最新版本9.2.3源码为例,学习PostgreSQL。
PostgreSQL从7.1开始,引入了内存上下文(MemoryContent)机制,片汤话我不多说,简单的理解,内存上下文提供了一种管理内存的机制。我们通过图表和代码分析来理解PostgreSQL的内存上下文。
内存上下文的主要数据结构都在上图中了,主要是4个基本数据结构之间的关系分别是
MemoryContextData
AllocSetContext
AllocBlockData
AllocChunkData
这四个数据结构中核心的数据结构是AllocSetContext,从上图中也可以清楚的看出来。下面我们详细讲述之
1 内存上下文的创建
任何一个PostgreSQL进程使用内存上下文之前,都必须首先进行初始化,这句话是一句废话,呵呵。内存上下文的初始化是PostMasterMain函数里面开头调用的MemoryContextInit完成的。这个函数干了两件事情:创建了内存上下文的根 TopMemoryContext
创建了TopMemoryContext的第一个子节点ErrorContext。
创建内存上下文的工作是由AllocSetContextCreate函数完成的。这个很有意思。MemoryContext明明是上面提到的第一种数据结构,他的创建函数偏偏是AllocSetContextCreate,这个函数顾名思义也知道是创建第二个数据结构的。这其实很好理解,看上面绘制的图片可以看出,MemoryContextData不过是核心数据结构AllocSetContext的第一个成员变量(更严格的说是它的一个指针类型的成员变量指向这个MemoryContextData)。
AllocSetContextCreate这个函数其实是分成两部分的
MemoryContextCreate ,创建MemoryContext
创建AllocSetContxt剩余的部分,主要是确定initBlockSize,nextBlockSize,maxBlockSize和allocChunkLimit的大小。
对于MemoryContextCreate这个函数,TopMemoryContext是没有parent的,所以他的parent指针是NULL;另外一个需注意的点是method,在TopMemoryContext创建methods指针指向了一个结构体,这个结构体内是一系列分配释放相关的函数,都画在了上图的右上角。因为TopMemeoryContext是根,所以他的分配 需要用malloc,其他的MemoryContext创建的时候,就不需要调用系统函数malloc了,直接用method函数指针系列里面AllocSetAlloc函数分配就行了。见如下代码
if (TopMemoryContext != NULL)
{
/* Normal case: allocate
the node in TopMemoryContext */
node = (MemoryContext) MemoryContextAlloc(TopMemoryContext,
needed);
}
else
{
/* Special case for startup: use
good ol' malloc */
node = (MemoryContext) malloc(needed);
Assert(node != NULL);
}
确定initBlockSize,nextBlockSize等的代码比较简单,我就不赘述了。allocChunkLimit这个参数的含义是在这个内存上下文中,大内存块的门限值。比如如果maxBlockSize=8K,那么系统认为1KB是比较大的内存块,低于1KB的这个门限值的,都认为是小块内存。在分配策略和释放策略上,是不同的。我们认为大内存的分配不频繁,所以我们采用直接malloc的方法,如果释放的话,就真的调用free,将内存返还给系统。但是小内存块则不同,我们认为小内存块的分配是频繁的,而且频繁的malloc/free会造成内存碎片,所以当用户调用AllocSetFree的时候,我们并不真正的返还给系统,而是挂在可用chunk列表中。等待下一次的分配。
OK,我们已经透露了一些分配和释放的原则,那么,我们就看下分配和释放部分的代码吧。
2 内存上下文中的内存操作
在PostgreSQL中,内存的分配,重分配,释放都是在内存上下文中进行的,不再直接调用系统提供的malloc/realloc/free。PostgreSQL提供了一个系列的函数,来管理内存/*
* This is the virtual function table for AllocSet
contexts.
*/
static MemoryContextMethods AllocSetMethods = {
AllocSetAlloc,
AllocSetFree,
AllocSetRealloc,
AllocSetInit,
AllocSetReset,
AllocSetDelete,
AllocSetGetChunkSpace,
AllocSetIsEmpty,
AllocSetStats
#ifdef MEMORY_CONTEXT_CHECKING
,AllocSetCheck
#endif
};
下面我们重点介绍Alloc Free Realloc 这几个函数。
前面我们提到过,在AllocSetContext这个结构体中有个很重要的成员变量:allocChunkLimit,如下所示:
typedef struct AllocSetContext
{
MemoryContextData header; /* Standard memory-context
fields */
/* Info about storage allocated in this context: */
AllocBlock blocks; /* head of list of blocks in this set */
AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* free
chunk lists */
/* Allocation parameters for this context: */
Size initBlockSize; /* initial block size */
Size maxBlockSize; /* maximum block size */
Size nextBlockSize; /* next block
size to allocate */
Size allocChunkLimit; /* effective chunk size limit */
AllocBlock keeper; /* if not NULL, keep
this block over resets */
} AllocSetContext;
typedef AllocSetContext *AllocSet;
这个allocChunkLimit的是内容上下文中一个很重要的参数,这个参数的含义上面也曾提及到,含义是大小chunk的门限值。
如果PostgreSQL需要在内存上下文分配大于allocChunkLimit的内存区域,那么内存上下文认为这是分配较大的内存,采用malloc的方法,同时将分配出来的block链入内存上下文的block链表中。如果用户释放该内存区域(实际上是chunk),那么内存上下文会真正的free,返还给操作系统。
如果PostgreSQL需要在内存上下文分配小于allocChunkLimit的内存区域,那么行为是不同,往根本上将,这些小块内存当用户选择释放的时候,并不真正的调用free,而是将小块内存作为free chunk,根据大小链接在freelist。freelist的概念和伙伴内存系统有些类似,有11条链表,每条链表的chunk大小是不同的。分别是8/16/32/64/128/256/512/1024/2048/4096/8192。当进程调用AllocSetFree去释放这些小块内存的时候,内存上下文会将这些内存块放到freelist对应的链表中,以待下一次分配。
这么做的好处是防止小块内存的不停malloc/free造成大量的碎片产生。
这么看起来allocChunkLimit这个值很重要,那么这个值是怎么算出来的呢。首先需要说allocChunkLimit,不同的内存上下文,其大小可能是不同的。它的值大小是在AllocSetContextCreate函数里面计算出来的。
#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) */
context->allocChunkLimit = ALLOC_CHUNK_LIMIT;
while ((Size) (context->allocChunkLimit + ALLOC_CHUNKHDRSZ) >
(Size) ((maxBlockSize - ALLOC_BLOCKHDRSZ) /ALLOC_CHUNK_FRACTION))
context->allocChunkLimit >>= 1;
ALLOC_CHUNK_LIMIT的值为8K,也就说内存上下文的allocChunkLimit最大就是8K,但是实际的context->allocChunkLimit,还需要根据maxBlockSize来计算。下面这个while的含义是一个最大的block的应该不小于4倍的allocChunkLimit。以TopMemoryContext为例,maxBlockSize
= 8K,那么allocChunkLimit应该是小于2K,所以最终计算的结果是allocChunkLimit = 1K。 TopMemoryContextu作为根内存上下文,从这里分配的内存多是用来存储子内存上下文,而子内存上下文对应的数据结构非常的小,不会超过1K,所以allocChunkLimit=1K 是合理的。而PostmasterContext的 maxBlockSize
= 8M,所以PostmasterContext的allocChunkLimit=8K。
讲完了allocChunkLimit这个参数,nextBlockSize也很重要。block和chunk是这个内存上下文的比较重要的概念。这个概念简单理解就是大公司管理网线(因为内存有申请和释放,网线不用之后,还可以归还回去)。操作系统是个全公司总仓库,它的有点是货源充足(仓库里有大量的内存空间可用),缺点是提货不方便,你可以想想,几万人要1米
2 米的网线都要去千里之外的全公司总仓库,我们有多烦,不光我们烦躁,网线管理员也很烦躁,因为短则1米,长则上千米网线频繁的切割,会造成仓库的混乱。对应操作系统来讲,就是小块内存的频繁申请和释放,会造成内存碎片,仓库空间虽大,但是横七竖八的小网线弄得在也分配不了长网线了。 那么怎么办呢。很简单,成立分仓库。分仓库就是内存上下文。分仓库负责申请一段很长的网线,然后给公司员工用。员工用完了网线,再还给分仓库,就不用归还到全公司总仓库了,直接归还分仓库,分仓库会按照网线长短放在11个地方,存放网线,下次员工来取了,直接向对应的房间(对应的freelist)去取。有时候员工可能会取比较长的网线,比如这个员工要10000米的网线,分仓库去总仓库去取(malloc),然后员工用,员工归还的时候(AllocSetFree),分仓库真的将这10000米网线归还给总仓库(free)。
block就是分仓库批发过来的很长的网线,既然是批发,就要有规则,不可能今天去总仓库取1米,明天去取3米,公司总仓库烦都烦死了。maxBlockSize是分仓库一次最多取的长度,nextBlockSize记录的是下一次我应该去总仓库取多少米。以Postmastercontext为例,刚初始化的时候,nextBlockSize=8K,maxBlockSize=8M。这个分仓库刚开始的时候,他取的是8K,因为员工用完了还会归还,所以,一旦发生货源不足的话,下一次进货,应该是nextBlockSize×2。
请看分仓库去总仓库申请长网线的代码:
if (block == NULL)
{
Size required_size;
/*
* The first such block has size initBlockSize, and we double the
* space in each succeeding
block, but not more than maxBlockSize.
*/
blksize = set->nextBlockSize;
set->nextBlockSize <<= 1;
//下一次去总仓库取网线,要多取1倍
if (set->nextBlockSize > set->maxBlockSize)
set->nextBlockSize = set->maxBlockSize;//取网线最多不能超过maxBlockSize
/*
* If initBlockSize is less than ALLOC_CHUNK_LIMIT, we
could need more
* space... but
try to keep it a power of 2.
*/
required_size = chunk_size + ALLOC_BLOCKHDRSZ + ALLOC_CHUNKHDRSZ;
while (blksize < required_size)
blksize <<= 1;
//如果当前要网线的员工要的太多,超过了本次应该的取的长度,则double
block = (AllocBlock) malloc(blksize);
.....
block->aset = set;
block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ;
block->endptr = ((char *) block) + blksize;
....
}
有了很长的网线,就能满足当前员工的需求了。但是去总仓库申请来的很长的网线是不是立刻就截断成1米
2米 4米 8米这种长度呢?答案是否定的。
我们来看下员工申请网线的情况。员工申请14米的网线1根,那么仓库管理员首先干的事情是看下有没有16米的网线。对应freelist的某个chunk。如果有的话,皆大欢喜,员工拿了网线走人。对应的代码如下:
fidx = AllocSetFreeIndex(size);
chunk = set->freelist[fidx];
if (chunk != NULL)
{
Assert(chunk->size >= size);
set->freelist[fidx] = (AllocChunk) chunk->aset;//长度16的网线,是链接在一起的。
chunk->aset = (void *) set;
#ifdef MEMORY_CONTEXT_CHECKING
chunk->requested_size = size;
/* set mark to catch
clobber of "unused" space */
if (size < chunk->size)
((char *) AllocChunkGetPointer(chunk))[size] = 0x7E;
#endif
#ifdef RANDOMIZE_ALLOCATED_MEMORY
/* fill the allocated space with junk */
randomize_mem((char *) AllocChunkGetPointer(chunk), size);
#endif
AllocAllocInfo(set, chunk);
return AllocChunkGetPointer(chunk);
}
很不幸,没有16米的网线,则去看下上次从总仓库那回来的网线还有多长。
1 剩余的网线超过16米 ,则可以在这个剩余的网线上面截取。
2 如果不够长的话,比如从总仓库带回的网线已经只剩下13米了,此时分仓库管理员会将13米的网线截成1米 4米 8米,放入Freelist中,共员工来申请使用。同时去总仓库再次申请,当然不是申请16米,好不容易去一次总仓库,不可能只申请16米,而是申请nextBlocksize,如前所述。
剩余网线长度不够长,被分仓库管理员截断成规整的长度代码如下:
if ((block = set->blocks) != NULL)
{
Size availspace = block->endptr - block->freeptr;
if (availspace < (chunk_size + ALLOC_CHUNKHDRSZ))//剩余长度不够长
{
/*
* The existing active (top) block does not have
enough room for
* the requested allocation, but it might still have a useful
* amount of space in it. Once
we push it down in the block list,
* we'll never try to allocate more space from
it. So, before we
* do that, carve up its free space into
chunks that we can put on
* the set's freelists.
*
* Because we can only get here when there's less than
* ALLOC_CHUNK_LIMIT left in the block, this loop cannot
iterate
* more than ALLOCSET_NUM_FREELISTS-1 times.
*/
while (availspace >= ((1 << ALLOC_MINBITS) + ALLOC_CHUNKHDRSZ))
{
Size availchunk = availspace - ALLOC_CHUNKHDRSZ;
int a_fidx = AllocSetFreeIndex(availchunk);
/*
* In most cases, we'll get back
the index of the next larger
* freelist than the one we need to put this chunk on. The
* exception is when availchunk is exactly a power of 2.
*/
if (availchunk != ((Size) 1 << (a_fidx + ALLOC_MINBITS)))
{
a_fidx--;
Assert(a_fidx >= 0);
availchunk = ((Size) 1 << (a_fidx + ALLOC_MINBITS));
}
chunk = (AllocChunk) (block->freeptr);
block->freeptr += (availchunk + ALLOC_CHUNKHDRSZ);
availspace -= (availchunk + ALLOC_CHUNKHDRSZ);
chunk->size = availchunk;
#ifdef MEMORY_CONTEXT_CHECKING
chunk->requested_size = 0; /* mark
it free */
#endif
chunk->aset = (void *) set->freelist[a_fidx];
set->freelist[a_fidx] = chunk;
}
/* Mark that we need to create a new block */
block = NULL; //block = NULL,需要分仓库去总仓库申请一卷长网线回来。
}
}
freelist上面可供分配的chunk是从哪里来的呢?上面的代码是一个途径即剩余长度不能满足员工本次需求的时候,分仓库管理员会将剩余的网线截断成8米4米这种和freelist匹配的长度,放入对应的freelist中。另外一个途径是员工归还,即AllocSetFree.
AllocSetFree和AllocSetAlloc一样,也是分情况的。如果超过allocChunkLimit,表明员工要归还长网线,那么分仓库会将长网线亲自归还到总仓库(free)。如果员工归还的网线是16米的网线1根,直接放到16米对应的freelist中去。
else
{
/* Normal case, put the chunk into appropriate freelist */
int fidx = AllocSetFreeIndex(chunk->size);
chunk->aset = (void *) set->freelist[fidx];
#ifdef CLOBBER_FREED_MEMORY
/* Wipe freed memory for debugging purposes */
memset(pointer, 0x7F, chunk->size);
#endif
#ifdef MEMORY_CONTEXT_CHECKING
/* Reset requested_size to 0 in chunks that are on freelist */
chunk->requested_size = 0;
#endif
set->freelist[fidx] = chunk;
}
AllocSetRealloc部分的代码也很好理解,只要用这个网线申请理论去理解,这个内存上下文其实是比较简单的。
参考文献:
1 PostgreSQL数据库内核分析
相关文章推荐
- 转载:PostgreSQL源码分析之page
- Postgresql内存上下文分析
- Postgresql存储引擎源码分析六(转载,不断更新)
- [转载]cocos2d-x3.2源码分析(一)类FileUtils--实现把资源放在Resources文件目录下达到多平台的引用
- apache kafka源码分析-Producer分析---转载
- [postgreSQL,c++] PostgreSQL源码分析_StringInfo_primary
- Android 匿名共享内存驱动源码分析
- linux内存源码分析 - 内存回收(lru链表)
- Memcached源码分析之内存管理篇
- java虚拟机类加载过程内存情况底层源码分析及ClassLoader讲解
- Spark BlockManager的通信及内存占用分析(源码阅读九)
- 转载_LKM backdoor研究linux系列--insmod源码分析篇
- Linux内存源码分析-内存池
- PostgreSQL存储引擎源码分析五(原创,不断更新)
- Unity3D–Texture图片空间和内存占用分析(转载)
- 跟我一起读postgresql源码(二)——Parser(查询分析模块)
- Fresco源码分析-SimpleDraweeView在ListView里怎么释放内存的?
- JQuery 1.10.2源码分析学习4(转载)
- (转载)Java中HashMap底层实现原理(JDK1.8)源码分析
- Linux kernel 3.10内核源码分析--进程上下文切换