PG内存上下文管理(MemoryContext)——内存上下文树

  MemoryContext是一个抽象类,是内存分配发生的逻辑上下文,作为内存上下文的实际实现的节点类型必须以与MemoryContext相同的字段开头。内存上下文管理模块(src/backend/utils/mmgr/mcxt.c)处理独立于正在操作的特定类型上下文的上下文管理操作。它通过内存上下文的MemoryContextMethods结构中的函数指针调用上下文类型特定的操作。如下定义了一些全局内存上下文指针。

 根节点为TopMemoryContext,根节点之下有多个子节点,每个子节点都用于不同的功能模块,例如CacheMemoryContext用于管理Cache、ErrorMemoryContext用于错误处理,每个子节点又有自己的子节点。

 

 

 Init操作

   MemoryContextInit函数用于建立内存上下文子系统,TopMemoryContext和ErrorContext在该函数中初始化。在通常multi-backend操作中,在postmaster启动中调用一次,并不是在所有独立backend启动过程调用。since the backends inherit an already-initialized context subsystem by virtue of being forked off the postmaster。

 1 void MemoryContextInit(void) {
 2     AssertState(TopMemoryContext == NULL);
 3 
 4     /*Initialize TopMemoryContext as an AllocSetContext with slow growth rate*/
 5     TopMemoryContext = AllocSetContextCreate((MemoryContext) NULL, "TopMemoryContext", 0, 8 * 1024, 8 * 1024);
 6 
 7     /*Not having any other place to point CurrentMemoryContext, make it point to TopMemoryContext. */
 8     CurrentMemoryContext = TopMemoryContext;
 9 
10     /*Initialize ErrorContext as an AllocSetContext with slow growth rate --- we don't really expect much to be allocated in it. More to the point, require it to contain at least 8K at all times. */
11     ErrorContext = AllocSetContextCreate(TopMemoryContext, "ErrorContext", 8 * 1024, 8 * 1024,8 * 1024);
12 }

Reset操作  

  MemoryCotextReset函数释放在内存上下文中分配的空间和其孩子内存空间,不删除对应的内存上下文。

1 void MemoryContextReset(MemoryContext context) {
2     AssertArg(MemoryContextIsValid(context));
3     /* save a function call in common case where there are no children */
4     if (context->firstchild != NULL)
5         MemoryContextResetChildren(context);
6     (*context->methods->reset) (context);
7 }

  MemoryContextResetChildren函数释放内存上下文孩子descendants分配的所有空间,不释放内存上下文本身。

1 void MemoryContextResetChildren(MemoryContext context) {
2     MemoryContext child;
3     AssertArg(MemoryContextIsValid(context));
4     for (child = context->firstchild; child != NULL; child = child->nextchild)
5         MemoryContextReset(child);
6 }

Delete操作

  MemoryContextDelete函数删除内存上下文和其孩子,释放其中分配的内存。如果有link,需要从父节点中delink子节点。第一步是调用MemoryContextDeleteChildren函数去处理孩子节点。下一步看该节点是否指向父节点,如果指向父节点,并且如果该孩子节点是第父节点第一个子节点,就将父节点的孩子指针指向下一个子孩子,如果不是第一个子节点,则需要将该子节点的前一个节点的兄弟节点指针指向该子节点的兄弟节点,也就是从内存上下文树中删除该内存上下文节点;不指向就直接删除该内存上下文。

 1 void MemoryContextDelete(MemoryContext context) {
 2     AssertArg(MemoryContextIsValid(context));
 3     /* We had better not be deleting TopMemoryContext ... */
 4     Assert(context != TopMemoryContext);
 5     /* And not CurrentMemoryContext, either */
 6     Assert(context != CurrentMemoryContext);
 7     MemoryContextDeleteChildren(context);
 8     /*delink the context from its parent before deleting it, so that if there's an error we won't have deleted/busted contexts still attached to the context tree.  Better a leak than a crash.*/
 9     if (context->parent) {
10         MemoryContext parent = context->parent;
11         if (context == parent->firstchild)
12             parent->firstchild = context->nextchild;
13         else {
14             MemoryContext child;
15             for (child = parent->firstchild; child; child = child->nextchild) {
16                 if (context == child->nextchild) {
17                     child->nextchild = context->nextchild;
18                     break;
19                 }
20             }
21         }
22     }
23     (*context->methods->delete) (context);
24     pfree(context);
25 }

  MemoryContextDeleteChildren函数删除内存上下文树中的内存

