转载:PostgreSQL源码分析之内存上下文
转载:http://blog.chinaunix.net/uid-24774106-id-3547274.html 前言PostgreSQL是我们项目采用的数据库,从来没读过数据库的代码,尽管也曾写过一些应用层的代码,希望今年能粗读一遍PostgreSQL的源码,提高自己对数据库的理解。 本系列以PostgreSQL的最新版本9.2.3源码为例,学习PostgreSQL。 PostgreSQL从7.1开始,引入了内存上下文(MemoryContent)机制,片汤话我不多说,简单的理解,内存上下文提供了一种管理内存的机制。我们通过图表和代码分析来理解PostgreSQL的内存上下文。
内存上下文的主要数据结构都在上图中了,主要是4个基本数据结构之间的关系分别是
这四个数据结构中核心的数据结构是AllocSetContext,从上图中也可以清楚的看出来。下面我们详细讲述之 1 内存上下文的创建 任何一个PostgreSQL进程使用内存上下文之前,都必须首先进行初始化,这句话是一句废话,呵呵。内存上下文的初始化是PostMasterMain函数里面开头调用的MemoryContextInit完成的。这个函数干了两件事情:
创建内存上下文的工作是由AllocSetContextCreate函数完成的。这个很有意思。MemoryContext明明是上面提到的第一种数据结构,他的创建函数偏偏是AllocSetContextCreate,这个函数顾名思义也知道是创建第二个数据结构的。这其实很好理解,看上面绘制的图片可以看出,MemoryContextData不过是核心数据结构AllocSetContext的第一个成员变量(更严格的说是它的一个指针类型的成员变量指向这个MemoryContextData)。 AllocSetContextCreate这个函数其实是分成两部分的
对于MemoryContextCreate这个函数,TopMemoryContext是没有parent的,所以他的parent指针是NULL;另外一个需注意的点是method,在TopMemoryContext创建methods指针指向了一个结构体,这个结构体内是一系列分配释放相关的函数,都画在了上图的右上角。因为TopMemeoryContext是根,所以他的分配 需要用malloc,其他的MemoryContext创建的时候,就不需要调用系统函数malloc了,直接用method函数指针系列里面AllocSetAlloc函数分配就行了。见如下代码
确定initBlockSize,nextBlockSize等的代码比较简单,我就不赘述了。allocChunkLimit这个参数的含义是在这个内存上下文中,大内存块的门限值。比如如果maxBlockSize=8K,那么系统认为1KB是比较大的内存块,低于1KB的这个门限值的,都认为是小块内存。在分配策略和释放策略上,是不同的。我们认为大内存的分配不频繁,所以我们采用直接malloc的方法,如果释放的话,就真的调用free,将内存返还给系统。但是小内存块则不同,我们认为小内存块的分配是频繁的,而且频繁的malloc/free会造成内存碎片,所以当用户调用AllocSetFree的时候,我们并不真正的返还给系统,而是挂在可用chunk列表中。等待下一次的分配。 OK,我们已经透露了一些分配和释放的原则,那么,我们就看下分配和释放部分的代码吧。 2 内存上下文中的内存操作 在PostgreSQL中,内存的分配,重分配,释放都是在内存上下文中进行的,不再直接调用系统提供的malloc/realloc/free。PostgreSQL提供了一个系列的函数,来管理内存 *
下面我们重点介绍Alloc Free Realloc 这几个函数。 前面我们提到过,在AllocSetContext这个结构体中有个很重要的成员变量:allocChunkLimit,如下所示: typedef struct AllocSetContext
这个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 sizeis8 bytes/
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),然后员工用,员工归还的时候( ),分仓库 真的将这10000米网线归还给总仓库(free )。 block就是分仓库批发过来的很长的网线,既然是批发,就要有规则,不可能今天去总仓库取1米,明天去取3米,公司总仓库烦都烦死了。maxBlockSize是分仓库一次最多取的长度,nextBlockSize记录的是下一次我应该去总仓库取多少米。以Postmastercontext为例,刚初始化的时候,nextBlockSize=8K,maxBlockSize=8M。这个分仓库刚开始的时候,他取的是8K,因为员工用完了还会归还,所以,一旦发生货源不足的话,下一次进货,应该是nextBlockSize×2。 请看分仓库去总仓库申请长网线 的代码: (block{ block->freeptr = ((char *) block) + ALLOC_BLOCKHDRSZ; block->endptr = ((char *) block) + blksize; } 有了很长的网线,就能满足当前员工的需求了。但是去总仓库申请来的 很长的网线是不是立刻就截断成1米 2米 4米 8米这种长度呢?答案是否定的。我们来看下员工申请网线的情况。员工申请14米的网线1根,那么仓库管理员首先干的事情是看下有没有16米的网线。对应freelist的某个chunk。如果有的话,皆大欢喜,员工拿了网线走人。对应的代码如下: fidx=AllocSetFreeIndex(size;
很不幸,没有16米的网线,则去看下上次从总仓库那回来的网线还有多长。
1 剩余的网线超过16米 ,则可以在这个剩余的网线上面截取。 2 如果不够长的话,比如从总仓库带回的网线已经只剩下13米了,此时分仓库管理员会将13米的网线截成1米 4米 8米,放入Freelist中,共员工来申请使用。同时去总仓库再次申请,当然不是申请16米,好不容易去一次总仓库,不可能只申请16米 ,而是申请nextBlocksize,如前所述。 剩余网线长度不够长,被分仓库管理员截断成规整的长度代码如下: >blocks{ AllocSetFree和AllocSetAlloc一样,也是分情况的。如果超过allocChunkLimit,表明员工要归还长网线,那么分仓库会将长网线亲自归还到总仓库(free)。如果员工归还的网线是16米的网线1根,直接放到16米对应的freelist中去。 else 参考文献: 1 PostgreSQL数据库内核分析 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |