為什麼我們需要fork來建立新進程?

為什麼我們需要fork來建立新進程?

在Unix中,每當我們想要建立一個新進程時,我們都會fork目前進程,建立一個與父進程完全相同的新子進程;然後我們執行 exec 系統調用,將父進程中的所有資料替換為新進程的資料。

為什麼我們首先創建父進程的副本而不直接創建新進程?

答案1

簡短的答案是,fork在 Unix 中,因為它很容易融入當時的現有系統,而且因為伯克利的前身系統使用了分叉的概念。

Unix分時系統的演變(相關文字已突出顯示):

現代形式的過程控制在幾天內設計並實施。令人驚訝的是它如此容易地融入現有系統;同時很容易看出如何設計中一些稍微不尋常的特徵之所以出現,正是因為它們代表了對現有內容的小的、易於編碼的更改。一個很好的例子是 fork 和 exec 函數的分離。創建新進程的最常見模型涉及指定要執行的進程的程序;在 Unix 中,分叉進程繼續運行與其父進程相同的程序,直到它執行明確 exec。功能的分離當然不是 Unix 獨有的,而且事實上,它存在於湯普森所熟知的伯克利分時系統中。儘管如此,這樣的假設似乎是合理的它存在於 Unix 中主要是因為 fork 可以輕鬆實現而無需改變太多其他東西。系統已經處理了多個(即兩個)進程;有一個進程表,進程在主記憶體和磁碟之間交換。最初只需要fork的實現

1)進程表的擴展

2) 新增一個 fork 調用,使用現有的交換 IO 原語將目前進程複製到磁碟交換區域,並對進程表進行一些調整。

事實上,PDP-7 的 fork 呼叫恰好需要 27 行彙編程式碼。當然,還需要對作業系統和使用者程式進行其他更改,其中一些更改相當有趣且出乎意料。但組合的 fork-exec 會更加複雜,如果只是因為 exec 本身不存在;它的功能已經由 shell 使用明確 IO 執行。

自從那篇論文以來,Unix 不斷發展。fork其次exec不再是運行程式的唯一方法。

  • 叉子被創建為一個更有效的 fork,用於新進程打算在 fork 之後立即執行 exec 的情況。進行 vfork 後,父進程和子進程共享相同的資料空間,並且父進程被掛起,直到子進程執行程式或退出。

  • posix_spawn建立一個新進程並在單一系統呼叫中執行一個檔案。它需要一堆參數,讓您選擇性地共用呼叫者開啟的檔案並將其訊號配置和其他屬性複製到新進程。

答案2

[我將重複我的部分回答這裡.]

為什麼不直接使用一個從頭開始創建新進程的命令呢? 複製一個馬上就會被替換的東西不是很荒謬而且效率低嗎?

事實上,由於以下幾個原因,這可能不會那麼有效:

  1. 產生的“副本”fork()有點抽象,因為內核使用寫時複製系統;真正需要創建的只是一個虛擬記憶體映射。如果複製然後立即調用exec(),則如果進程的活動修改了大部分數據,則實際上不必複製/創建大部分數據,因為進程不執行任何需要使用它的操作。

  2. 子進程的各個重要方面(例如,其環境)不必單獨複製或基於上下文的複雜分析等進行設定。

為了進一步解釋#1,被「複製」但隨後從未訪問過的記憶體從未真正複製過,至少在大多數情況下是如此。在這種情況下有一個例外可能如果您分叉了一個進程,然後在子進程將其替換為 之前讓父進程退出exec()。我說可能因為如果有足夠的可用內存,大部分父級都可以被緩存,而且我不確定這會被利用到什麼程度(這取決於作業系統的實現)。

當然,從表面上看這並不意味著使用副本更多的比使用空白石板更有效——除了“空白石板”實際上並不是什麼都沒有,並且必須涉及分配。系統可以有一個通用的空白/新流程模板,它以相同的方式複製,1,但是與寫入時複製分支相比,這不會真正保存任何內容。所以#1 只是證明使用「新」空進程不會更有效。

第 2 點確實解釋了為什麼使用叉子可能更有效。子級的環境是從其父級繼承的,即使它是完全不同的可執行檔。例如,如果父進程是 shell,子進程是 Web 瀏覽器,則$HOME它們兩者仍然相同,但由於任何一個都可能隨後更改它,因此它們必須是兩個單獨的副本。子裡的是原作製作的fork()

1. 這種策略可能沒有多大字面意義,但我的觀點是,創建一個進程不僅僅涉及將其映像從磁碟複製到記憶體中。

答案3

我認為Unix只有fork創建新進程的功能的原因是Unix哲學

他們建立一個可以很好地完成一件事的函數。它創建一個子進程。

如何處理新進程就取決於程式設計師了。他可以使用其中一個exec*函數並啟動另一個程序,或者他不能使用 exec 並使用同一程式的兩個實例,這可能很有用。

因此,您可以獲得更大的自由度,因為您可以使用

  1. 沒有 exec 的 fork*
  2. 與 exec* 分叉或
  3. 只需 exec* 而不使用 fork

此外,您只需記住forkexec*函數調用,這在 20 世紀 70 年代是您必須做的。

答案4

fork() 函數不僅僅是複製父進程,它還傳回一個值來指示該進程是父進程還是子進程,下圖解釋如何使用 fork() 作為父進程和子進程。

在此輸入影像描述

如圖所示,當進程是父進程時 fork() 傳回子進程 ID PID ,否則傳回0

例如,如果您有一個接收請求的進程(Web 伺服器),並且在每個請求上建立一個進程來處理該請求,那麼您可以使用它son process,這裡父親和兒子有不同的工作。

所以,不運行進程的副本並不像 fork() 那樣。

相關內容