话说 main()->PostmasterMain()->StartupDataBase() , fork 了启动进程调用,调用 StartupXLOG 方法,启动 XLOG 、验证数据库一致性、根据情况做数据库恢复和创建检查点,然后启动进程退出。 Postmaster 进程响应启动进程退出信号,启动了后台写进程、 WAL 日志写进程、 AUTOVACUUM 进程、归档进程、统计进程这些辅助进程。
1
StartupDataBase 调用流程略图
这几个进程都调用自己相应的函数,组织不同入参,然后调用 postmaster_forkexec 函数,创建一个进程,根据不同的入参在 SubPostmasterMain 函数里走了不同的分支。
这一节讨论 walwriter 的启动过程 。该进程是 pg 8.3 里新加的。
walwriter 后台进程尝试保持一个常规后台进程完成写出 WAL 页面和文件同步 WAL 页面。它还保证 未被伴随提交立即同步到磁盘的事务提交记录(即“异步提交”)在可知的时间里到达磁盘——象实际发生的那样,最多三次 wal 写延迟( wal_writer_delay )周期时间。
注意,象 bgwriter 进程对于共享缓存那样,当 walwriter 进程不能保持时,其它后台进程有权发起 WAL 写和文件同步。
Walwriter 进程有 postmaster 进程中 startup 子进程一结束就启动。一直保留到 postmaster 进程命令其结束。通常结束时通过 SIGTERM 信号,其指示 walwriter 进程以 exit(0) 退出。紧急结束通过 SIGQUIT 信号;象其他任何后台进程, walwriter 在 SIGQUIT 上快速退出。
如果 walwriter 非期望退出, postmaster 进程等同后台进程崩溃处理:共享内存可能被干掉,因此保留的后台进程应该由 SIGQUIT 信号杀死然后开始一个恢复周期。
Write Ahead XLog 是在写事务数据到数据文件前先写事务日志 XLOG 。这样事务提交时可以线性文件同步事务日志到 XLOG 文件,异步写事务数据到数据文件,减少了集中 I/O 操作。
2
walwriter 进程启动后的调用流程图如下:
walwriter 进程的调用流程略图
在启动进程里 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 。定义见下面。
static PGShmemHeader *ShmemSegHdr ; /* shared mem segment header */
static void *ShmemBase; /* start address of shared memory */
static void *ShmemEnd; /* end+1 address of shared memory */
8 ) InitAuxiliaryProcess 方法,初始化一个 PGPROC 结构;
9 ) CreateSharedMemoryAndSemaphores 方法,参见《 PostgresQL 中的那些事七》;
10 ) InitXlogAccess 方法,根据在关系内存里的 xlog 控制结构 XLogCtl 初始化了全局变量 ThisTimeLineID 和 RedoRecPtr 。
11 ) WalWriterMain 方法是辅助进程 WalWriter 进程的入口函数,其主要工作都在这个方法里。
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 的空闲进程列表。
注意,两阶段提交会为每一个当前已准备事务设置一个假的 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 backend is 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 for sync rep */
SHM_QUEUE syncRepLinks ; /* list link if process is in syncrep queue */
/* 为锁持有的所有 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 步调用 InitXlogAccess 方法,根据在关系内存里的 xlog 控制结构 XLogCtl 初始化了全局变量 ThisTimeLineID 和 RedoRecPtr 。
第 11 步调用 WalWriterMain 方法,做了 walwriter 进程的主要工作。
AuxiliaryProcessMain 的流程图
在 AuxiliaryProcessMain 方法中,设置本进程运行模式为引导模式,调用 BaseInit 方法初始化了一个虚拟文件描述符结构 Vfd 的头指针 VfdCache 并注册了进程退出是清理临时文件的函数,接着初始化了存储管理器,这个另行讨论,最后初始化了本地记录每个缓存信息的数组 。然后根据情况 ProcSignalInit 为辅助进程分配 ProcSignalSlot ,调用 InitBufferPoolBackbend 方法,在其中调用 on_shmem_exit 注册共享内存退出清理缓存相关资源要调用的函数 AtProcExit_Buffers 。然后把进程设回通常模式。根据传入参数先调用了 InitXLOGAccess 方法,先从共享内存的 XLogCtl 结构里取了当前时间线 ID ThisTimeLineID 放到同名的变量里,然后又从 XLogCtl 结构的 XLogCtlInsert 类型成员 Insert 里取了最后检查点的下一个记录位置 RedoRecPtr 放到同名变量里。接着调用 WalWriterMain ,做了 walwriter 进程的主要主要工作。
先到这儿吧,下一篇接着讨论 WalWriterMain 方法。
------------
转载请著明出处,来自博客:
blog.csdn.net/beiigang
beigang.iteye.com