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" 結構圖

 

這一節就到這兒吧。