2012-03-13 216 views
9

我正在嘗試編寫一個將執行腳本作爲會話負責人的包裝器。 我對linux命令setsid的行爲感到困惑。考慮這個劇本,叫test.shlinux命令setsid

#!/bin/bash 
SID=$(ps -p $$ --no-headers -o sid) 
if [ $# -ge 1 -a $$ -ne $SID ] ; then 
    setsid bash test.sh 
    echo pid=$$ ppid=$PPID sid=$SID parent 
else 
    sleep 2 
    echo pid=$$ ppid=$PPID sid=$SID child 
    sleep 2 
fi 

輸出的不同取決於它是否被執行或來源:

$ bash 
$ SID=$(ps -p $$ --no-headers -o sid) 
$ echo pid=$$ ppid=$PPID sid=$SID 
pid=9213 ppid=9104 sid= 9104 
$ ./test.sh 1 ; sleep 5 
pid=9326 ppid=9324 sid= 9326 child 
pid=9324 ppid=9213 sid= 9104 parent 
$ . ./test.sh 1 ; sleep 5 
pid=9213 ppid=9104 sid= 9104 parent 
pid=9336 ppid=1 sid= 9336 child 
$ echo $BASH_VERSION 
4.2.8(1)-release 
$ exit 
exit 

所以,在我看來,立即setsid返回時腳本來源,但它在腳本執行時等待它的孩子。 爲什麼控制tty的存在與setsid有什麼關係?謝謝!

編輯:爲了說明起見,我添加了pid/ppid/sid報告給所有相關的命令。

回答

17

The source code of the setsid utility實際上非常簡單。如果它看到它的進程ID和進程組ID相同(即,如果它看到它是一個進程組的領導者),並且它從未爲其子進程wait() s,那麼您將注意到它只有fork():如果它fork() s,那麼父進程立即返回。如果它不是fork(),那麼它爲wait()給出一個孩子的外觀,但真正發生的只是它孩子,而它的Bash是wait() ing(就像它總是那樣)。 (當然,當它確實爲fork(),Bash不能爲它創建的子項wait(),因爲子進程wait(),而不是他們的孫輩。)

所以,你看到的行爲是不同的行爲的直接後果:

當您運行
  • . ./test.shsource ./test.sh或諸如此類的東西—或與此有關,當你剛剛從運行setsid直接Bash提示— Bash將啓動setsid,並帶有用於job control目的的新進程組標識,因此setsid將具有與其進程組ID相同的進程標識(即它是進程組標識),因此它將會fork()並不會wait()
  • 當您運行./test.shbash test.sh或諸如此類的東西,它推出setsidsetsid將是相同的進程組爲正在運行它的腳本的一部分,所以它的進程ID和進程組ID會有所不同,因此贏得了't fork(),所以它會顯示等待(實際上沒有wait() ing)。
+4

你說得對。我想知道是否值得提議'setsid'帶上一個額外的標誌,例如'-w',如果有的話,它應該等到它的孩子出現在那兒。事實上,我覺得它的行爲是不一致的:當且僅當它由組長領導(並且分叉)時它立即返回。另外,就像你說的,只有'setsid'可以等待它的孩子,調用的'bash'不能等待一個孫子。 – 2012-03-19 13:14:04

+1

是的;一般來說,沒有「等待」的「fork」有點奇怪。我不認爲我的大學操作系統教授會批准。 :-P – ruakh 2012-03-19 13:21:52

1

我觀察到的行爲是我期望的,儘管與您的不同。你可以使用set -x來確保你看到的東西正確嗎?

 
$ ./test.sh 1 
child 
parent 
$ . test.sh 1 
child 
$ uname -r 
3.1.10 
$ echo $BASH_VERSION 
4.2.20(1)-release 

當運行./test.sh 1,腳本的母公司 - 交互式shell - 是會議的領導者,所以$$ != $SID和條件是真實的。

運行. test.sh 1時,交互式shell正在執行腳本進程中,並且是它自己的會話引導程序,所以$$ == $SID和條件爲false,因此從不執行內部子腳本。

+0

你是對的;但我擔心的是當啓動腳本的shell(執行或源代碼)不是會話負責人時發生的情況。嘗試像我在原始示例中那樣添加另一個「bash」級別。 (我試圖實現的是將腳本作爲保證會話負責人運行,如果我認爲是這種情況,那麼將腳本放在第一位毫無意義。) – 2012-03-13 03:54:03

0

我覺得你的腳本沒有任何問題。我添加了額外的語句在你的代碼,看看發生了什麼:

#!/bin/bash 

    ps -H -o pid,ppid,sid,cmd 

    echo '$$' is $$ 

    SID=`ps -p $$ --no-headers -o sid` 

    if [ $# -ge 1 -a $$ -ne $SID ] ; then 
     setsid bash test.sh 
     echo pid=$$ ppid=$PPID sid=$SID parent 
    else 
     sleep 2 
     echo pid=$$ ppid=$PPID sid=$SID child 
     sleep 2 
    fi 

你關注的是這樣的:

./test.sh 1 

相信我運行這個修改後的腳本,你會看到究竟發生了什麼。如果不是會話負責人的shell運行腳本,那麼它只是去else塊。我錯過了什麼嗎?

我現在明白你的意思了:當你用腳本做./test.sh 1時,父母等待孩子完成。孩子阻止父母。但是如果你在後臺啓動孩子,那麼你會發現父母在孩子之前完成。因此,只需在腳本中進行以下更改:

 setsid bash test.sh & 
+1

我想要這段代碼的原因是將它整合到一個更復雜的腳本中,而這個腳本又可以手動執行,也可以手工執行,或者甚至可以由我知道的SGE作業引擎運行。我需要的是父母和孩子的_consistent_行爲。理想情況下:_if_'setsid'是必要的(即,我還不是會議的領導者),然後_我希望父母等待孩子。在我的例子中,_if_部分在這兩種情況下都是正確的:父'PID'!=父'SID'。讓我感到困惑的是,當腳本來源時,_then_部分不成立:parent在孩子之前退出。 – 2012-03-13 16:33:38