最近在尋找一個程式被莫名殺死的BUG,在一年內只發生過兩次
無法重現而且只有部分線索,在處理上變得更加困難
我曾經把兇手懷疑到nohup上,但是後來發現應該是其他因素
儘管如此,在尋找問題時看到一篇有趣的文章
是掛上了nohup的程式仍然可能被殺死,我也重新做了一次實驗來印證這件事。
原文在此
なぜnohupをバックグランドジョブとして起動するのが定番なのか?(擬似端末, Pseudo Terminal, SIGHUP他)
Example program:
#!/bin/bash
# a.sh
for ((i=1; i<1000; i++));
do
echo $i
sleep 1
done
先來看兩個例子
沒有nohup仍然不會被終止的task
$ ./a.sh > out &
$ exit
程序仍然繼續存在
有nohup仍然被終止的task
$ nohup ./a.sh
^Z
$ exit
a.sh被殺死
Principle
在釐清這個問題前,先複習一點UNIX的Process management
在UNIX的process management中有著controlling terminal、session、process group、process等角色
controlling terminal代表一個終端機,會連接到一個session
當user login後會開啟一個shell,此shell成為該session的leader
leader代表這個session發生什麼事是告知該process的
session下可以有多個process group,但只能有一個是foreground process group
foreground process group表示只有它才能與controlling terminal互動 (stdin, stdout)
其他的process group則是background process group
一旦他們想要存取stdin, stdout,只會收到SIGTTIN和SIGTTOU的訊號
以下對於session的example,表示方法為:
- session [SID]
- [Procrss Group ID] [Process Name] + [Process Name]
- [Procrss Group ID] [Process Name] + [Process Name]
當user login後會開啟一個session,由shell作為session leader
PID PPID PGID SID COMMAND
28621 28620 28621 28621 zsh
- session 28621
- 28621 zsh
開啟多個工作後
PID PPID PGID SID COMMAND
28621 28620 28621 28621 zsh
28729 28621 28729 28621 vim
28800 28621 28800 28621 ps
28801 28621 28800 28621 egrep
由shell作為session leader,底下有多個process group 同時只會有一個是foreground process group
- session 28621
- 28621 zsh
- 28729 vim
- 28800 ps + egrep
用tmux開啟多個工作
PID PPID PGID SID COMMAND
2481 1 2481 2481 tmux
2482 2481 2482 2482 zsh
23673 2482 23673 2482 Server
2490 2481 2490 2490 zsh
23677 2490 23677 2490 Client
8162 2481 8162 8162 zsh
23685 8162 23685 8162 Client
8200 2481 8200 8200 zsh
23694 8200 23694 8200 Client
9844 2481 9844 9844 zsh
23148 9844 23148 9844 top
28621 28620 28621 28621 zsh
28718 28621 28718 28621 ps
28719 28621 28718 28621 egrep
以tmux開啟時,每個window都代表了一個session
- session 2481
- 2481 tmux
- session 2482
- 2482 zsh
- 23673 Server
- session 2490
- 2490 zsh
- 23677 Client
- session 8162
- 8162 zsh
- 23685 Client
- session 8200
- 8200 zsh
- 23694 Client
- session 9844
- 9844 zsh
- 23148 top
- session 28621
- 28621 zsh
- 28718 ps + egrep
About OS
當一個pts(rsh或ssh連上後會建立的虛擬終端)斷線時
會發出SIGHUP通知斷線,讓還在執行的process結束
沒有設定的情況下收到SIGHUP的process動作是terminated
抄一篇整理文
- kernel or driver發現pseudo terminal被關閉時,發SIGHUP給session leader(shell)
- shell發給foreground process group SIGHUP
神奇的問題出現了,既然SIGHUP沒有發給background process group
那為何需要nohup ... &
呢
既然已經放到背景就不會收到SIGHUP,那根本就不需要nohup來阻止訊號吧?
在假設kernel和driver都如預期的運行下,我們把焦點放到了shell上
About shell
bash有兩種結束方式
- 自行輸入exit或類似指令
- 收到SIGHUP、斷線
而1的case經由bash的設定shopt huponexit
也可以達到跟2一樣的效果
使用disown時,bash在內部有一個flag (J_NOHUP)來設定要不要發送SIGHUP訊息
這些退出方式組合後 如下表
case 1 + huponexit ON 或 case 2:
state | J_NOHUP | SIGHUP | SIGCONT | SIGTERM |
---|---|---|---|---|
RUNNING | YES | |||
RUNNING | NO | o | ||
STOP | YES | o | o | |
STOP | NO | o | o | o |
case 1 + huponexit OFF:
state | J_NOHUP | SIGHUP | SIGCONT | SIGTERM |
---|---|---|---|---|
RUNNING | YES | |||
RUNNING | NO | |||
STOP | YES | o | o | |
STOP | NO | o | o |
可以發現,之所以前面的範例程式會繼續執行,是因為case 1下程式在background執行,而kernel又不會發送SIGHUP給background。
另外一個會被殺死的範例,則是在STOP狀態下(我們已經先用了Ctrl+Z來停止),儘管阻止了SIGHUP訊號,仍然被SIGTERM殺死
到這裡就皆大歡喜的結案了,而以下是我們做實驗的方式
針對了幾個不同shell會有不一樣的結果
離開方法
case 1: 輸入exit case 2: 關閉視窗
執行方法
state | J_NOHUP | 指令 |
---|---|---|
RUNNING | YES | ./test > test.out &; disown |
RUNNING | NO | ./test > test.out & |
STOP | YES | ./test > test.out; ^Z; disown |
STOP | NO | ./test > test.out; ^Z |
1,2,3代表有收到該訊號及其傳達順序,o代表有收到該訊號,留空白表示未收到
實驗(zsh)
zsh –version zsh 5.0.5 (x86_64-suse-linux-gnu)
case 1 (exit):
state | J_NOHUP | SIGHUP | SIGCONT | SIGTERM |
---|---|---|---|---|
RUNNING | YES | |||
RUNNING | NO | |||
STOP | YES | 2 | 1 | |
STOP | NO | 2 | 1 |
case 2 (X):
state | J_NOHUP | SIGHUP | SIGCONT | SIGTERM |
---|---|---|---|---|
RUNNING | YES | |||
RUNNING | NO | |||
STOP | YES | 2 | 1 | |
STOP | NO | 2 | 1 |
實驗(ksh)
strings /bin/ksh | grep ersion @(#)Version M-11/16/88i
case 1 (exit):
state | J_NOHUP | SIGHUP | SIGCONT | SIGTERM |
---|---|---|---|---|
RUNNING | NO | o | ||
STOP | NO | 3 | 1 | 2 |
case 2 (X):
state | J_NOHUP | SIGHUP | SIGCONT | SIGTERM |
---|---|---|---|---|
RUNNING | NO | o | ||
STOP | NO | 2 | 1 |
實驗(bash)
GNU bash, version 3.2.57(1)-release (sparc-sun-solaris2.10) Copyright (C) 2007 Free Software Foundation, Inc.
case 1 (exit):
state | J_NOHUP | SIGHUP | SIGCONT | SIGTERM |
---|---|---|---|---|
RUNNING | YES | |||
RUNNING | NO | |||
STOP | YES | 2 | 1 | |
STOP | NO | 1 | 2 |
case 2 (X):
state | J_NOHUP | SIGHUP | SIGCONT | SIGTERM |
---|---|---|---|---|
RUNNING | YES | |||
RUNNING | NO | 1 | ||
STOP | YES | 2 | 1 | |
STOP | NO | 3 | 1 | 2 |