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。

 

 



 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM