pg 现在要初始化另一块内存——共享内存 shared memory (以后 shared memory 有时会简写成 shmem ),在这块内存里, pg 存放数据、锁、各种 backend 进程等。
1 先上个图,看一下函数调用过程梗概,中间略过部分细节
初始化共享内存方法调用流程图
2 计算 shared memory 大小
话说 main()-> … ->PostmasterMain()-> … ->reset_shared() ,在 reset_shared () 这个函数里, pg 首先计算干 xxx 一堆事需要的内存大小 size ,然后分之。
首先我们看看都计算了哪些内存, 估算使用动态哈希表管理共享内存需要的内存;计算数据池及管理需要的内存(根据shared_buffer );计算锁表需要的共享内存;计算xlog 、clog 需要的共享内存;计算共享进程、子事务、并发控制、轻量级锁、backend 进程、后台写等需要的共享内存等,这些共享内存统统累加到size 。计算shared memory 共享内存代码如下:
size = 100000;
size = add_size(size, hash_estimate_size(SHMEM_INDEX_SIZE,
sizeof (ShmemIndexEnt)));
size = add_size(size, BufferShmemSize());
size = add_size(size, LockShmemSize());
size = add_size(size, ProcGlobalShmemSize());
size = add_size(size, XLOGShmemSize());
size = add_size(size, CLOGShmemSize());
size = add_size(size, SUBTRANSShmemSize());
size = add_size(size, TwoPhaseShmemSize());
size = add_size(size, MultiXactShmemSize());
size = add_size(size, LWLockShmemSize());
size = add_size(size, ProcArrayShmemSize());
size = add_size(size, BackendStatusShmemSize());
size = add_size(size, SInvalShmemSize());
size = add_size(size, BgWriterShmemSize());
size = add_size(size, BTreeShmemSize());
size = add_size(size, SyncScanShmemSize());
size = add_size(size, ShmemBackendArraySize());
2 分配并初始化 shared memory
计算好需要的共享内存大小 size 后调用 PGSharedMemoryCreate() 函数分配共享内存。 PGSharedMemoryCreate() 函数创建给定大小的共享内存段并初始化一个 PGShmemHeader 结构类型标准头,且给释放内存注册回调函数。如果发现死 postgres 段就回收,但是和非 postgres 内存段碰撞后 pg 不会失败。这儿的想法是检测和重用崩溃的 postmaster 或 backend 进程已经分配的 key 。
PGSharedMemoryCreate () 分配内存是先根据 postmaster 进程端口号计算找一个空闲 IPC key 的起始值。接着调用 InternalIpcMemoryCreate() 函数, 尝试根据给定 IPC key 调用 shmget() 函数 创建共享内存段。如果给定 key 的内存段已经存在就失败返回 NULL 。如果成功,把该内存段 attach 到当前进程 postmaster 并返回该内存段地址。调用 on_shmem_exit() 函数注册 detach 和 delete 该段内存时的回调函数 IpcMemoryDelete() 和IpcMemoryDetach() 到on_shmem_exit_list 数组 。
on_shmem_exit() 函数注册函数到以 ONEXIT 结构为元素的数组on_shmem_exit_list[MAX_ON_EXITS] 中以供shmem_exit() 函数执行时调用。ONEXIT 结构结构定义见下面。
static struct ONEXIT
{
void (*function) (int code, Datum arg);
Datum arg;
} on_proc_exit_list[MAX_ON_EXITS], on_shmem_exit_list[MAX_ON_EXITS];
接着调用RecordSharedMemoryInLockFile() 函数把IPC key 和shmid 记录到postmaster.pid 文件,然后从InternalIpcMemoryCreate() 返回到 PGSharedMemoryCreate() 函数,再接着在分配到的共享内存的头部放一个 PGShmemHeader (结构定义见下面)结构实例并初始化其成员,使全局静态PGShmemHeader * 类型变量ShmemSegHdr 指到这个结构。然后调用PGReserveSemaphores() 函数分配存放信号的数组需要的内存到mySemSet 数组并用 on_shmem_exit() 函数注册 ReleaseSemaphores() 函数 到on_shmem_exit_list 数组,这个数组大小和backend 进程数有关。
typedef struct PGShmemHeader /* standard header for all Postgres shmem */
{
int32 magic; /* magic # to identify Postgres segments */
#define PGShmemMagic 679834894
pid_t creatorPID; /* PID of creating process */
Size totalsize; /* total size of segment */
Size freeoffset; /* offset to first free space */
void *index; /* pointer to ShmemIndex table */
#ifndef WIN32 /* Windows doesn't have useful inode#s */
dev_t device; /* device data directory is on */
ino_t inode; /* inode number of data directory */
#endif
} PGShmemHeader;
现在到了 InitShmemAllocation() 函数,调用SpinLockInit() 给该共享内存初始化spinlock 锁ShmemLock 以备shmem 分配时使用。再调用ShmemAlloc() (这个涉及到pg 的另一块内存——共享内存/shared memory/shmem 的管理机制,到pg 的内存管理机制时在讨论。共享内存占pg 整个使用内存的90% 以上)给事务管理器transaction manager 在shmem 上分配一个VariableCacheData 类型的空间赋给VariableCacheData * 类型变量ShmemVariableCache 以备后用。
接着调用CreateLWLocks() 计算需要的LWLock 锁(关于pg 中的锁到并发控制的时候再讨论)的数目,并根据计算的数目分配LWLock 数组需要的空间。每个内存块(根据设定,一般8k )需要两个LWLock ,还有clog 、subtrans 等需要的,这个数目会比较大,在我PC 上shared_buffer 是200MB 时这个数目是50,000+ 。
3 分配并初始化 shmem 索引 "ShmemIndex" ——可扩展哈希表
下来调用InitShmemIndex() 初始化一个pg 的可扩展哈希表(见pg 中的数据结构一) "ShmemIndex" 作为共享内存/shared memory/shmem 的索引。Pg 基于该索引表 "ShmemIndex" 管理shmem 内存。这里就是HTAB 、HASHHDR 、HashSegment 、HashBucket 、HashElemen 等等一堆招呼,可扩展哈希表 "ShmemIndex" 诞生了。其中的HTAB 在TopMemoryContext 里,其它在shmem 里, "ShmemIndex" 哈希表里存的是ShmemIndexEnt 类型实例,记录shmem 里每个内存块的名字、大小及偏移信息。按默认信息创建的 "ShmemIndex" 哈希表可以管理64M 以上个内存片段(每个哈希桶的开链表按1 个元素计算),结构见下图。
typedef struct
{
char key[SHMEM_INDEX_KEYSIZE]; /* string name */
void *location; /* location in shared mem */
Size size; /* # bytes allocated for the structure */
} ShmemIndexEnt;
static PGShmemHeader *ShmemSegHdr; /* shared mem segment header */
共享内存及其索引 "ShmemIndex" 结构图
这一节就到这儿吧。