PostgreSQL启动过程中的那些事一:初始化TopMemoryContext和ErrorContext

1先上个示意图,看一下函数调用过程梗概,中间略过部分细节

 

前面标1的是初始化TopMemoryContext

前面标2的是初始化ErrorContext

初始化TopMemoryContext和ErrorContext的方法调用过程图

 

2初始化TopMemoryContext的过程

话说main()->…->PostmasterMain()->…->MemoryContextInit()->AllocSetContextCreate()(以后用“->”表示调用),AllocSetContextCreate()函数主要是初始化AllocSet类型的变量,AllocSet的类型是“AllocSetContext*”,AllocSetContext是结构,定义见下面,也就是说,AllocSetContextCreate()函数主要是初始化AllocSetContext类型的变量。

在MemoryContextInit()函数中,一上来就是下面这句,调用AllocSetContextCreate初始化TopMemoryContext。

 

     TopMemoryContext =AllocSetContextCreate((MemoryContext) NULL,

                                      "TopMemoryContext",0,8 * 1024, 8 * 1024);

TopMemoryContext是个全局变量,定义如下

MemoryContext TopMemoryContext = NULL;

从MemoryContextInit()函数调用过来初始化Memorycontext类型的全局变量TopMemoryContext,不是AllocSetContextCreate()函数主要是初始化AllocSetContext类型的变量吗,怎么又初始化上Memorycontext类型的全局变量TopMemoryContext了呢,先卖个关子,容后再说。这是PostgresSQL(以后简称pg)一个比较秒的地方,也涉及了后面用到的面向过程编程的一个技巧。

TopMemoryContext这个全局变量在pg有至关重要,统领全局的地位,以后自然明白。

 

AllocSetContext定义如下:

typedef struct AllocSetContext

{

         MemoryContextData header;  /* Standardmemory-context fields */

         /* Info aboutstorage allocated in this context: */

         AllocBlock blocks;                         /* head of list of blocks in this set */

         AllocChunk        freelist[ALLOCSET_NUM_FREELISTS];             /* free chunk lists */

         bool           isReset;               /* T = no space allocedsince last reset */

         /* Allocationparameters for this context: */

         Size            initBlockSize;      /* initial blocksize */

         Size            maxBlockSize;   /* maximum blocksize */

         Size            nextBlockSize;   /* next block sizeto allocate */

         AllocBlock keeper;                        /* if not NULL, keep this block over resets */

}AllocSetContext;

 

AllocSetContext中有三个变量的类型分别是MemoryContextData、AllocBlock、AllocChunk,这些类型是pg管理AllocSet和MemoryContext涉及内存机制的主要元素,搭起pg管理AllocSet和MemoryContext涉及的内存的架构,后面会逐个提到。

 

在AllocSetContextCreate()函数中,刚声明一个AllocSet类型的变量conext,马上就调用MemoryContextCreate()函数,代码见下。MemoryContextCreate()函数从名字就能看出来是创建MemoryContext的,创建一个MemoryContext类型的值后返回,返回后做了类型强制转换为AllocSet赋给context。调用MemoryContextCreate()函数创建MemoryContext时传的第二个参数是要创建类型的大小,这里取的就是AllocSetContext的大小,而不是MemoryContext的大小。

     AllocSet context;

     context = (AllocSet) MemoryContextCreate(T_AllocSetContext,

                        sizeof(AllocSetContext), &AllocSetMethods,parent, name);

 

MemoryContextCreate()函数主要是初始化MemoryContext,MemoryContext的类型是“MemoryContextData *”,MemoryContextData是个结构,定义见下面。

typedef struct MemoryContextData

{

         NodeTag           type;                            /* identifies exact kind of context */

         MemoryContextMethods *methods;                   /*virtual function table */

         MemoryContext parent;            /* NULL ifno parent (toplevel context) */

         MemoryContext firstchild; /* head of linkedlist of children */

         MemoryContext nextchild;        /* next childof same parent */

         char    *name;                            /* context name (just for debugging) */

}MemoryContextData;

MemoryContextCreate()函数的简化代码如下,主要是声明一个MemoryContext类型的局部变量node,分配内存并初始化。注意,这时还没有初始化TopMemoryContext,所以node的内存空间是malloc出来的。MemoryContext类型的局部变量node初始化完后返回。

 

MemoryContext

MemoryContextCreate(NodeTag tag, Size size,

                       MemoryContextMethods*methods,

                       MemoryContextparent,

                       const char *name)

