话说pg中有个昙花一现的进程“启动进程”(“startup progress”),做了启动XLOG、验证数据库一致性、根据需要做数据库恢复和创建检查点等事情(参见《pg启动过程中的那些事十五StartDataBase梗概》),现在来讨论这个进程。
1
StartupDataBase调用流程略图
话说Main()->PostmasterMain()->StartupDatabase(),启动数据库,StartupDatabase方法其实就是StartChildProcess方法。还有启动后台写进程的方法StartBackgroundWriter、WAL日志写进程的方法StartWalWriter、WAL日志接受进程的方法StartWalreceiver都是StartChildProcess方法,只是入参不同,可能的入参见枚举类型AuxProcType。相关定义见下面。
#define StartupDataBase() StartChildProcess(StartupProcess)
#define StartBackgroundWriter()StartChildProcess(BgWriterProcess)
#define StartWalWriter() StartChildProcess(WalWriterProcess)
#define StartWalReceiver() StartChildProcess(WalReceiverProcess)
typedef enum
{
CheckerProcess,
BootstrapProcess,
StartupProcess,
BgWriterProcess,
WalWriterProcess,
WalReceiverProcess,
NUM_AUXPROCTYPES /* Must be last! */
} AuxProcType;
接着StartChildProcess()->postmaster_forkexec()->internal_forexec(),在StartChildProcess方法中,组织了参数“postgres –forkbootNULL [v_AuxProcType]”,[v_AuxProcType]就是枚举类型AuxProcType的变量。再调用internal_forexec(),把要传递给即将产生的新进程的参数组织到了BackendParameters结构里并写入到文件pgsql_tmp/pgsql_tmp.backend_var.[pid].[tmpFileNum]里,[pid]是当前进程ID,[tmpFileNum]是一个静态递增的无符号整型变量。然后用这个文件名字代替参数串中的NULL,现在参数成了这样“postgres –forkboot pgsql_tmp/pgsql_tmp.backend_var.[pid].[tmpFileNum][v_AuxProcType]”,然后fork一个进程——“启动进程”。BackendParameters结构的定义见下面
typedef struct
{
Port port;
InheritableSocket portsocket;
char DataDir[MAXPGPATH];
pgsocket ListenSocket[MAXLISTEN];
long MyCancelKey;
int MyPMChildSlot;
#ifndef WIN32
unsigned long UsedShmemSegID;
#else
HANDLE UsedShmemSegID;
#endif
void *UsedShmemSegAddr;
slock_t *ShmemLock;
VariableCache ShmemVariableCache;
Backend*ShmemBackendArray;
LWLock *LWLockArray;
slock_t *ProcStructLock;
PROC_HDR *ProcGlobal;
PGPROC *AuxiliaryProcs;
PMSignalData *PMSignalState;
InheritableSocket pgStatSock;
pid_t PostmasterPid;
TimestampTz PgStartTime;
TimestampTz PgReloadTime;
bool redirection_done;
#ifdef WIN32
HANDLE PostmasterHandle;
HANDLE initial_signal_pipe;
HANDLE syslogPipe[2];
#else
int syslogPipe[2];
#endif
char my_exec_path[MAXPGPATH];
char pkglib_path[MAXPGPATH];
char ExtraOptions[MAXPGPATH];
} BackendParameters;
该结构由postmaster用于它和前端进程交互。它包含在后台进程运行前所需要的所有状态信息。这个结构由malloc分配内存并且后台进程运行时它仍然是可用的,或者由palloc在TopMemoryContext中分配,因此它使PostgresMain执行。
typedef struct Port
{
pgsocket sock; /* File descriptor */
bool noblock; /* is the socket in non-blocking mode? */
ProtocolVersion proto; /* FE/BE protocol version */
SockAddr laddr; /* local addr (postmaster) */
SockAddr raddr; /* remote addr (client) */
char *remote_host; /* name (or ip addr) of remote host */
char *remote_hostname;/* name (not ipaddr) of remote host, if
*available */
int remote_hostname_resolv; /* +1 = remote_hostname is known to
* resolve to client's IP address; -1
* = remote_hostname is known NOT to
* resolve to client's IP address; 0 =
* we have not done the forward DNS
* lookup yet */
char *remote_port; /* text rep of remote port */
CAC_state canAcceptConnections; /*postmaster connection status */
/* 需要启动包保存的 并传递进后台进程使用的信息。指向一个可替换的名字值对列表 */
char *database_name;
char *user_name;
char *cmdline_options;
List *guc_options;
/*在认证周期期间需要持有的信息 */
HbaLine *hba;
char md5Salt[4]; /*Password salt */
/*没有业务的放在该结构里的信息,但是因为elog.c要用到,database_name和其它成员也是同样的情况。 */
TimestampTz SessionStartTime; /* backend start time */
/*TCP活跃设置
如果AF_UNIX或者还不知道,默认值是0;如果AF_UNIX或者使用默认值,当前值是0。-1作为默认值意味着本进程不能发现默认的。 */
int default_keepalives_idle;
int default_keepalives_interval;
int default_keepalives_count;
int keepalives_idle;
int keepalives_interval;
int keepalives_count;
#if defined(ENABLE_GSS) || defined(ENABLE_SSPI)
/*如果支持GSSAPI,存储GSSAPI信息。否则,物理保证在该结构里相同的偏移量而置NULL*/
pg_gssinfo *gss;
#else
void *gss;
#endif
/* SSL 结构(放在最后以使其不影响其他字段位置) */
#ifdef USE_SSL
SSL *ssl;
X509 *peer;
char peer_dn[128 + 1];
char peer_cn[SM_USER + 1];
unsigned long count;
#endif
} Port;
2
启动进程的调用流程略图
在启动进程里Main()->SubPostmasterMain(),调用了如下方法,启动XLOG后就结束了生命。
1)MemoryContextInt方法,参见《PostgresQL启动过程中的那些事一》;
2)InitializeGUCOptions方法,参见《PostgresQL启动过程中的那些事三》;
3)Read_backend_variablases方法,为重组BackendParameters结构读取前面存储的文件pgsql_tmp/pgsql_tmp.backend_var.[pid].[tmpFileNum];
4)PGSharedMemoryReAttach方法,attach进程postmaster里的共享内存;
5)read_nondefault_variables方法,读非默认GUC参数,参见《PostgresQL中的那些事十一:保存非默认GUC参数到文件》;
6)ClosePostmasterPorts方法,关闭“启动进程”不用的文件句柄,当然,在postmaster进程里这些文件还是打开的;
7)InitShmemAccess方法,在初始化本进程共享内存全局变量:这些shmem头的ShmemSegHdr、shmem起始地址ShmemBase和shmem结束地址+1的ShmemBase。定义见下面。
staticPGShmemHeader *ShmemSegHdr;/* shared mem segment header */
staticvoid *ShmemBase;/* start address of shared memory */
staticvoid *ShmemEnd;/* end+1 address of shared memory */
8)InitAuxiliaryProcess方法,初始化一个PGPROC结构;
9)CreateSharedMemoryAndSemaphores方法,参见《PostgresQL中的那些事七》;
10)AuxiliaryProcessMain方法,辅助进程入口函数,干一堆活;
SubPostmasterMain的流程图见下面。根据启动进程的传入参数“postgres –forkboot NULL [v_AuxProcType]”走了"--forkboot"这个分支。还有bgwriter进程、WalWriter进程、WalReciver进程都走了这个分支,以后要讨论到相关进程,就直接从这个分支里开始了。还有AutoVacuumLauncher进程、AutoVacuumWorker进程、归档进程、统计进程以及为前端提供服务的postgres进程等在进程初始阶段,几乎没有区别的都走了1-6步,然后根据不同入参走了不同的分支,因此以后要讨论到这些进程,就直接从这些分支开始。
SubPostmasterMain的流程图
第8步InitAuxiliaryProcess初始化了一个每个辅助进程都有一个的PGPROC结构。每一个后台进程都在共享内存里有一个PGPROC结构,共享内存里有一个未使用的PGPROC结构链表,从其中给新的后台进程分配。参见《PostgresQL启动过程中的那些事七初始化ProcGlobal》。
当等待锁时,这个PGPROC结构被链入锁的等待进程队列。回收后的PGPROC链入ProcGlobal(见《PostgresQL启动过程中的那些事七初始化ProcGlobal》)的空闲进程列表。
注意,两阶段提交会为每一个当前已准备事务设置一个假的PGPROC。这些PGPROC出现在ProcArray数据结构里以使已准备事务显示其还在运行并且能正确显示其持有锁。已准备事务的PGPROC和真实进程的PGPROC的区别是已准备事务的PGPROC的pid等于0。在已准备事务的PGPROC里不使用信号和锁行为,但是它的myProcLocks[]列表是有效的。
struct PGPROC
{
/* proc->links必须是结构的第一个成员*/
SHM_QUEUE links; /* list link if process is in a list */
PGSemaphoreData sem; /* ONE semaphore to sleep on */
int waitStatus; /*STATUS_WAITING, STATUS_OK or STATUS_ERROR */
LocalTransactionId lxid; /* local id of top-level transaction currently
*being executed by this proc, if running;
*else InvalidLocalTransactionId */
TransactionId xid; /* id of top-level transaction currently being
*executed by this proc, if running and XID
* is assigned; else InvalidTransactionId */
TransactionId xmin; /* minimal running XID as it was when we were
*starting our xact, excluding LAZY VACUUM:
*vacuum must not remove tuples deleted by
* xid>= xmin ! */
int pid; /* Backend's process ID; 0 if prepared xact */
/* 当后台进程仍在启动时这些字段是0: */
BackendId backendId; /* This backend's backend ID (if assigned) */
Oid databaseId; /* OID of database this backendis using */
Oid roleId; /* OID of role using this backend */
bool inCommit; /* true if within commit critical section */
uint8 vacuumFlags; /* vacuum-related flags, see above */
/*当是热备模式时,显示已为当前事务发出冲突信号。尽管没有要求,当持有ProcArrayLock锁事设置/消除。如果需要,没有锁可以访问。
bool recoveryConflictPending;
/* 如果有,是进程当前正在等待的轻量锁的信息 */
bool lwWaiting; /* true if waiting for an LW lock */
bool lwExclusive; /* true if waiting for exclusive access */
struct PGPROC*lwWaitLink; /* next waiter for same LW lock */
/* 如果有,进程当前正在等待的锁的信息*/
/* 如果当前没有等待的锁,waitLock和waitProcLock是NULL */
LOCK *waitLock; /* Lock object we're sleeping on ... */
PROCLOCK *waitProcLock; /* Per-holder info for awaited lock */
LOCKMODE waitLockMode; /* type of lock we're waiting for */
LOCKMASK heldLocks; /* bitmask for lock types already held on this
*lock object by this backend */
Latch procLatch; /* generic latch for process */
/*如果需要,是允许本进程等待同步复制的信息。如果没有等待的话waitLSN的值是InvalidXLogRecPtr;仅由用户后台进程设置。除非属主进程或WALSender进程可以touch syncRepState。仅当持有SyncRepLock锁时才可以用syncRepLinks。
*/
XLogRecPtr waitLSN; /* waiting for this LSN or higher */
int syncRepState; /* wait state forsync rep */
SHM_QUEUE syncRepLinks; /* list link if process is in syncrepqueue */
/*为锁持有的所有PROCLOCK对象或者由该后台进程等待的PROCLOCK被链入这些链表中的一个,根据他们锁的分区号。
所有为持有锁或者有后台进程等待的PROCLOCK对象被链到这个列表,股他们锁的发布号。
*/
SHM_QUEUE myProcLocks[NUM_LOCK_PARTITIONS];
struct XidCache subxids; /* cache for subtransaction XIDs */
};
typedef struct SHM_QUEUE
{
struct SHM_QUEUE *prev;
struct SHM_QUEUE *next;
} SHM_QUEUE;
第10步行信息
AuxiliaryProcessMain的流程图
在AuxiliaryProcessMain方法中,设置本进程运行模式为引导模式,调用BaseInit方法初始化了一个虚拟文件描述符结构Vfd的头指针VfdCache并注册了进程退出是清理临时文件的函数,接着初始化了存储管理器,这个另行讨论,最后初始化了本地记录每个缓存信息的数组。然后根据情况ProcSignalInit为辅助进程分配ProcSignalSlot,调用InitBufferPoolBackbend方法,在其中调用on_shmem_exit注册共享内存退出清理缓存相关资源要调用的函数AtProcExit_Buffers。然后把进程设回通常模式。根据传入参数调用StartupProcessMain,设置合适的信号处理句柄,然后调用StartupXLOG方法,这个方法有约1000行,搞完退出,产生子进程退出信号,激活postmaster进程响应该信号句柄reaper,启动其它进程。
先到这儿把,下一篇接着讨论StartupXLOG方法。
------------