PostgreSQL的信号处理机制
作者: 张文升
邮箱:zhang.wensheng@foxmail.com
日期: 2015-12-01
背景
信号是一种软件中断机制,是linux很重要的一种进程间通信方式,很多重要的应用程序都需要处理信号。PostgreSQL在启动Postmaster主进程时注册信号处理函数。
一、PostgreSQL安装信号的实现方法
在linux中使用signal()和sigaction()函数安装信号。PostgreSQL定义了pgsignal函数实现信号的安装。
pgsignal的定义:
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 = 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
}
如果当前的系统不遵循POSIX标准,例如一些早期的Unix系统,PG使用signal安装信号和信号处理程序,signal的第一个参数指定信号值,第二个参数指定针对前面信号值的处理程序,也可以忽略该信号(参数设为SIG_IGN);
对于遵循POSIX标准的操作系统,则使用sigaction安装信号。上面的代码定义了两个结构为sigaction的实例act和oact。act的sa_handler可以是用户自定义的处理函数外,还可以为SIG_DFL(缺省处理方式),也可以为SIG_IGN(忽略信号)。
sigaction函数的第一个signo参数指定信号值,第二个参数&act是指向结构sigaction的一个实例的指针;第三个参数&oact指向的对象不为空,用来保存原来对相应信号的处理。
pgsignal返回的是pgsigfunc,pgsigfunc只是一个简单的函数指针:typedef void (*pqsigfunc) (int signo);
等价于:void pgsigfunc(int signo);
二、PostgreSQL变更了哪些信号的处理
PostgreSQL在PostmasterMain入口函数中注册信号处理函数,也可以称为安装信号。/* PostgresMain.c line577:*/
pqsignal(SIGHUP, SIGHUP_handler); /* reread config file */
pqsignal(SIGINT, pmdie); /* send SIGTERM and shut down */
pqsignal(SIGQUIT, pmdie); /* send SIGQUIT and die */
pqsignal(SIGTERM, pmdie); /* wait for children and shut down */
pqsignal(SIGALRM, SIG_IGN); /* ignored */
pqsignal(SIGPIPE, SIG_IGN); /* ignored */
pqsignal(SIGUSR1, sigusr1_handler); /* message from child process */
pqsignal(SIGUSR2, dummy_handler); /* unused, reserve for children */
pqsignal(SIGCHLD, reaper); /* handle child termination */
pqsignal(SIGTTIN, SIG_IGN); /* ignored */
pqsignal(SIGTTOU, SIG_IGN); /* ignored */
/* ignore SIGXFSZ, so that ulimit violations work like disk full */
#ifdef SIGXFSZ
pqsignal(SIGXFSZ, SIG_IGN); /* ignored */
#endif
我把这段代码总结了一张图表,方便了解PostgreSQL对系统信号的变更。
在PostmasterMain函数中更改这12个信号的默认处理方式为6种信号处理程序。SIGHUP在控制TTY断开连接时的默认处理方式为终止进程,更改为SIGHUP_handler。
static void SIGHUP_handler(SIGNAL_ARGS){...}
SIGHUP_handler负责重新装载配置文件,包括hba文件,并告知子进程做同样的事情。
SIGINT,SIGTERM,SIGQUIT的默认处理方式更改为pmdie。
static void pmdie(SIGNAL_ARGS) {...}
pmdie干的事情很多。简单总结一下就是对SIGINT,SIGTERM,SIGQUIT的处理方式对应PostgreSQL的三种关闭方式:Smart/Fast/Immediate Shutdown Mode.文档对这三种关闭方式描述非常清楚: http://www.postgresql.org/docs/devel/static/server-shutdown.html
SIGALRM,SIGPIPE,SIGTTIN,SIGTTOU,SIGXFSZ这几个信号均被忽略。
SIGUSR1的默认处理程序更改为sigusr1_handler,用来处理来自子进程的信号。
SIGUSR2的默认处理程序更改为dummy_handler,我很想用“顾名思义”这个成语来描述我看到这个函数名称的第一感觉,不过还是很好奇的看看代码来验证感觉。
static void dummy_handler(SIGNAL_ARGS){ }
它果然是个空函数,实际上什么都没有做。这是为什么呢?对于实际上并不在postmaster进程使用的信号使用dummy_handler作为信号处理程序。
reaper用来处理子进程挂掉后的善后事项,也就是俗称擦屁股的,处理子进程的信号及部分内容比较多,需要专门写一篇笔记来梳理。
三、精简后的PostmasterMain函数
这个精简的PostmasterMain函数只描述了信号的处理部分。int main(int argc,char *argv[])
{
int status = 0;
pqinitmask();
PG_SETMASK(&BlockSig);
pqsignal(SIG_x, SIG_x_handler);
status = ServerLoop();
return status;
}
PostmasterMain通过以下步骤完成信号的处理:
pginitmask()
pginitmask()函数用来设置阻塞信号集(sig set)和不阻塞信号集,这里有三个信号集:UnBlockSig,BlockSig,StartupBlockSig。void pqinitmask(void)
{
/* 清除UnBlockSig信号集中的所有信号*/
sigemptyset(&UnBlockSig);
/* 初始化BlockSig指向的信号集,使其包括所有信号 */
sigfillset(&BlockSig);
sigfillset(&StartupBlockSig);
/* 增删特定的信号,这里我用SIG_x标识了特定的信号 */
sigdelset(&BlockSig, SIG_x);
sigdelset(&StartupBlockSig, SIG_x);
}
PG_SETMASK()
PG_SETMASK()函数把BlockSig信号集中的信号全部屏蔽。PG_SETMASK()为不同的OS采用了不同的处理方式,谁让PG是世界上支持操作系统最多的数据库系统呢。在类UNIX中,它只是sigsetmask(),在windows中它是pgsigsetmask()。#ifndef WIN32
// for *nix
#define PG_SETMASK(mask) sigsetmask(*((int*)(mask)))
#else
// for windows
#define PG_SETMASK(mask) pgsigsetmask(*((int*)(mask)))
int pgsigsetmask(int mask);
#endif
pgsignal()
安装表中描述的各种信号。
四、其他
以上就是PG对信号的处理。这里主要讨论了在Linux系统的处理,对于windows系统,则是模拟一个类似lunix信号的消息队列出来,在src/backend/port/win32/signal.c的代码,是在win32中模拟linux信号的一些函数,关于windows相关的内容,我没有深入了解了。
五、精简的PostmasterMain的实现
附上一个最精简的PostmasterMain的实现,只安装了一个SIGHUP信号。#include #include #include #ifdef HAVE_SIGPROCMASK
sigset_t UnBlockSig,
BlockSig,
StartupBlockSig;
#else
int UnBlockSig,
BlockSig,
StartupBlockSig;
#endif
#ifndef WIN32
// for *nix
#define PG_SETMASK(mask) sigsetmask(*((int*)(mask)))
#else
// for windows
#define PG_SETMASK(mask) pgsigsetmask(*((int*)(mask)))
int pgsigsetmask(int mask);
#endif
#define sigmask(sig) ( 1 << ((sig)-1) )
//void pqinitmask(void);
void pqinitmask(void)
{
#ifdef HAVE_SIGPROCMASK
#else
UnBlockSig = 0;
BlockSig = sigmask(SIGQUIT) |
sigmask(SIGTERM) | sigmask(SIGALRM) |
sigmask(SIGHUP) |
sigmask(SIGINT) | sigmask(SIGUSR1) |
sigmask(SIGUSR2) | sigmask(SIGCHLD) |
sigmask(SIGWINCH) | sigmask(SIGFPE);
StartupBlockSig = sigmask(SIGHUP) |
sigmask(SIGINT) | sigmask(SIGUSR1) |
sigmask(SIGUSR2) | sigmask(SIGCHLD) |
sigmask(SIGWINCH) | sigmask(SIGFPE);
#endif
}
static void SIGHUP_handler(SIGNAL_ARGS)
{
// do sth.
}
typedef void (*pqsigfunc) (int signo);
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 = 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
}
static int ServerLoop()
{
int status = 0;
for(;;){
// ..do sth.
}
return status;
}
int main(int argc,char *argv[])
{
int status = 0;
pqinitmask();
PG_SETMASK(&BlockSig);
pqsignal(SIGHUP, SIGHUP_handler);
printf("current pid is : %d\n",getpid());
status = ServerLoop();
return status;
}
编译zhangwensheng@x220:~$ gcc -g pgsignal.c -o pgsignal
运行zhangwensheng@x220:~$ ./pgsignal
current pid is : 16009
在当前终端按下终止符或者在其他终端向他发出不同信号进行观察和实验。zhangwensheng@x220:~$ kill -SIGHUP 16009
六、参考资料
《PostgreSQL数据库内核分析(彭智勇 彭煜玮)》