PostgreSQL 信号
信号是一种软件中断机制,很重要的一种进程间通信方式,很多重要的应用程序都需要处理信号。具体流程是:当两个进程想要同步时,一个进程可以调用 kill 函数向另一个进程发送一个信号。另一个进程在收到信号时会调用已注册的信号处理函数去执行一些操作,从而达到进程同步的效果。PostgreSQL 在启动 Postmaster 主进程时注册信号处理函数。
-
Postmaster 主进程安装信号的方式是 pqsignal_pm:
pqinitmask(); PG_SETMASK(&BlockSig); pqsignal_pm(SIGHUP, SIGHUP_handler); /* reread config file and have children do same */ pqsignal_pm(SIGINT, pmdie); /* send SIGTERM and shut down */ pqsignal_pm(SIGQUIT, pmdie); /* send SIGQUIT and die */ pqsignal_pm(SIGTERM, pmdie); /* wait for children and shut down */ pqsignal_pm(SIGALRM, SIG_IGN); /* ignored */ ...
-
pqinitmask() 函数用于初始化 BlockSig, UnBlockSig, StartupBlockSig。BlockSig 是当我们希望阻塞的一组信号。这包括我们通常期望得到的所有信号,但不包括永远不应该关闭的信号。StartupBlockSig 是在启动包收集期间要阻塞的信号集,它本质上是 BlockSig 减去 SIGTERM、SIGQUIT 和 SIGALRM。UnBlockSig 是我们不希望阻塞的一组信号。
typedef int sigset_t; sigset_t UnBlockSig, BlockSig, StartupBlockSig;
-
Postmaster 主进程使用 pqsignal_pm() 函数来注册信号及处理函数。在 Postmaster 子进程中全部使用 pqsignal() 函数。Postmaster 进程使用 pqsignal_pm() 函数有以下两个原因:
-
除了在 Windows 系统,该函数会在执行信号处理期间阻塞所有信号,这样比旧版本中,在信号处理程序中显式地阻塞/解除阻塞方式要快。并且如果信号到达地非常快速,这样做还可以防止堆栈消耗速度过快。
-
不设置 SA_RESTART 标志。这是因为信号在任何时候都将被阻塞,除非 ServerLoop 正在等待某些事情发生,并且在该窗口期间,我们希望信号退出 select(2) 等待,以便 ServerLoop 可以在发生一些事情时做出响应。在某些平台上,标记为 SA_RESTART 的信号不会导致 select() 等待结束。子进程通常需要SA_RESTART,所以 pqsignal() 设置了这个标志。我们希望子进程在解除阻塞信号之前建立自己的处理程序。
-
-
pqsigfunc
pqsignal_pm(int signo, pqsigfunc func)
{
#ifndef WIN32
struct sigaction act,
oact;
act.sa_handler = func;
if (func == SIG_IGN || func == SIG_DFL)
{
/* in these cases, act the same as pqsignal() */
sigemptyset(&act.sa_mask);
act.sa_flags = SA_RESTART;
}
else
{
act.sa_mask = BlockSig;
act.sa_flags = 0;
}
#ifdef SA_NOCLDSTOP
if (signo == SIGCHLD)
act.sa_flags |= SA_NOCLDSTOP;
#endif
if (sigaction(signo, &act, &oact) < 0)
return SIG_ERR;
return oact.sa_handler;
#else /* WIN32 */
return pqsignal(signo, func);
#endif
}
-
Backend 进程和辅助进程采用 pqsignal 安装信号:
pqsignal(SIGHUP, PostgresSigHupHandler); /* set flag to read config file */
pqsignal(SIGINT, StatementCancelHandler); /* cancel current query */
pqsignal(SIGTERM, die); /* cancel current query and exit */
-
pqsignal 的实现如下,分为 WIN32 和非 WIN32两套函数:
# WIN32
pqsigfunc
pqsignal(int signum, pqsigfunc handler)
{
pqsigfunc prevfunc;
if (signum >= PG_SIGNAL_COUNT || signum < 0)
return SIG_ERR;
prevfunc = pg_signal_array[signum];
pg_signal_array[signum] = handler;
return prevfunc;
}
# 非 WIN32
pqsigfunc
pqsignal(int signo, pqsigfunc func)
{
#if !defined(HAVE_POSIX_SIGNALS)
return signal(signo, func);
#else
struct sigaction act,
oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (signo != SIGALRM)
act.sa_flags |= SA_RESTART;
#ifdef SA_NOCLDSTOP
if (signo == SIGCHLD)
act.sa_flags |= SA_NOCLDSTOP;
#endif
if (sigaction(signo, &act, &oact) < 0)
return SIG_ERR;
return oact.sa_handler;
#endif /* !HAVE_POSIX_SIGNALS */
}
常用信号
进程间通信使用的常用信号包括 SIGHUP、SIGINT、SIGQUIT、SIGTERM、SIGUSR1、SIGUSR2 等。
/* Signals. */
#define SIGHUP 1 /* Hangup (POSIX). */
#define SIGINT 2 /* Interrupt (ANSI). */
#define SIGQUIT 3 /* Quit (POSIX). */
#define SIGILL 4 /* Illegal instruction (ANSI). */
#define SIGTRAP 5 /* Trace trap (POSIX). */
#define SIGABRT 6 /* Abort (ANSI). */
#define SIGIOT 6 /* IOT trap (4.2 BSD). */
#define SIGBUS 7 /* BUS error (4.2 BSD). */
#define SIGFPE 8 /* Floating-point exception (ANSI). */
#define SIGKILL 9 /* Kill, unblockable (POSIX). */
#define SIGUSR1 10 /* User-defined signal 1 (POSIX). */
#define SIGSEGV 11 /* Segmentation violation (ANSI). */
#define SIGUSR2 12 /* User-defined signal 2 (POSIX). */
#define SIGPIPE 13 /* Broken pipe (POSIX). */
#define SIGALRM 14 /* Alarm clock (POSIX). */
#define SIGTERM 15 /* Termination (ANSI). */
#define SIGSTKFLT 16 /* Stack fault. */
#define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
#define SIGCHLD 17 /* Child status has changed (POSIX). */
#define SIGCONT 18 /* Continue (POSIX). */
#define SIGSTOP 19 /* Stop, unblockable (POSIX). */
#define SIGTSTP 20 /* Keyboard stop (POSIX). */
#define SIGTTIN 21 /* Background read from tty (POSIX). */
#define SIGTTOU 22 /* Background write to tty (POSIX). */
#define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
#define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). */
#define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). */
#define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
#define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
#define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
#define SIGPOLL SIGIO /* Pollable event occurred (System V). */
#define SIGIO 29 /* I/O now possible (4.2 BSD). */
#define SIGPWR 30 /* Power failure restart (System V). */
#define SIGSYS 31 /* Bad system call. */
#define SIGUNUSED 31
#define _NSIG 65 /* Biggest signal number + 1
(including real-time signals). */
#define SIGRTMIN (__libc_current_sigrtmin ())
#define SIGRTMAX (__libc_current_sigrtmax ())
kill 函数
当一个进程想要向另一个进程发送信号量时,调用 kill 函数发送。kill 函数是 C 标准库中的一个函数,用于向指定进程或进程组发送信号。它定义在 <signal.h> 头文件中,允许程序向其他进程发送信号以影响其行为。
int kill(pid_t pid, int sig);
-
入参:
-
pid_t pid:要发送信号的目标进程的进程 ID(PID)。 如果 pid > 0,则信号 sig 将发送给进程 ID 等于 pid 的进程。 如果 pid == 0,则信号 sig 将发送给与调用进程属于同一进程组的所有进程。 如果 pid < -1,则信号 sig 将发送给进程组 ID 等于 pid 的所有进程。 如果 pid == -1,则信号 sig 将发送给所有有权限发送信号的进程(除了进程 ID 为 1 的进程)。
-
int sig:要发送的信号编号。
-
-
返回值:
-
成功时返回 0。
失败时返回 -1,并设置 errno 以指示错误类型。 -
信号处理函数
以 Postmaster 进程的信号处理函数为例。
SIGHUP_handler
当配置文件发生改变时产生 SIGHUP 信号。 Postmaster 进程在收到 IGHUP 信号时重读配置文件 postgresql.conf (注意这是在 PGC_SIGHUP 环境下) ,然后向子进程发同样的信号(Postmasler 维护着一个子进程队列 BackendList,以方便向子进程发消息) ,并重新装载 pg_hba.conf 和 pg_ident.conf 文件(这两个文件主要用来进行客户端认证)。
pmdie
pmdie处理三种信号:SIGTERM、SIGINT和SIGQUIT。
这三种信号分别对应三种不同的系统关闭方式:
-
Smart Shutdown:Postmaster 将等待所有子进程完成当前的任务后再安全关闭系统,对应于 SIGTERM 信号。
-
Fast Shutdown:Postmaster 将向所有子进程发出 SIGTERM 信号,等待子进程接收到这个信号回滚当前事务并退出后再安全关闭系统,对应于 SIGINT 信号。
-
Immediate Shutdown:Postmaster 将向所有子进程发 SIGQUIT 信号,子进程接收到这个信号马上退出,同时系统非正常关闭,对应于 SIGQUIT 信号。
全局变量Shutdown用于表示当前系统所处的关闭状态,可以有三种状态:NoShutdown、SmartShutdown 和FastShutdown。三种状态值分别对应整数0、1、2。
SIGTERM 信号处理
若系统已经处于 SmartShutdown 或 FastShutdown(之前已经收到了SIGTERM信号),说明 Postmaster 已经在准备关闭系统了,此时不予处理,直接退出信号处理函数。否则将 Shutdown 置为 SmartShutdown 状态,表示正以这种方式关闭系统。如果系统当前正处于正常运行、归档恢复模式或者一致恢复模式,需要分别向自动清理相关的子进程以及 WalWriter 子进程发送 SIGTERM 信号,并且将系统状态置为等待在线备份结束。然后调用函数 PostmasterStateMachine 来根据当前状态机的状态来等待在线备份结束以及子进程退出。
SIGINT 信号处理
若系统已经处于 FastShutdown 说明已经在准备关闭系统,直接退出信号处理函数。否则首先将Shutdown设为 FastShutdown。如果系统当前正处于归档恢复模式,则说明当前只有 BgWriter 是活动的,将当前状态设置为等待后台进程退出;如果处于正常运行、等待在线备份结束、等待后台进程结束或者一致恢复模式,则以发送 SIGTERM 信号的方式关闭所有的后台进程。最后仍然是调用函数 PostmasterStateMachine 来根据当前状态机的状态来等待在线备份结束以及子进程退出。
SIGQUIT 信号处理
这是极其暴力的做法,Postmaster 将给 PostgreSQL 系统中所有的活动进程发信号 SIGQUIT,然后自己也退出。当收到 SIGQUIT 信号时,首先通过 SignalChildren 函数向 BackendList 中的所有进程发送 SIGQUIT 信号,然后依次检查 BgWriter、WalWriter、AutoVacuum、PgArch、PgStat等辅助进程的存在状态,如果辅助进程存在,则调用 signal_child 函数向它们发送 SIGQUIT 信号。完成向各子进程发送信号的工作后,Postmaster 调用 ExitPostmaster 函数退出。
reaper
当系统中有子进程退出的时候,子进程就会给 Postmaster 发送一个 SIGCHLD 信号。Postmaster 收到 SIGCHLD 信号后调用 reaper 函数清理退出的子进程。
-
若这个退出的子进程是启动系统的子进程且属于异常退出,那么调用 ExitPostmaster 退出;否则先将 StartupPID 置为 0(标志启动结束)、FatalError 置为 false(标志系统已恢复),然后重新装载权限检测所需要的文件。最后检查其他后台辅助进程是否存在,如果不存在还需要启动它们。
-
BgWriter 进程退出,首先将 BgWriterPID 置为0。检查全局变量 pmState 是否为 PM_SHUTDOWN(表明系统状态是等待 BgWriter 做一个关闭检查点),是则向 PgStat 发送 SIGUSER2 信号要求它进行最后一次归档并退出,接着关闭 PgStat 进程;否则认为 BgWriter 是意外退出,调用 HandleChildCrash 来处理子进程崩溃(设置 BgWriterPID 为 0 等工作)。
-
WalWriter 或者 AutoVacuum 进程退出,这里将会忽略正常退出的情况(因为这两个进程会周期性地启动,执行完工作后又正常退出,且正常退出时会主动完成必要的清理),如果是意外退出则同样调用 HandleChildCrash 来进行崩溃处理。
-
PgArch 进程异常退出,系统正处于 PM_RUN(系统正常运行)状态且允许进行归档,调用 pgarch_start 函数重新启动 PgArch 进程。否则将 pmStat 改为 PM_WAIT_DEAD_END 表示正在等待“死亡”的子进程退出。
-
PgStat 进程异常退出,且 pmStat 处于 PM_RUN 的状态,调用 pgstat_start 函数重新启动 PgStat 进程。
-
SysLogger 进程意外退出,调用 SysLogger_Start 函数重启 SysLogger。