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" 結構圖
這一節就到這兒吧。