天天看点

数据库内核月报 - 2015 / 08-PgSQL · 答疑解惑 · 归档进程cp命令的core文件追查

最近我们的几个非生产实例中,均出现了由archiver进程产生的core dump文件,让人如临大敌:是不是遇到了pg的大bug导致了crash?

先来看看这些core文件。由于我们在/proc/sys/kernel/core_pattern指定了存放core文件的目录,所以可以在这个目录里面找到这些core文件。幸运的是,这些core文件都不大,一般几百kb,没有对文件系统的存储空间造成压力:

使用file命令,看一下core文件的基本信息:

可以看到,这些core文件由执行cp命令的进程产生,而且这个cp命令是在拷贝pg的xlog。因为我们设置了pg的archieve_command参数为’cp %f /xxx/yyy/zzz/%p’,所以判断这个执行cp命令的进程,应该是由归档进程调用,用于归档日志的。

根据我们以前的经验,crash一般是由于代码的bug引起。难道系统的cp命令有bug导致了core?

我们用gdb看一下发生core dump时的调用栈:

oops,由于没有安装glibc的调试信息(debug info),看不到调用栈的函数名。但从上面的信息里面,我们得到了一个重要线索:

就是说cp命令在执行的过程中,收到一个编号为3的信号。查看文档,这个信号就是sigquit。这儿有两个疑问:

为什么收到sigquit后,会产生core文件?

为什么归档进程在调用cp命令进行日志归档时,会收到sigquit信号?

下面我们以这两个问题为线索进行分析。

先看第1个问题,我们使用简单的脚本模拟一下。用一个脚本,不断的拷贝文件,我们使用较大的文件,以使cp命令运行的时间足够长:

在上述脚本运行过程中,再用另一个脚本不断的向其发送sigquit信号:

注意,我们发送信号的目标进程必须是cp.sh进程的执行<code>cp -fr</code>命令的子进程,而不是cp.sh本身。这时,我们发现cp.sh的脚本收到了sigquit信号:

但奇怪的是,系统的core目录并没用core文件产生。原来,linux系统缺省的环境下,每个用户进程的core文件的size limit是0,需要显式增大size limit,才能打印出core文件。我们使用下面的命令,放开core文件的size limit:

再次运行cp.sh和kill.sh,发现core file 产生了!

看来,cp命令在收到sigquit时,产生core文件是正常的。查阅文档和cp命令的源代码发现,原来,linux下如果进程不对sigquit信号做捕获(即不设置信号处理函数),进程在收到sigquit的行为就是打印core文件并退出。

现在再看第2个问题,为什么cp命令的进程会收到sigquit信号?是不是postmaster发出的呢?

仔细查看系统日志记录(位于/var/log/messages),发现系统出现过oom(out of memory) kill事件。就是说,pg实例使用了过多的内存,把系统内存耗光后,linux系统发出了kill -9信号给某些占用内存较多的子进程。子进程收到kill -9信号后,就会无条件退出。而linux内核在子进程退出时,会向其父进程,即pg的postmaster主进程发送sigchild信号。从pg代码可以看到,postmaster在处理这些sigchild信号时,如果发现子进程是被kill -9杀掉的,则要用发信号的方式通知所有子进程退出(除了像sysloger这种非关键进程外)。这时postmaster向子进程发生的退出信号就是sigquit!所以我们高度怀疑cp命令收到的sigquit正是postmaster发出的。

但有个疑问是,产生core文件的cp命令进程,是归档进程利用syscmd函数新启动一个独立的子进程,所以其实它是postmaster进程的“孙子”进程;而postmaster只是像它直接的子进程发送了信号,信号是如何到达这个孙子进程的呢?

仔细查看代码发现,原来,pg代码里面fork一个子进程后,会建立一个子进程组(process group),这个子进程fork出的进程,都会在这个进程组里面。向这个子进程发信号,组中所有进程都会收到。

自此,谜底揭开,core文件的产生原因可以总结为,发生oom kill时,pg主进程会向所有子进程和子进程所拥有的process group发送kill -3信号;另一方面,归档进程会fork子进程来执行归档命令(即cp命令),此子进程在归档进程的process group里面,故也收到了kill -3信号。而且该进程会对信号执行缺省动作即产生core文件。所产生的core文件为cp命令的core文件(一般300k左右,对系统影响不大)。

我们知道,如果我们设置core file的size limit为0,就会阻止core文件产生。而对于出问题的pg实例,我们是在pg_ctl启动进程时加入了-c选项,将core file 的size limit去除;而所有postmaster的子进程和孙子进程,又继承了父进程的size limit,导致core file产生。所以,此问题的一个规避方法为,对archive_command做如下设置:

这样在cp命令被归档进程调用时,其core file的size limit为0,即便收到sigquit信号,也不会打印core dump file。