天天看點

答疑解惑 · 歸檔程序cp指令的core檔案追查

## 問題現象

最近我們的幾個非生産執行個體中,均出現了由archiver程序産生的core dump檔案,讓人如臨大敵:是不是遇到了pg的大bug導緻了crash?

先來看看這些core檔案。由于我們在/proc/sys/kernel/core_pattern指定了存放core檔案的目錄,是以可以在這個目錄裡面找到這些core檔案。幸運的是,這些core檔案都不大,一般幾百kb,沒有對>檔案系統的存儲空間造成壓力:

```bash

$du -sh *

248k    core.170254

248k    core.242719

248k    core.31624

```

使用file指令,看一下core檔案的基本資訊:

#file core.170254

core.170254: elf 64-bit lsb core file x86-64, version 1 (sysv), svr4-style, from 'cp pg_xlog/00000001000000410000006e /xxxx/yyy/zzz/00000001000'

可以看到,這些core檔案由執行cp指令的程序産生,而且這個cp指令是在拷貝pg的xlog。因為我們設定了pg的archieve_command參數為'cp %f /xxx/yyy/zzz/%p',是以判斷這個執行cp指令的程序,應>該是由歸檔程序調用,用于歸檔日志的。

根據我們以前的經驗,crash一般是由于代碼的bug引起。難道系統的cp指令有bug導緻了core?

## 初步分析

我們用gdb看一下發生core dump時的調用棧:

$gdb postgres core.31624

core was generated by `cp pg_xlog/000000010000004200000091 /xxx/yyy/zzz/00000001000'.

program terminated with signal 3, quit.

#0  0x0000003ed44da360 in ?? ()

missing separate debuginfos, use: debuginfo-install glibc-2.12-1.80.2.alios6.x86_64

(gdb) bt

#1  0x0000000000407df9 in ?? ()

#2  0x007da781c2f60000 in ?? ()

......

oops,由于沒有安裝glibc的調試資訊(debug info),看不到調用棧的函數名。但從上面的資訊裡面,我們得到了一個重要線索:

就是說cp指令在執行的過程中,收到一個編号為3的信号。檢視文檔,這個信号就是sigquit。這兒有兩個疑問:

1. 為什麼收到sigquit後,會産生core檔案?

2. 為什麼歸檔程序在調用cp指令進行日志歸檔時,會收到sigquit信号?

下面我們以這兩個問題為線索進行分析。

## 問題分析

### 問題1

先看第1個問題,我們使用簡單的腳本模拟一下。用一個腳本,不斷的拷貝檔案,我們使用較大的檔案,以使cp指令運作的時間足夠長:

$cat cp.sh

while true ; do

cp -fr bigfile bigfile_copy

sleep 1

done

在上述腳本運作過程中,再用另一個腳本不斷的向其發送sigquit信号:

$cat kill.sh

while true ;

do

kill -3 `ps -ef |grep "cp -fr" |head -n 1 |awk -f' ' '{print $2}'`

注意,我們發送信号的目标程序必須是cp.sh程序的執行`cp -fr`指令的子程序,而不是cp.sh本身。這時,我們發現cp.sh的腳本收到了sigquit信号:

cp.sh: line 7:  3683 quit                    cp -fr /tmp/* /tmp2

cp.sh: line 7:  3879 quit                    cp -fr /tmp/* /tmp2

但奇怪的是,系統的core目錄并沒用core檔案産生。原來,linux系統預設的環境下,每個使用者程序的core檔案的size limit是0,需要顯式增大size limit,才能列印出core檔案。我們使用下面的指令,放開core檔案的size limit:

ulimit -c unlimited

再次運作cp.sh和kill.sh,發現core file 産生了!

$sh cp.sh

cp.sh: line 7:  7222 quit                    (core dumped) cp -fr bigfile bigfile_copy

cp.sh: line 7:  7416 quit                    (core dumped) cp -fr bigfile bigfile_copy

cp.sh: line 7:  7605 quit                    (core dumped) cp -fr bigfile bigfile_copy

cp.sh: line 7:  7798 quit                    (core dumped) cp -fr bigfile bigfile_copy

看來,cp指令在收到sigquit時,産生core檔案是正常的。查閱文檔和cp指令的源代碼發現,原來,linux下如果程序不對sigquit信号做捕獲(即不設定信号處理函數),程序在收到sigquit的行為就是列印core檔案并退出。

### 問題2

現在再看第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做如下設定:

archive_command='ulimit -c 0 && cp %p /u01/tmp/%f'

這樣在cp指令被歸檔程序調用時,其core file的size limit為0,即便收到sigquit信号,也不會列印core dump file。

繼續閱讀