{

     MemoryContext node;

     Size     needed = size + strlen(name) + 1;

     if(TopMemoryContext == NULL)

     {

         /*Special case for startup: use good ol' malloc */

         node = (MemoryContext)malloc(needed);

         Assert(node != NULL);

     }

 

     /*Initialize the node as best we can */

     MemSet(node, 0, size);

     node->type = tag;

     node->methods = methods;

     node->parent = NULL;        /* for themoment */

     node->firstchild = NULL;

     node->nextchild = NULL;

     node->name = ((char *) node) + size;

     strcpy(node->name, name);

     /*Return to type-specific creation routine to finish up */

     return node;

}

 

 

AllocSetContextCreate()函数得到从MemoryContextCreate()函数返回的MemoryContextCreate类型的值后转换为AllocSet类型,赋给context,把context其他属性初始化。最后又把context的类型由AllocSet转换为MemoryContext后返回给调用方法,赋值给TopMemoryContext。至此TopMemoryContext初始化完成。

代码见下面。

 

MemoryContext

AllocSetContextCreate(MemoryContext parent,

         constchar *name,

         Size minContextSize,

         Size initBlockSize,

         Size maxBlockSize)

{

    AllocSet    context;

     /* Dothe type-independent part of context creation */

     context = (AllocSet) MemoryContextCreate(T_AllocSetContext,

                                                    sizeof(AllocSetContext),

                                                    &AllocSetMethods,

                                                    parent,

                                                    name);

 

     context->initBlockSize =initBlockSize;

     context->maxBlockSize =maxBlockSize;

     context->nextBlockSize =initBlockSize;

     context->isReset = true;

     return(MemoryContext) context;

}

在初始化TomMemoryContext的过程中,先按AllocSet的类型初始化一个MemoryContext类型的变量,然后把这个变量转成AllocSet类型,初始化AllocSet类型的其它成员,再将其转成MemoryContext类型的变量后赋值给TopMemoryContext。为什么能做这两次类型转换呢?见下面分解。

下面上个图看看初始化出来的TopMemoryContext的结构图(以后所有内存结构图都是低地址在下面,高地址在上面,再后面有内存结构图时就不标低地址位了):



TopMemoryContext的内存结构图

 

TopMemoryContext转成AllocSet类型后的内存结构图

 

上图中分两块说,先看左边的大方块。其中下面深蓝色部分是MemoryContextData的内存结构,深蓝色和上面的浅颜色部分合起来是AllocSetContext的内存结构。

       初始化TopMemoryContext的时候,分配的内存大小不是按MemoryContextData结构的大小分的,是按AllocSetContext的大小分的内存,又在紧挨着上面分配了17个字节记录这个MemoryContextData /AllocSetContext 的名字“TopMemoryContext”。

       能在初始化TopMemoryContext的过程中能在类型MemoryContextData 、AllocSetContext之间相互转化,是利用了AllocSetContext结构布局上的特点,刚一开始就是个MemoryContextData,接着是其它成员,这样要是把TopMemoryContext当MemoryContextData类型处理,就是上面那个图,图中黑色部分相当于未知,不用管它,剩下的就是MemoryContextData类型,当TopMemoryContext是AllocSetContext类型时,就面的图,它就是个AllocSetContext类型的变量了。这样做的好处是在一个结构里既能管理内存空间,又能管理内存空间里的内容,而且逻辑功能划分很清晰。

哦,有人说从中看出了面向对象编程中继承的影子,嗯、啊,是有点,如果MemoryContextData是父类,AllocSetContext是MemoryContextData的子类,看起来是挺像的,从内存布局上看子类里也少了指向vitualtable 的member function /viturl member function pointer(vptr)。看来从面向对象编程到面向过程编程是技术的渐进,就像设备的升级改造。扯的有点远了,打住。

       再看上图的右边,就是上面提到的vitual table,这个是调用MemoryContextCreate()传进去的第三个参数AllocSetMethods,这是一个MemoryContextMethods类型的静态全局变量, 根据这个变量的定义(在下面)就可以看出这是一个为AllocSet Context管理内存的虚拟函数的表(vitual function table),MemoryContextMethods结构里定义了这些函数原型。这些函数实现了pg对内存的管理,整个就是pg的AllocSet和MemoryContext的内存空间管理机制。

      

静态全局变量AllocSetMethods的定义:

