pg启动过程中的那些事七:初始化共享内存和信号一:初始化shmemIndex和信号

 

       pg 现在要初始化另一块内存——共享内存 shared memory (以后 shared memory 有时会简写成 shmem ),在这块内存里, pg 存放数据、锁、各种 backend 进程等。

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


 

初始化共享内存方法调用流程图

 

2 计算 shared memory 大小

话说 main()-> ->PostmasterMain()-> ->reset_shared() ,在 reset_shared () 这个函数里, pg 首先计算干 xxx 一堆事需要的内存大小 size ,然后分之。

首先我们看看都计算了哪些内存, 估算使用动态哈希表管理共享内存需要的内存;计算数据池及管理需要的内存(根据shared_buffer );计算锁表需要的共享内存;计算xlogclog 需要的共享内存;计算共享进程、子事务、并发控制、轻量级锁、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 keyshmid 记录到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() 给该共享内存初始化spinlockShmemLock 以备shmem 分配时使用。再调用ShmemAlloc() (这个涉及到pg 的另一块内存——共享内存/shared memory/shmem 的管理机制,到pg 的内存管理机制时在讨论。共享内存占pg 整个使用内存的90% 以上)给事务管理器transaction manager shmem 上分配一个VariableCacheData 类型的空间赋给VariableCacheData * 类型变量ShmemVariableCache 以备后用。

接着调用CreateLWLocks() 计算需要的LWLock 锁(关于pg 中的锁到并发控制的时候再讨论)的数目,并根据计算的数目分配LWLock 数组需要的空间。每个内存块(根据设定,一般8k )需要两个LWLock ,还有clogsubtrans 等需要的,这个数目会比较大,在我PCshared_buffer200MB 时这个数目是50,000+

3 分配并初始化 shmem 索引 "ShmemIndex" ——可扩展哈希表

下来调用InitShmemIndex() 初始化一个pg 的可扩展哈希表(见pg 中的数据结构一) "ShmemIndex" 作为共享内存/shared memory/shmem 的索引。Pg 基于该索引表 "ShmemIndex" 管理shmem 内存。这里就是HTABHASHHDRHashSegmentHashBucketHashElemen 等等一堆招呼,可扩展哈希表 "ShmemIndex" 诞生了。其中的HTABTopMemoryContext 里,其它在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" 结构图

 

这一节就到这儿吧。