Bash

為什麼後台控制操作符(&)創建的子shell不顯示在pstree下

  • January 27, 2016

我知道當我執行exit它時它會終止我目前的 shell,因為exit命令在同一個 shell 中執行。我也明白,當我執行時,exit &原始 shell 不會終止,因為&確保命令在子 shell 中執行,結果exit將終止這個子 shell 並返回到原始 shell。但我不明白的是為什麼有和沒有的命令在 下&看起來完全一樣pstree,在這種情況下sleep 10sleep 10 &。4669 是 bash 的 PID,在該 PID 下首先發出sleep 10然後sleep 10 &發出,並且在此期間從另一個 shell 實例獲得以下輸出:

# version without &
$ pstree 4669
bash(4669)───sleep(6345)

# version with &
$ pstree 4669
bash(4669)───sleep(6364)

版本不應該&包含更多生成的子外殼(例如,在這種情況下,PID 為 5555),就像這個?

bash(4669)───bash(5555)───sleep(6364)

pstreePS:為了更好的可讀性,從開頭的輸出中省略了以下程式碼:

systemd(1)───slim(1009)───ck-launch-sessi(1370)───openbox(1551)───/usr/bin/termin(4510)───bash(4518)───screen(4667)───screen(4668)───

在我開始回答這個問題之前,我還沒有意識到使用& 控制操作符在後台執行*作業會啟動一個子shell。*當命令包含在括號中或構成管道的一部分時,將創建子外殼(管道中的每個命令都在其自己的子外殼中執行)。

Bash 手冊的命令列表部分(感謝 jimmij)指出:

如果命令被控制操作符“&”終止,shell 將在子 shell 中非同步執行命令。這稱為在後台執行命令。shell 不等待命令完成,返回狀態為 0(真)。

據我了解,當您執行sleep 10 &shell fork創建一個新的子程序(自身的副本),然後立即execsleep用外部命令( )中的程式碼替換該子程序時。這類似於正常執行命令(在前台)時發生的情況。有關此機制的簡短概述,請參閱Fork-exec Wikipedia 文章

我不明白為什麼 Bash 會在子 shell 中執行後台命令,但如果您還希望能夠執行 shell 內置命令,例如exitecho在後台執行(不僅僅是外部命令),這是有道理的。

當它是在後台執行的內置 shell 時,fork會發生(導致子 shell)而無需exec呼叫以用外部命令替換自身。執行以下命令表明,當echo命令用花括號括起來並在後台執行(帶有&)時,確實創建了一個子shell:

$ { echo $BASH_SUBSHELL $BASHPID; }
0 21516
$ { echo $BASH_SUBSHELL $BASHPID; } &
[1] 22064
$ 1 22064

在上面的範例中,我將echo命令用大括號括起來以避免BASH_SUBSHELL被目前 shell 擴展;花括號用於在不使用子shell 的情況下將命令組合在一起。該命令的第二個版本(以&控制運算符結尾)清楚地表明,用 & 號終止命令會導致創建一個子 shell(帶有新的 PID)來執行echo內置命令。(我可能在這裡簡化了 shell 的行為。請參閱 mikeserv 的評論。)

如果我沒有閱讀您的問題,我永遠不會想到執行exit &,我會期望目前的 shell 退出。現在知道這些命令是在子shell中執行的,您對退出的是子shell的解釋是有道理的。


“為什麼後台控制操作符(&)創建的子shell不顯示在pstree下”

如上所述,當您執行時sleep 10 &,Bash 會自行創建子 shell,但由於sleep是外部命令,因此它會呼叫系統呼叫,該exec()系統呼叫會立即將子程序中的 Bash 程式碼和數據替換為程序的執行副本sleep。執行pstree時,exec呼叫已經完成,子程序現在將具有名稱“ sleep ”。


在遠離我的電腦時,我試圖想辦法讓子shell 執行足夠長的時間,以使子shell 顯示為pstree. 我想我們可以通過time內置命令執行命令:

$ time sleep 11 &
[2] 4502
$ pstree -p 26793
bash(26793)─┬─bash(4502)───sleep(4503)
           └─pstree(4504)

在這裡,Bash shell (26793) 派生出一個子 shell (4502),以便在後台執行命令。這個子shell執行它自己的time內置命令,反過來,fork(創建一個PID為4503的新程序)和execs來執行外部sleep命令。


使用命名管道jimmij想出了一個聰明的方法來保持創建的子shell 執行exit足夠長的時間以使其顯示pstree

$ mkfifo file
$ exit <file &
[2] 6413
$ pstree -p 26793
bash(26793)─┬─bash(6413)
           └─pstree(6414)
$ echo > file
$ jobs
[2]-  Done    exit < file

從命名管道重定向stdin很聰明,因為它會導致子shell 阻塞,直到它接收到來自命名管道的輸入。稍後,重定向echo(不帶任何參數)的輸出會將換行符寫入命名管道,從而解除對子shell 程序的阻塞,而子shell 程序又執行exit內置命令。


同樣,對於sleep命令:

$ mkfifo named_pipe
$ sleep 11 < named_pipe &
[1] 6600
$ pstree -p 26793
bash(26793)─┬─bash(6600)
           └─pstree(6603)

在這裡,我們看到為在後台執行命令而創建的子 shell 的 PID 為6600. 接下來,我們通過向管道寫入換行符來解除程序阻塞:

$ echo > named_pipe

然後子shell execs 執行sleep命令。

$ pstree -p 26793
bash(26793)─┬─pstree(6607)
           └─sleep(6600)

呼叫後exec(),我們可以看到子程序 ( 6600) 現在正在執行sleep程序。

引用自:https://unix.stackexchange.com/questions/256659