首頁 > shell教程 閱讀:0更新時間:2020-03-28 02:19:26

子Shell和子進程到底有什么區別?

shell 中有很多方法產生子進程,比如以新進程的方式運行 Shell 腳本,使用組命令、管道、命令替換等,但是這些子進程是有區別的。

子進程的概念是由父進程的概念引申而來的。在 linux 系統中,系統運行的應用程序幾乎都是從 init(pid為 1 的進程)進程派生而來的,所有這些應用程序都可以視為 init 進程的子進程,而 init 則為它們的父進程。

使用pstree -p命令就可以看到 init 及系統中其他進程的進程樹信息(包括 pid):

systemd(1)─┬─ModemManager(796)─┬─{ModemManager}(821)
            │                     └─{ModemManager}(882)
            ├─NetworkManager(975)─┬─{NetworkManager}(1061)
            │                       └─{NetworkManager}(1077)
            ├─abrt-watch-log(774)
            ├─abrt-watch-log(776)
            ├─abrtd(773)
            ├─accounts-daemon(806)─┬─{accounts-daemon}(839)
            │                        └─{accounts-daemon}(883)
            ├─alsactl(768)
            ├─at-spi-bus-laun(1954)─┬─dbus-daemon(1958)───{dbus-daemon}(1960)
            │                         ├─{at-spi-bus-laun}(1955)
            │                         ├─{at-spi-bus-laun}(1957)
            │                         └─{at-spi-bus-laun}(1959)
            ├─at-spi2-registr(1962)───{at-spi2-registr}(1965)
            ├─atd(842)
            ├─auditd(739)─┬─audispd(753)─┬─sedispatch(757)
            │               │                └─{audispd}(759)
            │               └─{auditd}(752)

本教程基于 CentOS 7 編寫,CentOS 7 為了提高啟動速度使用 systemd 替代了 init。CentOS 7 之前的版本依然使用 init。

Shell 腳本是從上至下、從左至右依次執行的,即執行完一個命令之后再執行下一個。如果在 Shell 腳本中遇到子腳本(即腳本嵌套,但是必須以新進程的方式運行)或者外部命令,就會向系統內核申請創建一個新的進程,以便在該進程中執行子腳本或者外部命令,這個新的進程就是子進程。子進程執行完畢后才能回到父進程,才能繼續執行父腳本中后續的命令及語句。

子Shell和子進程

子進程的創建

了解 Linux 編程的讀者應該知道,使用 fork() 函數可以創建一個子進程;除了 PID(進程ID)等極少的參數不同外,子進程的一切都來自父進程,包括代碼、數據、堆棧、打開的文件等,就連代碼的執行位置(狀態)都是一樣的。

也就是說,fork() 克隆了一個一模一樣的自己,身高、體重、顏值、嗓音、年齡等各種屬性都相同。當然,后期隨著各自的發展軌跡不同,兩者會變得不一樣,比如 A 好吃懶做越來越肥,B 經常健身成了一個肌肉男;但是在 fork() 出來的那一刻,兩者都是一樣的。

Linux 還有一種創建子進程的方式,就是子進程被 fork() 出來以后立即調用 exec() 函數加載新的可執行文件,而不使用從父進程繼承來的一切。什么意思呢?

比如在 ~/bin 目錄下有兩個可執行文件分別叫 a.out 和 b.out?,F在我運行 a.out,就會產生一個進程,比如叫做 A。在進程 A 中我又調用 fork() 函數創建了一個進程 B,那么 B 就是 A 的子進程,此時它們是一模一樣的。但是,我調用 fork() 后立即又調用 exec() 去加載 b.out,這可就壞事了,B 進程中的一切(包括代碼、數據、堆棧等)都會被銷毀,然后再根據 b.out 重建建立一切。這樣一折騰,B 進程除了 ID 沒有變,其它的都變了,再也沒有屬于 A 的東西了。

你看,同樣是創建子進程,但是結果卻大相徑庭:

  • 第一種只使用 fork() 函數,子進程和父進程幾乎是一模一樣的,父進程中的函數、變量、別名等在子進程中仍然有效。

  • 第二種使用 fork() 和 exec() 函數,子進程和父進程之間除了硬生生地維持一種“父子關系”外,再也沒有任何聯系了,它們就是兩個完全不同的程序。


對于 Shell 來說,以新進程的方式運行腳本文件,比如bash ./test.sh、chmod +x ./test.sh; ./test.sh,或者在當前 Shell 中使用 bash 命令啟動新的 Shell,它們都屬于第二種創建子進程的方式,所以子進程除了能繼承父進程的環境變量外,基本上也不能使用父進程的什么東西了,比如,父進程的全局變量、局部變量、文件描述符、別名等在子進程中都無效。

但是,組命令、命令替換、管道這幾種語法都使用第一種方式創建進程,所以子進程可以使用父進程的一切,包括全局變量、局部變量、別名等。我們將這種子進程稱為子 Shell(sub shell)。

子 Shell 雖然能使用父 Shell 的的一切,但是如果子 Shell 對數據做了修改,比如修改了全局變量,那么這種修改只能停留在子 Shell,無法傳遞給父 Shell。不管是子進程還是子 Shell,都是“傳子不傳父”。

總結

子 Shell 才是真正繼承了父進程的一切,這才像“一個模子刻出來的”;普通子進程和父進程是完全不同的兩個程序,只是維持著父子關系而已。

beylze編程學院,一個分享編程知識和seo優化知識的網站。跟著beylze一起學習,每天都有進步。

通俗易懂,深入淺出,一篇文章只講一個知識點。

文章不深奧,不需要鉆研,在公交、在地鐵、在廁所都可以閱讀,隨時隨地漲姿勢。

文章不涉及代碼,不燒腦細胞,人人都可以學習。

當你決定關注beylze(公眾號:beylze),你已然超越了90%的其他從業者!

相關文章

優秀教程

国产亚洲欧美日韩