## 問題現象
最近我們的幾個非生産執行個體中,均出現了由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。