Bash 的 no-fork 優化

紫雲飛發表於2015-11-13

我們知道,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 命令。

相關文章