1 void MemoryContextDeleteChildren(MemoryContext context) {
2     AssertArg(MemoryContextIsValid(context));
3     /*
4      * MemoryContextDelete will delink the child from me, so just iterate as
5      * long as there is a child.
6      */
7     while (context->firstchild != NULL)
8         MemoryContextDelete(context->firstchild);
9 }

  MemoryContextResetAndDeleteChildren函数释放内存上下文中分配的内存空间,删除所有的子节点。

1 void MemoryContextResetAndDeleteChildren(MemoryContext context) {
2     AssertArg(MemoryContextIsValid(context));
3     MemoryContextDeleteChildren(context);
4     (*context->methods->reset) (context);
5 }

Chunk操作

  GetMemoryChunkSpace函数对当前已分配的chunk,返回它占用的总大小(包含所有分配的内存)

 1 Size GetMemoryChunkSpace(void *pointer) {
 2     StandardChunkHeader *header;
 3     /* Try to detect bogus pointers handed to us, poorly though we can. Presumably, a pointer that isn't MAXALIGNED isn't pointing at an allocated chunk.*/
 4     Assert(pointer != NULL);
 5     Assert(pointer == (void *) MAXALIGN(pointer));
 6 
 7     /*OK, it's probably safe to look at the chunk header.*/
 8     header = (StandardChunkHeader *)
 9         ((char *) pointer - STANDARDCHUNKHEADERSIZE);
10     AssertArg(MemoryContextIsValid(header->context));
11     return (*header->context->methods->get_chunk_space) (header->context, pointer);
12 }

  GetMemoryChunkContext函数对当前分配的chunk,返回它属于的内存上下文

 1 MemoryContext GetMemoryChunkContext(void *pointer){
 2     StandardChunkHeader *header;
 3     /*Try to detect bogus pointers handed to us, poorly though we can. Presumably, a pointer that isn't MAXALIGNED isn't pointing at an allocated chunk.*/
 4     Assert(pointer != NULL);
 5     Assert(pointer == (void *) MAXALIGN(pointer));
 6     /*OK, it's probably safe to look at the chunk header.*/
 7     header = (StandardChunkHeader *)
 8         ((char *) pointer - STANDARDCHUNKHEADERSIZE);
 9     AssertArg(MemoryContextIsValid(header->context));
10     return header->context;
11 }

empty操作

   MemoryContextIsEmpty函数判断内存上下文分配的空间是否为空。

1 bool MemoryContextIsEmpty(MemoryContext context) {
2     AssertArg(MemoryContextIsValid(context));
3     /*For now, we consider a memory context nonempty if it has any children; perhaps this should be changed later.*/
4     if (context->firstchild != NULL)
5         return false;
6     /* Otherwise use the type-specific inquiry */
7     return (*context->methods->is_empty) (context);
8 }

Stats操作

   MemoryContextStats函数返回内存上下文和子孩子的统计信息,用于调试目的,统计信息大部分发往stderr

1 void MemoryContextStats(MemoryContext context) {
2     MemoryContextStatsInternal(context, 0);
3 }

  MemoryContextStatsInternal静态函数

1 static void MemoryContextStatsInternal(MemoryContext context, int level) {
2     MemoryContext child;
3     AssertArg(MemoryContextIsValid(context));
4     (*context->methods->stats) (context, level);
5     for (child = context->firstchild; child != NULL; child = child->nextchild)
6         MemoryContextStatsInternal(child, level + 1);
7 }

Check操作

  MemoryContextCheck函数检查内存上下文中所有chunk,调试函数

1 void MemoryContextCheck(MemoryContext context) {
2     MemoryContext child;
3     AssertArg(MemoryContextIsValid(context));
4     (*context->methods->check) (context);
5     for (child = context->firstchild; child != NULL; child = child->nextchild)
6         MemoryContextCheck(child);
7 }

Contain操作

  MemoryContextContains函数返回内存chunk是否属于某个内存上下文

 1 bool MemoryContextContains(MemoryContext context, void *pointer) {
 2     StandardChunkHeader *header;
 3     /* Try to detect bogus pointers handed to us, poorly though we can. Presumably, a pointer that isn't MAXALIGNED isn't pointing at an allocated chunk. */
 4     if (pointer == NULL || pointer != (void *) MAXALIGN(pointer))
 5         return false;
 6     /* OK, it's probably safe to look at the chunk header. */
 7     header = (StandardChunkHeader *)
 8         ((char *) pointer - STANDARDCHUNKHEADERSIZE);
 9     /* If the context link doesn't match then we certainly have a non-member chunk.  Also check for a reasonable-looking size as extra guard against being fooled by bogus pointers. */
10     if (header->context == context && AllocSizeIsValid(header->size))
11         return true;
12     return false;
13 }

 

