我們知道,Bash 在執行一個外部命令時,會先 fork() 一個子進程,然后在子進程里面執行 execve() 去加載那個外部程序。fork 子進程是會耗性能的,所以 Bash 會在下面幾種情況下不 fork 子進程,直接在當前進程執行 execve()。
bash -c 'command'
如果用了 bash -c 的形式啟動 Bash,同時 -c 選項的參數里只包含一個命令,比如 bash -c 'sleep 666',這時 Bash 不會 fork 子進程去運行 sleep 命令,它會讓 sleep 直接占用自己現有的進程:
$ bash -c 'sleep 666' & $ pstree -ap ... | `-bash,3117 | |-pstree,3119 -ap | `-sleep,3118 666 ... |
3117 是我當前敲入命令的交互 Shell,3118 就是 bash -c 啟動的那個進程,然后直接被替換成了 sleep,pid 還是 3118。
我們可以看一下 Bash 無法優化的情況下,進程樹是什么樣的:
$ bash -c 'sleep 666;ls' & $ pstree -ap ... | `-bash,3117 | |-bash,3120 -c sleep\040666;ls | | `-sleep,3121 666 | `-pstree,3122 -ap ... |
這次我們給 -c 的參數包含了兩個命令,sleep 和 ls,所以 Bash 不能讓 sleep 占用它的進程,因為執行完 sleep 它還得去執行 ls。
bash -c 'command1 && command2 || command3 ... && commandN'
在這種由若干個 && 和 || 把若干個簡單命令組成的的復合命令中,最右側的那個(commandN)命令執行時(如果執行到的話)會進行 no-fork 優化:
$ bash -c 'sleep 1 || sleep 2 && sleep 666' & $ pstree -ap # 等 3 秒鐘后再執行這條 ... | `-bash,3117 | |-pstree,3126 -ap | `-sleep,3123 666 ... |
在 bash -c 'sleep 1 || sleep 2 && sleep 666' 這條命令中,一共產生過 3 個進程,bash -c 首先產生了一個進程 3123,然后 3123 又分別 fork 出兩個子進程 3124 和 3125 來分別執行 sleep 1 和 sleep 2,sleep 666 沒有產生新的進程,它和 bash -c 用了同一個進程,也就是 3123。這個優化在 Bash 4.4 之前沒有。
( command )
用顯示的子 shell 語法運行一個單獨的命令,比如 ( sleep 100 ),如果不進行優化的話,這里應該先 fork 一個子 shell,然后這個子 shell 會再 fork 一個子子 shell 去運行 sleep,一共 fork 兩次,再極端點:( ( ( ( ( sleep 100 ) ) ) ) ),會產生一個 5 級的子 shell(( ( ( ( ( echo $BASH_SUBSHELL ) ) ) ) ) 的確會輸出 5),一共 fork 6次,然而 Bash 并不會這樣做,無論你嵌套了多少級,Bash 只會 fork 一次,只產生一個子進程,然后在這個進程里執行 sleep 命令。
文章列表