static MemoryContextMethods AllocSetMethods = {

     AllocSetAlloc,

     AllocSetFree,

     AllocSetRealloc,

     AllocSetInit,

     AllocSetReset,

     AllocSetDelete,

     AllocSetGetChunkSpace,

     AllocSetIsEmpty,

     AllocSetStats,

#ifdef MEMORY_CONTEXT_CHECKING

     ,AllocSetCheck

#endif

};

结构MemoryContextMethods的定义:

typedef struct MemoryContextMethods

{

     void   *(*alloc) (MemoryContext context, Size size);

     /*call this free_p in case someone #define's free() */

     void     (*free_p) (MemoryContext context, void *pointer);

     void   *(*realloc) (MemoryContext context, void*pointer, Size size);

     void     (*init) (MemoryContext context);

     void     (*reset) (MemoryContext context);

     void     (*delete)(MemoryContext context);

     Size     (*get_chunk_space) (MemoryContext context, void *pointer);

     bool     (*is_empty) (MemoryContext context);

     void     (*stats) (MemoryContext context);

#ifdef MEMORY_CONTEXT_CHECKING

     void     (*check) (MemoryContext context);

#endif

}MemoryContextMethods;

从pg中使用这些函数的情况来看,这些方法在MemoryContext中定义,在AllocSet中实现和使用,又像子类实现父类的虚拟方法,又是面向对象编程的影子。面向过程编程中的编程技巧在面向对象编程中基本上都成了编程语言的内在机制了。台湾的侯捷先生说要写一本《c语言高级编程》的书也有十来年了吧,到现在都没出,是否有这个原因呢

至此,TopMemoryContext初始化完成了,里面的其他属性就不写了。有了前面内容垫底,初始化ErrorContext就好写多了,因为它的类型和TomMemoryContext是一样的。

 

3 初始化ErrorContext的过程

先看看MemoryContextInit()的代码,这个很简洁,就是初始化TopMemoryContext和ErrorContext,完事。为了省地,把注释删了。在AllocSetContextCreate()中对TopMemoryContext和ErrorContext的处理基本上是一样的。在MemoryContextCreate()中则有些不同,判断TopMemoryContext非空后就去调用另一个函数MemoryContextAlloc(),这个函数没干什么事,直接调用上面提到的虚拟方法表中的Alloc函数,它的具体实现是AllocSetAlloc()函数,在其中分配好空间,初始化和设置AllocBlock和AllocChunk的属性,然后返回。接着在MemorycontextCreate()处理MemoryContextData结构的属性,其中把ErrorContext的parent指向TopMemoryContext,处理完MemoryContextData的属性后返回返回到AllocSetContextCreate(),设置AllocSetContext的属性,这样就完成了ErrorContext的初始化。

AllocSetAlloc()这个函数是pg中实际处理AllocSet和MemoryContext涉及的内存空间分配的函数,这个分配机制比较复杂,还涉及到了另外两个结构AllocBlock和AllocChunk,写pg的内存空间分配机制时再写吧。

       下面上初始化完以后的内存结构图

 

TopMemoryContext和ErrorContext的结构图一

 

上图中紫色的那一块是AllocBlock,中间浅紫色的那一小块是AllocChunk。

       只有两个MemoryContext图就看着有点眼花缭乱了,看来以后得画抽象图了。

       pg的内存管理主要是棵树,TopMemoryContext是树根,后面再建的MemoryContext就在根的下面。pg的内存管理实际是一个图,其主要部分是树形结构,另外子节点的partent指向了TopMemoryContext,形成了环。这样整个构成了图。这样的结构便于数据库的内存管理。

       AllocSetContext中blocks记录了分配给该Context的内存空间块AllockBlock的单向链表,freelist是空闲AllocChunk的链表数组。pg中给context分配内存时先分配AllockBlock,在AllockBlock里再分配AllocChunk,就是说AllocChunk是比AllockBlock小一级的内存空间单位。

上图中的ErrorContext所占的内存空间实际上在浅紫色的AllocChunk里,不是在外边,到写内存分配的时候再深入。上图是为了看清楚AllocBlock和AllocChunk,没有把ErrorContext放到AllocChunk里,实际见下面这个图。

http://dl.iteye.com/upload/attachment/596005/2b9c7c18-34bd-33be-80c6-67df16b94a17.bmp

 TopMemoryContext和ErrorContext的结构图二

 

TopMemoryContext和ErrorContext初始化完后的内存物理布局图,嗯,嗯,嗯,这个画着好费时费力,嗯,还好,有人画了,如下:

 

 


 

该图引用至http://blog.sciencenet.cn/home.php?mod=space&uid=419883&do=blog&id=308964


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值