代码位置
src/backend/postmaster/bgwriter.c
background writer (bgwriter)是PG 8.0的新特性。视图避免常规后端必须写出脏共享缓冲区(只有在需要释放共享缓冲区以在另一页中读取时才会这样做。)最佳方案中,共享缓冲区的所有写入将由后台写入器进程发出。但是,如果 bgwriter 无法保持足够干净的缓冲区,则定期后端仍然有权发出写入。
到PG 9.2中,bgwriter 不再处理 checkpoints。
一旦启动子进程结束,postmaster 就启动 bgwriter ,或者一旦恢复开始,如果我们正在进行存档恢复。它仍然活着,直到 postmaster 命令它终止。正常终止是由SIGTERM ,会发出 exit(0)指示bgwriter 。SIGQUIT 是紧急终止;与任何后端一样,bgwriter 将简单地中止并退出SIGQUIT 。
如果bgwriter出乎意料地退出,postmaster 将处理该后端崩溃:共享内存可能损坏,因此剩余的后端数据将被 SIGQUIT 杀死,然后开始恢复。
/* bgwriter process 主入口函数,这是从 AuxiliaryProcessMain 调用的,它已经创建了基本的执行环境,但还没有启用信号。 */
void
BackgroundWriterMain(void)
{
sigjmp_buf local_sigjmp_buf;
MemoryContext bgwriter_context;
bool prev_hibernate;
WritebackContext wb_context;
/* 正确地接受或忽略 postmaster 可能发送给我们的信号。 bgwriter 不参与ProcSignal 发信号,但仍然需要锁存唤醒的 SIGUSR1 处理程序。 */
pqsignal(SIGHUP, BgSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, SIG_IGN);
pqsignal(SIGTERM, ReqShutdownHandler); /* shutdown */
pqsignal(SIGQUIT, bg_quickdie); /* hard crash time */
pqsignal(SIGALRM, SIG_IGN);
pqsignal(SIGPIPE, SIG_IGN);
pqsignal(SIGUSR1, bgwriter_sigusr1_handler);
pqsignal(SIGUSR2, SIG_IGN);
/* 重置一些由 postmaster 接受但不在这里接受的信号 */
pqsignal(SIGCHLD, SIG_DFL);
pqsignal(SIGTTIN, SIG_DFL);
pqsignal(SIGTTOU, SIG_DFL);
pqsignal(SIGCONT, SIG_DFL);
pqsignal(SIGWINCH, SIG_DFL);
/* 允许在任何时候退出 SIGQUIT (quickdie) */
sigdelset(&BlockSig, SIGQUIT);
/* 创建一个 resource owner 来跟踪我们的资源 (目前仅是buffer pins ) */
CurrentResourceOwner = ResourceOwnerCreate(NULL, "Background Writer");
/* 我们刚刚开始,假设有一个关闭或 end-of-recovery 快照 */
last_snapshot_ts = GetCurrentTimestamp();
/* 创建一个内存上下文,我们将完成所有的工作。我们这样做,以便在错误恢复过程中可以重置上下文,从而避免可能的内存泄漏。以前,这段代码只是在 TopMemoryContext 运行,但是重新设置这将是一个非常糟糕的想法。 */
bgwriter_context = AllocSetContextCreate(TopMemoryContext,
"Background Writer",
ALLOCSET_DEFAULT_SIZES);
MemoryContextSwitchTo(bgwriter_context);
WritebackContextInit(&wb_context, &bgwriter_flush_after);
/* 如果遇到异常,则在此恢复处理 */
if (sigsetjmp(local_sigjmp_buf, 1) != 0)
{
/* 由于不使用PG_TRY,必须手动重置错误堆栈 */
error_context_stack = NULL;
/* 清理时防止中断 */
HOLD_INTERRUPTS();
/* 向服务器日志报告错误 */
EmitErrorReport();
/* 这些操作实际上只是 AbortTransaction() 的最小子集。在 bgwriter中,我们没有太多的资源需要担心,但我们确实有LWLocks, buffers, 和 temp files。 */
LWLockReleaseAll();
ConditionVariableCancelSleep();
AbortBufferIO();
UnlockBuffers();
/* buffer pins 在这里释放: */
ResourceOwnerRelease(CurrentResourceOwner,
RESOURCE_RELEASE_BEFORE_LOCKS,
false, true);
/* 我们不必担心其他ResourceOwnerRelease 阶段 */
AtEOXact_Buffers(false);
AtEOXact_SMgr();
AtEOXact_Files(false);
AtEOXact_HashTables(false);
/* 现在返回正常的顶级上下文,下次清除错误上下文。 */
MemoryContextSwitchTo(bgwriter_context);
FlushErrorState();
/* 在顶层上下文中清除任何泄漏的数据 */
MemoryContextResetAndDeleteChildren(bgwriter_context);
/* 重新初始化以避免重复错误引起问题 */
WritebackContextInit(&wb_context, &bgwriter_flush_after);
/* 现在我们可以再次中断 */
RESUME_INTERRUPTS();
/* 在发生错误的之后至少sleep一秒。一个写错误很可能会被重复,并且我们不想以尽可能快的速度填充错误日志。 */
pg_usleep(1000000L);
/* 在所有错误之后关闭所有打开的文件。这在Windows上是有用的,在那里保存删除的文件会导致各种奇怪的错误。目前还不清楚我们需要在别处 */
smgrcloseall();
/* 当没有进一步等待的可能性时,报告等待结束 */
pgstat_report_wait_end();
}
/* 现在我们可以处理ereport(ERROR) */
PG_exception_stack = &local_sigjmp_buf;
/* 解锁信号(当postmaster fork时,被锁住) */
PG_SETMASK(&UnBlockSig);
/* 任何错误之后重置休眠状态 */
prev_hibernate = false;
/* Loop forever */
for (;;)
{
bool can_hibernate;
int rc;
/* 清除任何已挂起的唤醒 */
ResetLatch(MyLatch);
if (got_SIGHUP)
{
got_SIGHUP = false;
ProcessConfigFile(PGC_SIGHUP);
}
if (shutdown_requested)
{
/* 从这里开始,elog(ERROR) 应该以 exit(1) 结束,而不是将控制返回到上面的sigsetjmp 块。 */
ExitOnAnyError = true;
/* 正常退出 */
proc_exit(0); /* done */
}
/* 做一个dirty-buffer 写入循环。 */
can_hibernate = BgBufferSync(&wb_context);
/* 向stats collector 发送活动统计信息 */
pgstat_send_bgwriter();
if (FirstCallSinceLastCheckpoint())
{
/* 在任何检查点之后,关闭所有 smgr 文件。因此,我们不会无限期地挂起 smgr 对删除文件的引用。 */
smgrcloseall();
}
/* 记录一个新的 xl_running_xacts 这样复制可以更快地进入一致状态(想想子溢出的快照),并且更频繁地清理资源(锁,KnownXids* )这样做的成本相对较低,所以一分钟做4次(LOG_SNAPSHOT_INTERVAL_MS)似乎很好。我们假设写入 xl_running_xacts 的间隔比BgWriterDelay 大很多,因此我们不会使整个超时处理复杂化,而只是假设即使休眠模式是活动的,我们也会经常被调用。严格地说,log_snap_interval_ms接口是不重要的。为了确保我们在空闲系统上没有不必要地唤醒磁盘,我们检查自上次记录运行的 xacts 以来是否插入了WAL。我们在 bgwriter 中进行日志记录,因为它是唯一有规律运行并始终返回到它的主循环的进程。例如,当激活时,检查指针几乎不在主循环中,因此很难定期记录。 */
if (XLogStandbyInfoActive() && !RecoveryInProgress())
{
TimestampTz timeout = 0;
TimestampTz now = GetCurrentTimestamp();
timeout = TimestampTzPlusMilliseconds(last_snapshot_ts,
LOG_SNAPSHOT_INTERVAL_MS);
/* 如果已经过了足够的时间,并且自上次快照以来插入了有用的记录,则只进行日志记录。<= 代替 < , 因为GetLastImportantRecPtr()指向记录的开始,而last_snapshot_lsn 指向记录的末尾。 */
if (now >= timeout &&
last_snapshot_lsn <= GetLastImportantRecPtr())
{
last_snapshot_lsn = LogStandbySnapshot();
last_snapshot_ts = now;
}
}
/* sleep,直到我们发出信号或BgWriterDelay已经完成。BgBufferSync()中的反馈控制循环期望我们将调用它的每一毫秒BgWriterDelay。虽然这并不是正确的正确性,但如果我们偏离太远,反馈回路可能会出错。因此,避免在正常操作期间可能频繁发生的闩锁事件加载该进程。 */
rc = WaitLatch(MyLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
BgWriterDelay /* ms */ , WAIT_EVENT_BGWRITER_MAIN);
/* 如果没有锁存事件并且 BgBufferSync 表示什么也没有发生,那么在“休眠”模式下扩展 sleep ,其中我们睡眠的时间比bgwriter_delay 更长。当后端再次使用缓冲区时,它会通过设置锁存器来唤醒我们。因为只有当没有缓冲区分配发生时才会持续额外的休眠,所以这不应该严重扭曲 BgBufferSync 的控制循环的行为;实际上,它会认为系统范围的空闲间隔不存在。这里存在一个竞争条件,后端可以在 BgBufferSync 将分配计数为零的时间和我们调用 StrategyNotifyBgWriter 的时间之间分配缓冲区。虽然我们无论如何不休眠并不重要,但我们试图通过只在 BgBufferSync 表示连续两个周期什么都没有发生时休眠来减少这种可能性。此外,我们用永远不休眠减轻了错过唤醒的任何可能的后果。 */
if (rc == WL_TIMEOUT && can_hibernate && prev_hibernate)
{
/* 在分配下一个缓冲区请求通知 */
StrategyNotifyBgWriter(MyProc->pgprocno);
/* Sleep ... */
rc = WaitLatch(MyLatch,
WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,
BgWriterDelay * HIBERNATE_FACTOR,
WAIT_EVENT_BGWRITER_HIBERNATE);
/* 在我们超时的情况下重置通知请求 */
StrategyNotifyBgWriter(-1);
}
/* 如果 postmaster 进程死了,将紧急救助。这是为了避免对所有 postmaster 的子进程进行手工清理。 */
if (rc & WL_POSTMASTER_DEATH)
exit(1);
prev_hibernate = can_hibernate;
}
}