1. main
這是整個postgres進程的程序入口。
- 首先在
startup_hacks函數中初始化全局的自旋鎖dummy_spinlock - 調用
save_ps_display_args函數,使用saved_argv保存初始的argv地址,然后將argv的內容拷貝到新申請的內存中,并讓argv指向新的內存。這樣做是因為postgres(master)進程在創建backend進程時,為了讓用戶通過ps命令可以看到不同的進程信息(包含:用戶名、數據庫、客戶端信息等),需要將這些信息填寫到原始的argv處,以便ps命令能夠正確顯示。同時因為在填入這些信息時,會破壞argv原有的內容,而很多系統的environ是緊挨著argv的,這樣有可能導致環境變量被破壞,因此這個函數里回分配一塊新的內存,將environ的內容拷貝過去,然后將environ指向新內存。 - 調用
MemoryContextInit函數創建并初始化TopMemoryContext和ErrorContext這兩個內存管理器 - 進行
locale設置 - 檢查是否
root用戶啟動,如果是就退出 - 單進程模式(調試用)調用
PostgresMain,多進程模式調用PostmasterMain
2. PostmasterMain
這是多進程postgres(master)進程的程序入口。
- 取進程id保存到
MyProcPid和PostmasterPid中,取當前時間保存到MyStartTime中 - 設置
IsPostmasterEnvironment為true,表示是在多進程模式中 - 創建
PostmasterContext內存管理器,并換為當前內存管理器 - 取當前進程二進制文件全路徑保存到
my_exec_path,取庫文件全路徑保存到pkglib_path,并檢查庫文件路徑是否能正確打開 - 設置信號處理方式:屏蔽或設置處理函數
- 調用
InitializeGUCOptions完成配置項的初始化,主要是加載默認配置項,然后從環境變量加載配置項(詳見[2.1])。 - 處理命令行啟動參數,設置配置項
- 調用
SelectConfigFiles函數加載配置文件,設置數據庫的主數據目錄。 - 調用
checkDataDir檢查主數據目錄是否可訪問,以及目錄下的PG_VERSION文件中記錄的版本號是否正確 - 當前工作目錄切換到主數據目錄
- 調用
CheckDateTokenTables對兩個時間日期格式的保留字表進行正確性校驗 - 調用
CreateDataDirLockFile在主數據目錄下創建postmaster.pid,并在里面記錄下:pid、主數據目錄、啟動時間和監聽端口等信息。然后將該文件加入到全局的鏈表lock_files中,這個列表中的文件在進程退出時會被清理 - 調用
ApplyLauncherRegister注冊1個后臺worker。這里只是指定這個worker的主入口函數、訪問權限、啟動時機等信息,然后加入到一個就緒列表中,等待后續合適時機再啟動進程。這里注冊的worker入口函數是ApplyLauncherMain。看注釋應該是注冊了一個負責邏輯復制的work - 調用
process_shared_preload_libraries加載配置文件中指定(參數:shared_preload_libraries)需要加載的插件so - 調用
InitializeMaxBackends初始化全局變量MaxBackends,表示系統信息的進程數。該值 = 客戶端最大連接數 + 自動vacuum進程數 + 最大后臺進程數 + 1 - 注冊
atexit回調函數:CloseServerPorts。這個函數主要就是關閉監聽的所有tcp端口(記錄在ListenSocket數組中),回收所有的unix socket(記錄在sock_paths列表中) - 綁定配置中指定的所有
TCP監聽服務地址(ip:port),并將這些監聽socket放到全局的ListenSocket數組中(步驟16中注冊的回調函數會釋放這些socket)。同時會把監聽的第一個host記錄到postmaster.pid的第6行 - 綁定配置中指定的所有
unix socket目錄,在這些目錄下創建unix socket句柄以及對應的lock文件句柄,并將其lock文件加入到sock_paths列表中(步驟16中注冊的回調函數會釋放這些unix socket)。同時會把創建的第一個unix socket記錄到postmaster.pid的第5行 - 調用
reset_shared函數創建共享內存和信號量。該函數直接調用CreateSharedMemoryAndSemaphores函數(詳見[2.2]) - 調用
set_max_safe_fds函數計算每個backend進程最大能打開的文件句柄數,保持在全局變量:max_safe_fds - 調用
set_stack_base設置全局變量stack_base_ptr,這個是值取該函數第1個函數局部變量的指針值,也就是set_stack_base這個函數的棧起始地址。后面可以用這個值配合檢查函數的棧起始地址是否偏移過多,也就是限制調用棧的大小 - 調用
InitPostmasterDeathWatchHandle函數創建匿名管道,讀寫句柄保持在全局數組:postmaster_alive_fds中。postgres(master)負責寫,backend進程負責監聽讀,用于postgres(master)在退出時通知喚醒backend進程 - 調用
CreateOptsFile函數創建$DataDir/postmaster.opts文件,并將執行本進程的命令行保存在其中 - 調用
RemovePgTempFiles函數刪除$DataDir/base/pgsql_tmp,$DataDir/base和$DataDir/pg_tablspc下的臨時文件和關系對象 - 調用
RemovePromoteSignalFiles函數刪除$DataDir/promote和$DataDir/fallback_promote文件,猜測這兩個文件用于將從實例提升為主 - 刪除用于保存當前日志文件名的
$DataDir/current_logfiles文件 - 調用
pgstat_init函數創建一個用于收發統計信息的udp端口 - 調用
load_hba函數,讀取加載$DataDir/pg_hba.conf文件,初始化用于客戶端連接權限控制的全局對象parsed_hba_lines列表,和用于保存這個列表的MemoryContext對象:parsed_hba_context - 調用
load_ident函數,讀取加載$DataDir/pg_ident.conf文件,初始化用于維護操作系統用戶到數據庫用戶映射關系的全局對象parsed_ident_lines列表,和用于保存這個列表的MemoryContext對象:parsed_ident_context - 獲取當前時間保存到全局對象:
PgStartTime - 在
$DataDir/postmaster.pid文件的第8行添加字符串starting,主要是為了讓pg_ctl進程能正確看到該進程的運行狀況 - 調用
maybe_start_bgworkers根據情況啟動一些后臺進程 - 調用
ServerLoop啟動postgres(master)進程的主循環:通過select處理客戶端請求,如果有新的連接請求,則調用BackendStartup創建子進程。此外在處理完客戶端連接請求后,還會檢查所有的后臺進程,如果發現有異常退出的后臺進程,就嘗試拉起
2.1 InitializeGUCOptions
這是初始化用戶配置的主函數。
- 在
pg_timezone_initialize函數中初始化時區 - 調用
build_guc_variables函數,將所有配置項放到一個大數組guc_variables中,并按配置項的字符串值排序 - 循環調用
InitializeOneGUCOption函數初始化guc_variables數組中的所有配置項,主要是加載默認值 - 設置
transaction_isolation、transaction_read_only和transaction_deferrable三個配置項 - 調用
InitializeGUCOptionsFromEnvironment函數,從環境變量中獲取配置值進行設置
2.2 CreateSharedMemoryAndSemaphores
這是創建共享內存和信號量的主函數。
- 首先判斷不能是
backend進程,因為這種情況下,共享內存應該已經存在,只需要attach上去 - 計算需要的信號量個數 =
MaxBackends+ 輔助進程數(NUM_AUXILIARY_PROCS) - 根據共享內存需要存儲的內存,估算其大小
- 調用
PGSharedMemoryCreate創建存放PGShmemHeader的共享內存- 調用
CreateAnonymousSegment創建匿名的共享內存,因為是匿名的,所以應該就是這個進程自己使用,有什么作用目前還沒看到- 如果設置使用
huge page,則從/proc/meminfo中獲取huge page的大小,然后將共享內存的大小擴大到huge page的整數倍,然后使用MAP_HUGETLB標志位調用mmap,這樣就是使用了huge page - 如果使用
huge page失敗或者設置就是不使用,則正常調用mmap
- 如果設置使用
- 將匿名的共享內存地址記錄到
AnonymousShmem,其大小記錄到AnonymousShmemSize - 注冊
AnonymousShmemDetach函數在on_shmem_exit_list中,以便postgres(master)退出時調用munmap釋放共享內存 - 調用
InternalIpcMemoryCreate創建大小為sizeof(PGShmemHeader)的共享內存- 調用
shmget分配共享內存 - 注冊
IpcMemoryDelete函數在on_shmem_exit_list中,以便進程退出時釋放共享內存 - 調用
shmat綁定共享內存 - 注冊
IpcMemoryDetach函數在on_shmem_exit_list中,以便進程退出時解綁內存 - 將共享內存的
key和id記錄到postmaster.pid的第7行
- 調用
- 將內存映射到
PGShmemHeader對象指針,進行復制,初始化如下字段:- creatorPID:當前的進程id
- magic:一個固定的整數值
- dsm_control:共享內存的id號,在
postgres(master)中為0 - device:主數據目錄的
dev - inode:主數據目錄的
inode - totalsize:匿名共享內存的大小
- freeoffset:剩余空閑空間的偏移量,這里其值為
sizeof(PGShmemHeader)
- 將該共享內存的地址保存到
UsedShmemSegAddr,將key保存到UsedShmemSegID - 將共享內存的內容拷貝到匿名的共享內存
AnonymousShmem中,返回AnonymousShmem(這里為什么要同時有個AnonymousShmem和UsedShmemSegAddr,現在還搞不清楚)
- 調用
- 調用
InitShmemAccess初始化3個全局變量:- ShmemSegHdr:指向上面分配的
AnonymousShmem,表示PGShmemHeader對象 - ShmemBase:數值上等于
ShmemSegHdr,表示起始地址 - ShmemEnd:
AnonymousShmem的結束地址
- ShmemSegHdr:指向上面分配的
- 調用
PGReserveSemaphores從共享內存中預留信號量對象數組的空間。其實就是在ShmemBase的共享內存空閑處(ShmemSegHdr->freeoffset)預留出足夠的空間,并更新ShmemSegHdr->freeoffset。同時也會初始化3個全局變量:- numSems:已經實際創建的信號量,這里為:0
- maxSems:最大能創建的信號量,這里就是前面計算的結果
- nextSemKey:下一個信號量的key,從綁定端口號計算得到,避免不同實例沖突
- 如果是
postgres(master),則調用InitShmemAllocation初始化共享內存分配機制- 從共享內存中分配1個全局自旋鎖對象
ShmemLock,并初始化。后面從共享內存中申請空間會調用ShmemAlloc(之前都是ShmemAllocUnlocked),此函數會對ShmemLock加鎖 - 從共享內存中分配1個全局的
ShmemVariableCache對象,這個對象主要保存了維護OID和XID需要的一些變量
- 從共享內存中分配1個全局自旋鎖對象
- 調用
CreateLWLocks創建LWLock數組,如果是postgres(master),要完成下面全部操作,如果是backend進程,只需要最后一步RegisterLWLockTranches- 首先預估需要的內存大小,然后從共享內存中分配足夠的空間
- 將全局變量
MainLWLockArray指向LWLock數組起始地址 - 調用
InitializeLWLocks初始化LWLock數組 - 調用
RegisterLWLockTranches對固定的LWLock對象進行注冊,主要是分配1個全局字符串數組LWLockTrancheArray,數組大小為LWLockTranchesAllocated(該數組的下標就是某類LWLock對象的TrancheID,而其字符串就是這個對象的名字,所以實際上表示LWLock對象的TrancheID到名字的映射關系)。然后注冊TranchID為64以下的固定LWLock對象的名字
- 調用
InitShmemIndex在共享內存中創建1個hash表,將ShmemSegHdr->index以及全局對象ShmemIndex指向它,后續接口絕大部分內存對象都是由這個hash表來管理 - 調用
XLOGShmemInit函數初始化pg_control對象和XLOG對象 - 調用
CLOGShmemInit函數初始化CLOG相關對象 - 調用
CommitTsShmemInit函數初始化CommitTs相關對象 - 調用
SUBTRANSShmemInit函數初始化SubTrans相關對象 - 調用
MultiXactShmemInit函數初始化MultiXact相關對象 - 調用
InitBufferPool初始化共享內存池 - 調用
InitLocks初始化鎖管理對象 - 調用
InitPredicateLocks初始化predicate鎖管理對象 - 調用
InitProcGlobal初始化全局進程表 - 調用
CreateSharedProcArray函數初始化ProcArray相關對象 - 調用
CreateSharedBackendStatus函數初始化BackendStatusArray相關對象 - 調用
TwoPhaseShmemInit函數初始化TwoPhaseState相關對象 - 調用
BackgroundWorkerShmemInit函數初始化BackgroundWorkerData相關對象 - 調用
CreateSharedInvalidationState函數初始化shmInvalBuffer相關對象 - 調用
PMSignalShmemInit函數初始化PMSignalState相關對象 - 調用
ProcSignalShmemInit函數初始化ProcSignalSlots相關對象 - 調用
CheckpointerShmemInit函數初始化CheckpointerShmem相關對象 - 調用
AutoVacuumShmemInit函數初始化AutoVacuumShmem相關對象 - 調用
ReplicationSlotsShmemInit函數初始化ReplicationSlotCtl相關對象 - 調用
ReplicationOriginShmemInit函數初始化replication_states_ctl相關對象 - 調用
WalSndShmemInit函數初始化WalSndCtl相關對象 - 調用
WalRcvShmemInit函數初始化WalRcv相關對象 - 調用
ApplyLauncherShmemInit函數初始化LogicalRepCtx相關對象 - 調用
SnapMgrInit函數初始化oldSnapshotControl相關對象 - 調用
BTreeShmemInit函數初始化btvacinfo相關對象 - 調用
SyncScanShmemInit函數初始化scan_locations相關對象 - 調用
AsyncShmemInit函數初始化asyncQueueControl和AsyncCtl相關對象 - 調用
BackendRandomShmemInit函數初始化TwoPhaseState相關對象 - 調用
dsm_postmaster_startup創建共享內存對象dsm_control,映射到文件/dev/shm/PostgreSQL.<handleID>,這個HandleID是個隨機數,保存在UsedShmemSegAddr->dsm_control字段