1. 概述
本文深入探讨PostgreSQL(简称PG)的内存管理机制,包括内存分配方式、分配流程以及各层级之间的关系。此外,文章还介绍了通过函数指针实现动态分配策略的方式,并重点分析了进程本地内存管理和多进程共享内存管理。
2. 内存管理原理
操作系统通常使用链表将相同大小的缓冲区连接起来以管理小块内存。类似地,PG定义了11个不同大小的空闲列表(freelist),用于管理不同尺寸的内存块。例如:
#define ALLOC_MINBITS 3 /* 最小内存块为8字节 */
#define ALLOCSET_NUM_FREELISTS 11 /* freelist数量 */
#define ALLOC_CHUNK_LIMIT (1 << (ALLOCSET_NUM_FREELISTS-1+ALLOC_MINBITS)) /* 最大固定尺寸内存块 */
3. 内存管理代码结构
3.1 整体设计
PG的内存管理采用两级结构:`MemoryContextData`记录和管理内存树的信息,而`AllocSetContext`负责具体的内存分配与管理。
3.2 MemoryContextData结构
以下是`MemoryContextData`的定义,其中包含父节点、子节点以及方法指针表等信息:
typedef struct MemoryContextData {
NodeTag type; /* 上下文类型 */
bool isReset; /* 是否重置 */
bool allowInCritSection; /* 是否允许在关键段内分配 */
const MemoryContextMethods *methods; /* 方法指针表 */
MemoryContext parent; /* 父节点 */
MemoryContext firstchild; /* 子节点链表头 */
MemoryContext prevchild; /* 前一个兄弟节点 */
MemoryContext nextchild; /* 后一个兄弟节点 */
const char *name; /* 名称 */
const char *ident; /* 标识符 */
MemoryContextCallback *reset_cbs; /* 回调列表 */
} MemoryContextData;
3.3 MemoryContextMethods定义
`MemoryContextMethods`是一个方法指针表,定义了各种内存操作接口:
typedef struct MemoryContextMethods {
void *(*alloc)(MemoryContext context, Size size); /* 分配 */
void (*free_p)(MemoryContext context, void *pointer); /* 释放 */
void *(*realloc)(MemoryContext context, void *pointer, Size size); /* 重新分配 */
void (*reset)(MemoryContext context); /* 重置 */
void (*delete_context)(MemoryContext context); /* 删除上下文 */
Size (*get_chunk_space)(MemoryContext context, void *pointer); /* 获取块大小 */
bool (*is_empty)(MemoryContext context); /* 是否为空 */
void (*stats)(MemoryContext context,
MemoryStatsPrintFunc printfunc, void *passthru,
MemoryContextCounters *totals); /* 统计 */
#ifdef MEMORY_CONTEXT_CHECKING
void (*check)(MemoryContext context); /* 检查 */
#endif
} MemoryContextMethods;
3.4 AllocSetContext结构
`AllocSetContext`负责实际的内存分配管理:
typedef struct AllocSetContext {
MemoryContextData header; /* 标准内存上下文字段 */
AllocBlock blocks; /* 内存块链表头 */
AllocChunk freelist[ALLOCSET_NUM_FREELISTS]; /* 空闲块链表 */
Size initBlockSize; /* 初始块大小 */
Size maxBlockSize; /* 最大块大小 */
Size nextBlockSize; /* 下一块大小 */
Size allocChunkLimit; /* 块大小限制 */
AllocBlock keeper; /* 保留块 */
int freeListIndex; /* 空闲列表索引 */
} AllocSetContext;
4. 内存分配与释放逻辑
当需要分配内存时,PG不会直接调用`malloc`,而是先通过`malloc`申请一大块内存,再从中切割出合适的内存片返回给用户。释放内存时,也不会直接归还给操作系统,而是通过空闲列表(freelist)保存这些碎片供后续使用。
如果请求的内存大小超过`allocChunkLimit`,则会直接通过`malloc`分配整块内存并返回;否则,PG会根据请求大小计算对应的空闲列表索引,并从该列表中寻找合适的内存块。
5. 内存上下文层次结构
程序启动时会初始化顶层内存上下文`TopMemoryContext`,其层次结构如下:
- `TopMemoryContext`:最顶层的内存上下文。
- `CacheMemoryContext`:持久存储RelCache、CatCache等模块的数据。
- `MessageContext`:存储与当前命令相关的数据。
- `TopTransactionContext`:事务级别的内存上下文。
- `CurTransactionContext`:当前事务使用的内存上下文。
- `ErrorContext`:错误恢复过程中使用的内存上下文。
6. 共享内存区域
共享内存区域主要包括以下部分:
- `Shared buffer pool`:存储表和索引页面。
- `WAL buffer`:事务日志写入前的缓存区域。
- `Commit log buffer`:保存事务状态信息。
7. 查看内存上下文
从PG14开始,可以通过`pg_log_backend_memory_contexts`函数查看内存上下文的使用情况。例如:
SELECT pg_log_backend_memory_contexts(pid);