create操作

  MemoryContextCreate函数上下文内存创建过程有点棘手,因为我们希望确保在发生故障时不会使上下文树无效(例如内存不足,无法分配上下文节点本身)。程序如下:

  1.上下文类型特定的例程首先调用MemoryContextCreate(),传递适当tag/size/methods值(methods指针通常指向静态分配的数据)。parent和name参数通常来自调用方。
  2.MemoryContextCreate()尝试分配上下文节点,并为名称添加空间。如果失败,我们可以在不造成损害的情况下调用ereport函数。
  3.我们填充所有类型无关的MemoryContext字段。
  4.我们调用类型特定的init例程(使用方法指针),init例程需要使节点的有效性最小,故障率为零,例如它不能分配更多的内存。
  5.现在我们有了一个最低限度有效的节点,当被告知重置或删除自身时,它可以正常工作。我们将节点链接到其父节点(如果有),使节点成为上下文树的一部分。
  6.我们返回到上下文类型特定例程,该例程完成特定于类型的初始化。这个例程现在可以做一些可能失败的事情(比如分配更多内存),只要它确定节点处于delete可以处理的状态。
如果在创建顶级上下文的过程中第6步失败,这个协议不会阻止我们泄漏内存,因为在这种情况下没有父链接。但是,如果在构建顶级上下文时内存不足,那么您还是可以回去的。
通常,上下文节点和名称是从TopMemoryContext分配的(不是从父上下文分配的,因为节点必须在父上下文的重置后生存!)。但是,这个例程本身用于创建TopMemoryContext!如果我们看到TopMemoryContext为空,我们假设我们正在创建TopMemoryContext并使用malloc()来分配节点。
请注意,MemoryContext的name字段没有指向单独分配的存储,因此在上下文删除时不应释放它。

 1 MemoryContext MemoryContextCreate(NodeTag tag, Size size,
 2                     MemoryContextMethods *methods,
 3                     MemoryContext parent,
 4                     const char *name) {
 5     MemoryContext node;
 6     Size        needed = size + strlen(name) + 1;
 7     /* Get space for node and name */
 8     if (TopMemoryContext != NULL) {
 9         /* Normal case: allocate the node in TopMemoryContext */
10         node = (MemoryContext) MemoryContextAlloc(TopMemoryContext,
11                                                   needed);
12     } else {
13         /* Special case for startup: use good ol' malloc */
14         node = (MemoryContext) malloc(needed);
15         Assert(node != NULL);
16     }
17     /* Initialize the node as best we can */
18     MemSet(node, 0, size);
19     node->type = tag;
20     node->methods = methods;
21     node->parent = NULL;        /* for the moment */
22     node->firstchild = NULL;
23     node->nextchild = NULL;
24     node->name = ((char *) node) + size;
25     strcpy(node->name, name);
26     /* Type-specific routine finishes any other essential initialization */
27     (*node->methods->init) (node);
28     /* OK to link node to parent (if any) */
29     if (parent) {
30         node->parent = parent;
31         node->nextchild = parent->firstchild;
32         parent->firstchild = node;
33     }
34     /* Return to type-specific creation routine to finish up */
35     return node;
36 }

第一步,如果在startup阶段,TopMemoryContext还没初始化,则调用malloc分配内存,如果已经初始化,就使用TopMemoryContext调用MemoryContextAlloc函数。调用methods的Init函数初始化申请的node内存上下文节点,如果指定父节点,则连接到父节点内存上下文树上去。

Alloc操作

MemoryContextAlloc函数在指定内存上下文中分配内存

1 void * MemoryContextAlloc(MemoryContext context, Size size) {
2     AssertArg(MemoryContextIsValid(context));
3     if (!AllocSizeIsValid(size))
4         elog(ERROR, "invalid memory alloc request size %lu",
5              (unsigned long) size);
6     return (*context->methods->alloc) (context, size);
7 }

MemoryContextAllocZero函数和上一个函数很像,但是会清空分配的内存

 1 void * MemoryContextAllocZero(MemoryContext context, Size size) {
 2     void       *ret;
 3     AssertArg(MemoryContextIsValid(context));
 4     if (!AllocSizeIsValid(size))
 5         elog(ERROR, "invalid memory alloc request size %lu",
 6              (unsigned long) size);
 7     ret = (*context->methods->alloc) (context, size);
 8     MemSetAligned(ret, 0, size);
 9     return ret;
10 }

