PostgreSQL启动过程中的那些事十八:bgwriter进程一
话说main()->PostmasterMain()->StartupDataBase(),fork了启动进程调用,调用StartupXLOG方法,启动XLOG、验证数据库一致性、根据情况做数据库恢复和创建检查点,然后启动进程退出。Postmaster进程响应启动进程退出信号,启动了后台写进程、WAL日志写进程、AUTOVACUUM进程、归档进程、统计进程这些辅助进程。
1
StartupDataBase调用流程略图
这几个进程都调用自己相应的函数,组织不同入参,然后调用postmaster_forkexec函数,创建一个进程,根据不同的入参在SubPostmasterMain函数里走了不同的分支。 这一节讨论后台写进程bgwriter的启动过程,其是在Postgres8.0里新加的。 在需要释放共享缓存以读入其他页面时,后台写进程(bgwriter)写出脏共享缓存数据(shared buffer)。在最好情况下所有从共享缓存里往外写将由后台写进程(bgwriter)发起。但是,如果后台写进程(bgwriter)没能维护足够多的干净共享缓存,其它后台进程仍然有权发起写脏共享缓存数据。 后台写进程(bgwriter)还负责处理所有检查点(checkpoint)。在从上次以来定量时间之后他将自动分派检查点,且后台写进程(bgwriter)也能够被发信号执行被请求的检查点。(每当很多WAL段被使用,后台进程填满WAL段时,通过后台进程信号通知后台写进程(bgwriter),GUC参数安排/委任一个检查点;后台写进程(bgwriter)自己不查看条件。) 后台写进程(bgwriter)由postmaster进程在启动子进程(startup process)一结束就启动,或者如果我们正在做归档回复,恢复进程一开始就启动。他一起存活到postmaster命令他结束。通常结束后台写进程(bgwriter)是通过SIGUSR2信号,且指示后台写进程(bgwriter)执行一个关闭检查点(shutdown checkpoint)并退出(exit(0))。(在SIGUSR2信号发起之前所有backend进程必须被停止!)紧急结束通过信号SIGQUIT;和任何后台进程(backend)一样,后台写进程(bgwriter)将简单退出且在SIGQUIT信号上退出。 如果后台写进程(bgwriter)非预期退出,postmaster进程象处理backend进程崩溃一样处理:共享内存可能被破坏,因此现有的backend进程应该通过SIGQUIT信号被杀掉而且后面启动一个恢复周期。(甚至如果共享内存没有被破坏,pg丢失了下一个检查点哪些文件需要同步的信息,因此需要强制做一个系统重启。) pg有三个GUC参数:bgwriter_delay(默认200毫秒)、bgwriter_lru_maxpages(默认100)、bgwriter_lru_multiplier(默认2.0)。系统每个bgwriter_delay时间唤醒bgwriter,扫描LRU链表,最多写出N×bgwriter_lru_multiplier且不超过bgwriter_lru_maxpages个脏缓冲页,N是最近两次运行bgwriter期间系统申请的缓冲页数。如果Bgwriter频繁写经常更新的页会增加I/O次数而性能下降,但如果bgwriter间隔很长时间才写,又不能优化I/O,这个需要根据实际情况权衡。
2 Bgwriter进程启动后的调用流程图如下:
bgwriter进程的调用流程略图
在启动进程里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)BackgroundWriterMain方法是辅助进程bgWriter进程的入口函数,其主要工作都在这个方法里。
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 */ LocalTransactionIdlxid; /* local id of top-level transaction currently *being executed by this proc,if running; *else InvalidLocalTransactionId */ TransactionIdxid; /* id of top-level transaction currently being *executed by this proc,if running and XID *is assigned; else InvalidTransactionId */ TransactionIdxmin; /* 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。 */ XLogRecPtrwaitLSN; /* 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 */ }; typedefstruct SHM_QUEUE { struct SHM_QUEUE *prev; struct SHM_QUEUE *next; } SHM_QUEUE; 第10步调用BackgroundWriterMain方法,做了bgwriter进程的主要工作。
AuxiliaryProcessMain的流程图 在AuxiliaryProcessMain方法中,设置本进程运行模式为引导模式,调用BaseInit方法初始化了一个虚拟文件描述符结构Vfd的头指针VfdCache并注册了进程退出是清理临时文件的函数,接着初始化了存储管理器,这个另行讨论,最后初始化了本地记录每个缓存信息的数组。然后根据情况ProcSignalInit为辅助进程分配ProcSignalSlot,调用InitBufferPoolBackbend方法,在其中调用on_shmem_exit注册共享内存退出清理缓存相关资源要调用的函数AtProcExit_Buffers。然后把进程设回通常模式。根据传入参数调用BackgroundWriterMain,做了bgwriter进程的主要主要工作。 先到这儿吧,下一篇接着讨论BackgroundWriterMain方法。
------------ 转载请著明出处,来自博客: blog.csdn.net/beiigang beigang.iteye.com (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |