是誰殺死了以nohup執行的程式

2017/09/20 UNIX 共 3616 字,約 11 分鐘

最近在尋找一個程式被莫名殺死的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有兩種結束方式

  1. 自行輸入exit或類似指令
  2. 收到SIGHUP、斷線

而1的case經由bash的設定shopt huponexit也可以達到跟2一樣的效果

使用disown時,bash在內部有一個flag (J_NOHUP)來設定要不要發送SIGHUP訊息

這些退出方式組合後 如下表

case 1 + huponexit ON 或 case 2:

stateJ_NOHUPSIGHUPSIGCONTSIGTERM
RUNNINGYES   
RUNNINGNOo  
STOPYES oo
STOPNOooo

case 1 + huponexit OFF:

stateJ_NOHUPSIGHUPSIGCONTSIGTERM
RUNNINGYES   
RUNNINGNO   
STOPYES oo
STOPNO oo

可以發現,之所以前面的範例程式會繼續執行,是因為case 1下程式在background執行,而kernel又不會發送SIGHUP給background。

另外一個會被殺死的範例,則是在STOP狀態下(我們已經先用了Ctrl+Z來停止),儘管阻止了SIGHUP訊號,仍然被SIGTERM殺死

到這裡就皆大歡喜的結案了,而以下是我們做實驗的方式

針對了幾個不同shell會有不一樣的結果


離開方法

case 1: 輸入exit case 2: 關閉視窗

執行方法

stateJ_NOHUP指令
RUNNINGYES./test > test.out &; disown
RUNNINGNO./test > test.out &
STOPYES./test > test.out; ^Z; disown
STOPNO./test > test.out; ^Z

1,2,3代表有收到該訊號及其傳達順序,o代表有收到該訊號,留空白表示未收到

實驗(zsh)

zsh –version zsh 5.0.5 (x86_64-suse-linux-gnu)

case 1 (exit):

stateJ_NOHUPSIGHUPSIGCONTSIGTERM
RUNNINGYES   
RUNNINGNO   
STOPYES21 
STOPNO21 

case 2 (X):

stateJ_NOHUPSIGHUPSIGCONTSIGTERM
RUNNINGYES   
RUNNINGNO   
STOPYES21 
STOPNO21 

實驗(ksh)

strings /bin/ksh | grep ersion @(#)Version M-11/16/88i

case 1 (exit):

stateJ_NOHUPSIGHUPSIGCONTSIGTERM
RUNNINGNOo  
STOPNO312

case 2 (X):

stateJ_NOHUPSIGHUPSIGCONTSIGTERM
RUNNINGNOo  
STOPNO21 

實驗(bash)

GNU bash, version 3.2.57(1)-release (sparc-sun-solaris2.10) Copyright (C) 2007 Free Software Foundation, Inc.

case 1 (exit):

stateJ_NOHUPSIGHUPSIGCONTSIGTERM
RUNNINGYES   
RUNNINGNO   
STOPYES21 
STOPNO 12

case 2 (X):

stateJ_NOHUPSIGHUPSIGCONTSIGTERM
RUNNINGYES   
RUNNINGNO1  
STOPYES21 
STOPNO312

文章訊息

Search

    Table of Contents