fork (系統呼叫)
此條目翻譯自其他語言維基百科,需要相關領域的編者協助校對翻譯。 |
此條目需要精通或熟悉相關主題的編者參與及協助編輯。 |
在電腦領域中,尤其是Unix及類Unix系統作業系統中,fork(行程複製)是一種建立自身行程副本的操作。它通常是核心實現的一種系統呼叫。Fork是類Unix作業系統上建立行程的一種主要方法,甚至歷史上是唯一方法。
概述
編輯在多工作業系統中,行程(執行的程式)需要一種方法來新增行程,例如執行其他程式。Fork及其變種在類Unix系統中通常是這樣做的唯一方式。如果行程需要啟動另一個程式的可執行檔,它需要先Fork來建立一個自身的副本。然後由該副本即「子行程」呼叫exec系統呼叫,用其他程式覆蓋自身:停止執行自己之前的程式並執行其他程式。
Fork操作會為子行程建立一個單獨的定址空間。子行程擁有父行程所有主記憶體段的精確副本。在現代的UNIX變種中,這遵循出自SunOS-4.0的虛擬記憶體模型,根據寫入時複製語意,實體記憶體不需要被實際複製。取而代之的是,兩個行程的虛擬記憶體頁面可能指向實體記憶體中的同一個頁,直到它們寫入該頁時,寫入才會發生。在用fork配合exec來執行新程式的情況下,此最佳化很重要。通常來說,子行程在停止程式執行前會執行一小組有利於其他程式的操作,它可能用到少量的其父行程的資料結構。
當一個行程呼叫fork時,它被認為是父行程,新建立的行程是它的孩子(子行程)。在fork之後,兩個行程還執行著相同的程式,都像是呼叫了該系統呼叫一般恢復執行。然後它們可以檢查呼叫的返回值確定其狀態:是父行程還是子行程,以及據此行事。
fork系統呼叫在第一個版本的Unix就已存在[1],它借用於更早的GENIE 分時系統。[2]Fork是標準化的POSIX的一部分。[3]
通訊
編輯子行程從父行程的檔案描述子副本開始。[3]對於行程間通訊,父行程通常會建立一個或多個管道,在fork行程之後,行程關閉它們不需要的管道端。[4]
變種
編輯Vfork
編輯Vfork是與fork具有相同呼叫約定和很多相同語意的一個變種,但只能在有限的情況下使用它。它起源於Unix的3BSD版本[5][6][7],這是首個支援虛擬記憶體的Unix版本。它已按POSIX標準化,這使得vfork能具有與fork完全相同的行為。但這已在2004年的版本中被標為過時[8],並在後續版本中被posix_spawn()取代(其通常通過vfork實現)。
在發出一個vfork系統呼叫時,父行程被暫停,直至子行程完成執行或被新的可執行映像取代(通過系統呼叫之「exec」家族中的一項)。子行程借用父行程的MMU設定和主記憶體頁面,在父行程與子行程之間共享,不進行複製,尤其是沒有寫入時複製語意;[8]因此,如果子行程在任何共享頁面中進行修改,不會新增的頁面,並且修改的頁面對父行程同樣可見。因為沒有頁面複製(消耗額外的主記憶體),此技術在純複製環境中使用exec時較普通fork更最佳化。在POSIX中,除非是將立即呼叫exec家族(及其他幾個操作)的函式,其他任何目的會導致未定義行為。[8]使用vfork時,子行程借用而非複製資料結構,所以vfork仍比使用寫時複製語意的fork更快。
System V在System VR4被引入前不支援此系統函式,因為它的主記憶體共享容易出錯:
Vfork 不複製頁表,因此它比 System V 的 fork 實現更快。但子行程在與父行程相同的實體位址空間中執行(直到執行 exec 或 exit),因此可能會覆蓋父行程的資料和棧。如果程式設計師錯誤地使用 vfork,可能會出現危險情況,因此呼叫 vfork 的責任在於程式設計師。System V 方法與 BSD 方法之間的區別是哲學上的:核心應該隱藏其實現的特殊性,不讓使用者知道,還是應該允許精通技術的使用者利用這些實現來更高效地完成邏輯功能?
——Maurice J. Bach[9]
同樣,Linux對vfork的手冊頁面強烈不鼓勵它的使用:[5]
It is rather unfortunate that Linux revived this specter from the past. The BSD man page states: "This system call will be eliminated when proper system sharing mechanisms are implemented. Users should not depend on the memory sharing semantics of vfork() as it will, in that case, be made synonymous to fork(2)."
使用vfork的其他問題包括死結 ,它可能發生在多執行緒程式中,由於與動態連結互動。[10] 作為vfork介面的替代品,POSIX引入了posix_spawn函式家族,它結合了fork和exec的動作。這些函式可以實現為fork的程式庫常式,就像Linux那樣[10],或者就像Solaris那樣為了更好的效能實現為vfork [10][11]。不過,POSIX規範中註明它是「為核心操作設計」,尤其是用於執行在受限硬體和即時系統上的作業系統。[12]
雖然4.4BSD的實現中擺脫了vfork的實現,使vfork做到與fork相同的行為,但它在NetBSD作業系統中因效能原因而恢復。[6]
一些嵌入式作業系統(例如uClinux)只實現vfork,因為它們需要在由於缺乏主記憶體管理單元(MMU)而不可能實現寫時複製的裝置上操作。
Rfork
編輯Plan 9作業系統由Unix的設計者創造,包括fork,但也有一個名為「rfork」的變種,它允許父行程與子行程之間資源的細粒度共享,包括定址空間(除了呼叫棧段,那是每個行程獨有的)、環境變數和檔案系統命名空間;[13]這使它成為了建立行程和其中的執行緒的一個統一介面。[14] 在FreeBSD[15]和IRIX中採用了來自Plan 9的rfork,後者將其更名為「sproc」。[16]
Clone
編輯「clone」(克隆)是Linux核心中的一個系統呼叫,它建立一個可以與其父共享「執行上下文」的子行程。類似FreeBSD的rfork和IRIX的sproc,Linux的clone受到了Plan 9的rfork啟發,並可用於實現執行緒(儘管應用程式的程式設計師通常使用更進階的介面,例如pthreads,實現在clone的頂層)。因為導致太多開銷,出自Plan 9和IRIX的「separate stacks」(單獨堆疊)特性已被省略(據Linus Torvalds)。[16]
其他作業系統中的Fork
編輯在VMS作業系統(1977年)的原始設計中,新行程根據當前一些特定位址進行複製來建立被認為是有風險的。當前行程中的錯誤狀態可能被複製給子行程。因此在這裡使用了行程「產卵」(spawning)之隱喻:新行程的每個組件的主記憶體布局都是重新建立的。spawn後來被微軟的作業系統採用(1993年)。
VM/CMS(OpenExtensions)的POSIX相容組件提供了一個非常有限的fork實現,其中的父行程在子行程執行時被暫停,並且子與父共享同一定址空間。[17]這本質上是一個名為fork的vfork。(注意,這隻適用於CMS客戶機作業系統,其他VM客戶機作業系統如Linux提供標準的fork功能。)
應用程式範例
編輯下列Hello World程式的變種以C語言展示了fork系統呼叫的機理。該程式fork為兩個行程,每個都基於fork系統呼叫的返回值決定它們執行什麼功能。樣板代碼中的標頭檔等已被省略。
int main(void)
{
pid_t pid = fork();
if (pid == -1) {
perror("fork failed");
exit(EXIT_FAILURE);
}
else if (pid == 0) {
printf("Hello from the child process!\n");
_exit(EXIT_SUCCESS);
}
else {
int status;
(void)waitpid(pid, &status, 0);
}
return EXIT_SUCCESS;
}
下面是該程式的解析:
pid_t pid = fork();
呼叫中的第一句是呼叫fork系統呼叫來分割執行為兩個行程。fork的返回值被記錄在類型為pid_t的變數中,其中是POSIX類型的行程識別碼(PID)。
在電腦領域,尤其是Unix及類Unix系統作業系統中,fork是一種建立自身行程副本的操作。它通常是核心實現的一種系統呼叫。Fork是在類Unix作業系統上建立行程的一種主要方法,甚至歷史上曾是唯一方法。
-1錯誤表示fork出錯:沒有新行程被建立。因此要印出一條錯誤訊息。
如果fork成功,那麼現在有兩個行程。兩者都從fork返回時開始執行main函式。為了使行程執行不同的任務,程式必須基於fork的返回值決定其作為子行程或父行程執行某個分支。
else if (pid == 0) {
printf("Hello from the child process!\n");
_exit(EXIT_SUCCESS);
}
Fork操作會為子行程建立一個單獨的定址空間。子行程擁有父行程所有主記憶體段的精確副本。在現代的UNIX變種中,這遵循出自SunOS-4.0的虛擬記憶體模型,根據寫入時複製語意,實體記憶體不需要被實際複製。取而代之的是,兩個行程的虛擬記憶體頁面可能指向實體記憶體中的同一個頁,直至它們寫入該頁時,寫入才會發生。在用fork配合exec來執行新程式的情況下,此最佳化很重要。通常,子行程在停止程式執行前會執行一小組有利於其他程式的操作,它可能用到少量的其父行程的資料結構。
else {
int status;
(void)waitpid(pid, &status, 0);
}
其他行程——即父行程,會收到fork傳來的子行程的行程識別碼,其始終為一個正數。父行程將此識別碼傳遞給 waitpid 系統呼叫來暫停執行,直至子行程退出。當此情況發生後,父行程繼續執行並按return語句的含義退出。
參見
編輯參考資料
編輯- ^ Ken Thompson和Dennis Ritchie. SYS FORK (II) (PDF). UNIX Programmer's Manual. Bell Laboratories. 3 November 1971 [2016年12月2日]. (原始內容 (PDF)存檔於2015年2月3日).
- ^ Ritchie, Dennis M.; Thompson, Ken. The UNIX Time-Sharing System (PDF). Bell System Tech. J. (AT&T). July 1978, 57 (6): 1905–1929 [22 April 2014]. doi:10.1002/j.1538-7305.1978.tb02136.x. (原始內容 (PDF)存檔於2015-06-11).
- ^ 3.0 3.1 單一UNIX®規範第7期,由國際開放標準組織發布 – 系統介面(System Interfaces)參考,
- ^ 單一UNIX®規範第7期,由國際開放標準組織發布 – 系統介面(System Interfaces)參考,
- ^ 5.0 5.1 Linux程式設計師手冊頁 – 系統呼叫(System Calls) –
- ^ 6.0 6.1 NetBSD Documentation: Why implement traditional vfork(). NetBSD Project. [16 October 2013]. (原始內容存檔於2016-12-22).
- ^ vfork(2). UNIX Programmer's Manual, Virtual VAX-11 Version. University of California, Berkeley. December 1979.
- ^ 8.0 8.1 8.2 Template:Man/SUS6 –
- ^ Bach, Maurice J. The Design of The UNIX Operating System. Prentice–Hall. 1986: 291–292.
- ^ 10.0 10.1 10.2 Nakhimovsky, Greg. Minimizing Memory Usage for Creating Application Subprocesses. Oracle Technology Network. Oracle Corporation. 2006 [2016-12-02]. (原始內容存檔於2016-12-03).
- ^ The OpenSolaris posix_spawn() implementation: https://sourceforge.net/p/schillix-on/schillix-on/ci/default/tree/usr/src/lib/libc/port/threads/spawn.c (頁面存檔備份,存於網際網路檔案館)
- ^ 單一UNIX®規範第7期,由國際開放標準組織發布 – 系統介面(System Interfaces)參考,
- ^ Plan 9庫函式和系統呼叫(Library Functions and System Calls)手冊頁 –
- ^ Plan 9庫函式和系統呼叫(Library Functions and System Calls)手冊頁 –
- ^ FreeBSD系統呼叫(System Calls)手冊頁 –
- ^ 16.0 16.1 Torvalds, Linus. The Linux edge. Open Sources: Voices from the Open Source Revolution. O'Reilly. 1999 [2016-12-02]. ISBN 1-56592-582-3. (原始內容存檔於2014-04-21).
- ^ z/VM > z/VM 6.2.0 > Application Programming > z/VM V6R2 OpenExtensions POSIX Conformance Document > POSIX.1 Conformance Document > Section 3. Process Primitives > 3.1 Process Creation and Execution > 3.1.1 Process Creation. IBM. [April 21, 2015]. (原始內容存檔於2019-10-16).