MemoryContextAllocZeroAligned函数使用MemSetLoop函数清零申请的空间

 1 void * MemoryContextAllocZeroAligned(MemoryContext context, Size size) {
 2     void       *ret;
 3     AssertArg(MemoryContextIsValid(context));
 4     if (!AllocSizeIsValid(size))
 5         elog(ERROR, "invalid memory alloc request size %lu",
 6              (unsigned long) size);
 7     ret = (*context->methods->alloc) (context, size);
 8     MemSetLoop(ret, 0, size);
 9     return ret;
10 }

Swith操作

MemoryContextSwitchTo函数返回当前内存上下文,并使用参数中的内存上下文

1 MemoryContext MemoryContextSwitchTo(MemoryContext context)
2 {
3     MemoryContext old;
4     AssertArg(MemoryContextIsValid(context));
5     old = CurrentMemoryContext;
6     CurrentMemoryContext = context;
7     return old;
8 }

Strdup操作

MemoryContextStrdup函数使用MemoryCntextAlloc函数分配内存,并将string复制到内存中

1 char * MemoryContextStrdup(MemoryContext context, const char *string) {
2     char       *nstr;
3     Size        len = strlen(string) + 1;
4     nstr = (char *) MemoryContextAlloc(context, len);
5     memcpy(nstr, string, len);
6     return nstr;
7 }

内存操作

pfree是否分配的内存chunk

 1 void pfree(void *pointer) {
 2     StandardChunkHeader *header;
 3     /* Try to detect bogus pointers handed to us, poorly though we can. Presumably, a pointer that isn't MAXALIGNED isn't pointing at an allocated chunk. */
 4     Assert(pointer != NULL);
 5     Assert(pointer == (void *) MAXALIGN(pointer));
 6     /* OK, it's probably safe to look at the chunk header. */
 7     header = (StandardChunkHeader *)
 8         ((char *) pointer - STANDARDCHUNKHEADERSIZE);
 9     AssertArg(MemoryContextIsValid(header->context));
10     (*header->context->methods->free_p) (header->context, pointer);
11 }

repalloc为已经分配的内存chunk调整内存大小

 1 void * repalloc(void *pointer, Size size) {
 2     StandardChunkHeader *header;
 3     /*Try to detect bogus pointers handed to us, poorly though we can. Presumably, a pointer that isn't MAXALIGNED isn't pointing at an allocated chunk. */
 4     Assert(pointer != NULL);
 5     Assert(pointer == (void *) MAXALIGN(pointer));
 6     /* OK, it's probably safe to look at the chunk header. */
 7     header = (StandardChunkHeader *)
 8         ((char *) pointer - STANDARDCHUNKHEADERSIZE);
 9     AssertArg(MemoryContextIsValid(header->context));
10     if (!AllocSizeIsValid(size))
11         elog(ERROR, "invalid memory alloc request size %lu",
12              (unsigned long) size);
13     return (*header->context->methods->realloc) (header->context, pointer, size);
14 }

pnstrdup和pstrdup相似,但是追加null字节到not-necessarily-null-terminated输入字符串

1 char * pnstrdup(const char *in, Size len)
2 {
3     char       *out = palloc(len + 1);
4     memcpy(out, in, len);
5     out[len] = '\0';
6     return out;
7 }

 

为Win32上的libpgport提供内存支持。Win32 can't load a library that PGDLLIMPORTs a variable if the link object files also PGDLLIMPORT the same variable.For this reason, libpgport can't reference CurrentMemoryContext in the palloc macro calls.

void * pgport_palloc(Size sz){
    return palloc(sz);
}
char * pgport_pstrdup(const char *str){
    return pstrdup(str);
}
/* Doesn't reference a PGDLLIMPORT variable, but here for completeness. */
void pgport_pfree(void *pointer) {
    pfree(pointer);
}

 

  MemoryContext(src/include/nodes/memnodes.h)中的methods字段是一个MemoryContextMethods类型,它是由一系列函数指针组成的集合,其中包含了对内存上下文进行操作的函数。对于不同的MemoryContext实现,可以设置不同的方法集合。但目前MemoryContext中只有AllocSetContext一种实现,因此PG中只有针对AllocSetContext的一种操作函数集合,由全局变量AllocSetMethods表示。每当创建新的MemoryContext时,会将其methods字段置为AllocSetMethods。

 

 



 

posted @ 2020-11-30 13:08  肥叔菌  阅读(2593)  评论(0编辑  收藏  举报