fork (系統調用)

計算機領域中,尤其是Unix類Unix系統操作系統中,fork(進程複製)是一種創建自身行程副本的操作。它通常是內核實現的一種系統調用。Fork是類Unix操作系統上創建進程的一種主要方法,甚至歷史上是唯一方法。

概述

在多任務操作系統中,行程(運行的程序)需要一種方法來創建新進程,例如運行其他程序。Fork及其變種在類Unix系統中通常是這樣做的唯一方式。如果進程需要啟動另一個程序的可執行文件,它需要先Fork來創建一個自身的副本。然後由該副本即「子進程」調用exec英語Exec (computing)系統調用,用其他程序覆蓋自身:停止執行自己之前的程序並執行其他程序。

Fork操作會為子進程創建一個單獨的定址空間。子進程擁有父進程所有內存段的精確副本。在現代的UNIX變種中,這遵循出自SunOS-4.0的虛擬內存模型,根據寫入時複製語義,物理內存不需要被實際複製。取而代之的是,兩個進程的虛擬內存頁面英語Virtual memory pages可能指向物理內存中的同一個頁,直到它們寫入該頁時,寫入才會發生。在用fork配合exec來執行新程序的情況下,此優化很重要。通常來說,子進程在停止程序運行前會執行一小組有利於其他程序的操作,它可能用到少量的其父進程的數據結構

當一個進程調用fork時,它被認為是父進程,新創建的進程是它的孩子(子進程)。在fork之後,兩個進程還運行着相同的程序,都像是調用了該系統調用一般恢復執行。然後它們可以檢查調用的返回值英語Return value確定其狀態:是父進程還是子進程,以及據此行事。

fork系統調用在第一個版本的Unix就已存在[1],它借用於更早的GENIE英語Project 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英語Exec (computing)」家族中的一項)。子進程借用父進程的MMU設置和內存頁面,在父進程與子進程之間共享,不進行複製,尤其是沒有寫入時複製語義;[8]因此,如果子進程在任何共享頁面中進行修改,不會創建新的頁面,並且修改的頁面對父進程同樣可見。因為沒有頁面複製(消耗額外的內存),此技術在純複製環境中使用exec時較普通fork更優化。在POSIX中,除非是將立即調用exec家族(及其他幾個操作)的函數,其他任何目的會導致未定義行為[8]使用vfork時,子進程借用而非複製數據結構,所以vfork仍比使用寫時複製語義的fork更快。

System V在System VR4被引入前不支持此系統函數,因為它的內存共享容易出錯:

Vfork 不複製頁表,因此它比 System V 的 fork 實現更快。但子進程在與父進程相同的物理地址空間中執行(直到執行 execexit),因此可能會覆蓋父進程的數據和棧。如果程序員錯誤地使用 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英語Spawn (computing)後來被微軟的操作系統採用(1993年)。

VM/CMS(OpenExtensions)的POSIX兼容組件提供了一個非常有限的fork實現,其中的父進程在子進程執行時被暫停,並且子與父共享同一地址空間。[17]這本質上是一個名為fork的vfork。(注意,這隻適用於CMS客戶機操作系統,其他VM客戶機操作系統如Linux提供標準的fork功能。)

應用程序範例

下列Hello World程序的變種以C語言展示了fork系統調用的機理。該程序fork為兩個進程,每個都基於fork系統調用的返回值決定它們執行什麼功能。樣板代碼英語Boilerplate code中的頭文件等已被省略。

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的虛擬內存模型,根據寫入時複製語義,物理內存不需要被實際複製。取而代之的是,兩個進程的虛擬內存頁面英語virtual memory pages可能指向物理內存中的同一個頁,直至它們寫入該頁時,寫入才會發生。在用fork配合exec來執行新程序的情況下,此優化很重要。通常,子進程在停止程序運行前會執行一小組有利於其他程序的操作,它可能用到少量的其父進程的數據結構

   else {
      int status;
      (void)waitpid(pid, &status, 0);
   }

其他進程——即父進程,會收到fork傳來的子進程的進程標識符,其始終為一個正數。父進程將此標識符傳遞給 waitpid 系統調用來暫停執行,直至子進程退出。當此情況發生後,父進程繼續執行並按return語句的含義退出。

參見

參考資料

  1. ^ Ken ThompsonDennis Ritchie. SYS FORK (II) (PDF). UNIX Programmer's Manual. Bell Laboratories. 3 November 1971 [2016年12月2日]. (原始內容 (PDF)存檔於2015年2月3日). 
  2. ^ 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. ^ 3.0 3.1 fork – 系統界面(System Interfaces)參考,單一UNIX®規範第7期,由國際開放標準組織發布
  4. ^ pipe – 系統界面(System Interfaces)參考,單一UNIX®規範第7期,由國際開放標準組織發布
  5. ^ 5.0 5.1 vfork(2) – Linux程序員手冊頁 – 系統調用(System Calls)
  6. ^ 6.0 6.1 NetBSD Documentation: Why implement traditional vfork(). NetBSD Project. [16 October 2013]. (原始內容存檔於2016-12-22). 
  7. ^ vfork(2). UNIX Programmer's Manual, Virtual VAX-11 Version. University of California, Berkeley. December 1979. 
  8. ^ 8.0 8.1 8.2 [[[:Template:Man/SUS6]] vfork(Template:Man/SUS6)] – Template:Man/SUS6
  9. ^ Bach, Maurice J. The Design of The UNIX Operating System. Prentice–Hall. 1986: 291–292. 
  10. ^ 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). 
  11. ^ The OpenSolaris posix_spawn() implementation: https://sourceforge.net/p/schillix-on/schillix-on/ci/default/tree/usr/src/lib/libc/port/threads/spawn.c頁面存檔備份,存於網際網路檔案館
  12. ^ posix_spawn – 系統界面(System Interfaces)參考,單一UNIX®規範第7期,由國際開放標準組織發布
  13. ^ fork(2) – Plan 9庫函數和系統調用(Library Functions and System Calls)手冊頁
  14. ^ intro(2) – Plan 9庫函數和系統調用(Library Functions and System Calls)手冊頁
  15. ^ rfork(2) – FreeBSD系統調用(System Calls)手冊頁
  16. ^ 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). 
  17. ^ 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).