2011-11-28 94 views
27

因此,我已經使用了fork(),我知道它的作用。作爲一個初學者,我非常害怕它(但我仍然不完全理解它)。您可以在網上找到的fork()的一般說明是,它複製當前進程並分配不同的PID,父PID,並且該進程將具有不同的地址空間。但是,所有這些都是好的,但是,考慮到這個功能描述,初學者會想「爲什麼這個功能如此重要......我爲什麼要複製我的過程?」。所以我很想知道,最終我發現,您可以通過execve()家族從當前流程中調用其他流程。爲什麼fork()以這種方式工作

我仍然不明白的是爲什麼你必須這樣做?最順理成章的事情是有,你可以調用像

create_process("executable_path+name",params..., more params); 

這將產生新的進程,並開始在主開始運行它(),並返回新的PID功能。

讓我感到困惑的是fork/execve解決方案正在進行可能不需要的工作。如果我的流程使用大量內存會怎麼樣?內核是否複製我的頁面表等。我相信它沒有真正分配真實的內存,除非我已經觸及它。另外,如果我有線程會發生什麼?在我看來,這太亂了。

幾乎所有fork的描述都說它只是複製進程,新進程在fork()調用後開始運行。這確實是發生了什麼,但爲什麼會發生這種情況,爲什麼fork/execve是產生新進程的唯一方法,以及從當前創建新進程的最普通unix方式是什麼?有沒有其他更有效的方法來產生過程?**這不需要複製更多的內存。

This同一個問題線程會談,但我發現它不是中規中矩:

謝謝。

+0

請在http://unix.stackexchange.com/或http://superuser.com/ – rlemon

+7

上發帖爲什麼unix?這是一個編程問題,它屬於堆棧溢出。 – Petr

+0

解讀http://cm.bell-labs.com/who/dmr/hist.html – ninjalj

回答

0

那麼就分頁/虛擬內存而言,有些技術中fork()並不總是複製整個進程的地址空間。在寫分支時,分支進程獲取與其父進程相同的地址空間,然後僅複製被更改的一部分空間(通過任一進程)。

2

看看spawn和朋友。

+2

請記住'spawn'是POSIX,而'fork'是純粹的Unix。並不是說它不能被使用,但是對於一個純粹的Unix體驗,你堅持使用'fork'-'execve' :) –

+0

另外,請注意''spawn'使用'fork'(或'clone')內部。內核中沒有任何東西可以提供所需的功能。這意味着它更加用戶友好和明顯,但無論是開銷(複製頁表和描述符),開銷都是相同的。 – Damon

10

請記住,fork很早就在Unix(早些時候可能在之前)發明的今天看起來很小(例如64K字節的內存)的機器上。

它通過最基本的可能行動提供基本機制而不是政策的整體(原始)理念更爲相位。

fork只是創建一個新的過程,最簡單的思維方式就是克隆當前過程。所以語義是非常自然的,它是最簡單的機制。

其他系統調用(execve)負責加載一個新的可執行文件,等..

將它們分開(並且還提供pipedup2系統調用)給出了很大的靈活性。

並且在當前的系統上,fork被非常有效地實現(通過寫入分頁技術的懶惰拷貝)。衆所周知,fork機制使得Unix進程的創建速度非常快(例如,比Windows或VAX/VMS更快,其中系統調用的創建過程與您提出的更類似)。

還有vfork系統調用,我不打擾使用。

而且posix_spawn API比fork或單獨execve要複雜得多,所以說明fork更簡單...

+0

所以,我聽說過產卵,但是我想知道,哪些創建一個新的進程方法可以做到大型備受推崇的linux應用程序使用(比如Gimp,openoffice,gnome等)。我認爲他們中至少有一些人需要這樣做。 – user1068779

+0

GTK在'fork'系統調用之上提供了(在Glib庫中)調用,就像http://developer.gnome.org/glib/unstable/glib-Spawning-Processes.html –

+0

我認爲這最終只是清晰的回答只是說「記住fork很早就發明了Unix」。儘管沒有人證實,但我相信可以實現更有效的新函數,除了額外的內存/屬性克隆之外,它只會執行「fork()」的功能,僅用於啓動一個新的獨立進程,該進程幾乎不會共享與其父母。 – Petr

2

fork創建通過複製當前進程的新工藝,它執行時拷貝寫。這意味着新進程的內存將與父進程共享,直到它被更改。當內存改變時,內存被複制以確保每個進程都有自己的內存有效副本。在fork之後執行execve時,沒有內存副本,因爲新進程只是加載一個新的可執行文件,因此新的內存空間。

至於爲什麼要這樣做的問題,我不確定,但它似乎是Unix方式的一部分 - 做一件好事。該操作不是創建一個創建新進程並加載新可執行文件的函數,而是分成兩個函數。這給了開發者最大的靈活性。儘管我自己還沒有使用過任何一種功能......

+0

它由MMU通過標記頁面COW完成。 Windows使用相同的機制來啓動新進程。系統調用底層fork(clone)與系統調用底層CreateProcess(ZwCreateProcess)非常相似,實際上你可以在ZwCreateProcess之上實現fork。 –

+0

http://doxygen.scilab.org/5.4/d0/d8f/forkWindows_8c_source.html –

1

假設底層實現使用寫入時複製尋址系統,fork()可以用很少的內存分配來實現。通過該優化來實現create_process函數是不可能的。

5

「fork()」是一個傑出的創新,它解決了單個API的全部問題。它是在多處理不常見的時候發明的(在我們今天使用的多處理之前,這種處理大約有20年)。

+0

Err,自1950年代以來一直在進行多處理。 – EJP

+0

輝煌?我會說一個愚蠢的(可以說)解決產生新過程的特定小子集 - 克隆現有的一個。在大多數情況下,你只需要啓動一個小幫手程序爲你做一些小工作,你得到的只是'叉子'?哎喲!太蹩腳了,從來不喜歡它。在許多情況下克隆確實有意義,但不在這裏,請相信我。 – Sergey

0

使用fork的主要原因是執行速度。

如果您按照您的建議使用一組參數啓動了流程的新副本,則新流程需要解析這些參數並重復父流程所完成的大部分處理。使用「fork()」,父進程堆棧的完整副本立即可用於孩子,並且所有內容都被解析並格式化。

此外,在大多數情況下,程序將是「.so」或「.dll」,因此可執行指令將不會被複制,只有堆棧和堆存儲將被複制。

2

正如其他人所說,fork實施得非常快,所以這不成問題。但爲什麼不是像create_process()這樣的功能?答案是:靈活性的簡單性。 全部 unix中的系統調用被編程爲只做一件事。像create_process這樣的函數可以做兩件事:創建一個進程並在其中加載一個二進制文件。

每當您嘗試並行化事物時,都可以使用線程 - 或使用fork()打開的進程。在大多數情況下,您通過fork()打開n進程,然後使用IPC機制在這些進程之間進行通信和同步。一些IPC堅持在全球空間中存在變數。

實施例與管道:

  • 創建管
  • 叉它繼承的管狀手柄
  • 孩子關閉輸入側
  • 父關閉輸出側

Impossible without fork() ...

另一個重要的事實是整個Unix API只有一些功能。每個程序員都可以輕鬆記住使用過的函數。但是請參閱Windows API:數以千計的功能是人們無法想起的。

所以總結起來,再說一遍:簡單靈活

+1

雖然我同意你fork()可以做的事情,「create_process()」我不能強烈反對,即使fork()被實現爲非常快,可以使它比一個函數,會做得更快除了內存複製外,fork()還是一樣。這總會節省一堆CPU指令,因此速度會更快。 – Petr

+0

@Petr:加載一個新進程主要是通過比較使'fork()'的開銷變得微不足道。 – ninjalj

+0

克隆是由MMU通過標記寫入時拷貝來完成的。它不吃任何CPU週期。實際上,產生線程是通過用於在Unix和Linux上實現fork的相同的系統調用來完成的,分支並沒有比產生線程更高的開銷。衆所周知,Windows也可以通過分叉來啓動一個新進程,雖然它被稱爲ZwCreateProcess並且隱藏在ntdll.dll中。 CreateProcess與fork的開銷來自必須清空並重新初始化克隆以啓動一個空進程。 –

1

所以,你主要關注的是:叉()導致不必要的內存複製。

答案是:不,沒有記憶浪費。總之,fork()是在內存資源非常有限的情況下誕生的,所以沒有人會考慮像這樣浪費它。

儘管每個進程都有自己的地址空間,但物理內存頁面和進程的虛擬內存頁面之間沒有一對一的映射關係。相反,可以將一頁物理內存映射到多個虛擬頁面(有關更多詳細信息,請搜索CPU TLB)。

因此,當您使用fork()創建新進程時,它們的虛擬地址空間被映射到相同的物理內存頁面。沒有內存拷貝是必需的。這也意味着沒有重複使用的庫,因爲它們的代碼段標記爲只讀。

實際內存複製僅在父進程或子進程修改某個內存頁面時發生。在這種情況下,新的物理內存頁面被分配並映射到修改頁面的進程的虛擬地址空間。

+0

CPU浪費怎麼樣?當流程的某些屬性被複制到新流程時,是不是這個操作只是一堆額外的指令,不需要執行,因爲我知道我會拋棄它們呢?我的意思是fork()製作一個進程的副本。它複製了許多後來被覆蓋並且消耗了一些不需要消耗的CPU的屬性,或者不是? – Petr

+0

沒有太多的屬性會被過度考慮。這樣的開銷是可接受的 –

1

這是一個很好的問題。我不得不在源代碼中進行一些挖掘,看看究竟發生了什麼。

fork()通過複製調用過程來創建一個新進程。

在Linux下,fork()是使用寫時複製頁面實現的,因此唯一的代價是複製父頁表所需的時間和內存,併爲子項創建獨特的任務結構。

新進程稱爲子進程,與調用進程完全相同(稱爲父進程)。不包括:

  • 孩子有其自己唯一的進程ID,並且此PID不匹配 任何現有進程組的ID。
  • 孩子的父進程ID與父進程ID相同。
  • 孩子不繼承父母的記憶鎖。
  • 處理資源利用率和CPU時間計數器在子中重置爲零 。
  • 孩子的待決信號集最初是空的。
  • 該子項不會從其父項繼承信號量調整。
  • 孩子不從其父母繼承記錄鎖。
  • 孩子不從其父母繼承定時器。
  • 該子項不從其父項繼承未完成的異步I/O操作 ,也不從其父項繼承任何異步I/O上下文。

結論:叉

主要目的是分裂的父母進程的任務分成更小的子任務,而不會影響父母的唯一的任務結構。這就是叉克隆現有流程的原因。

來源:

http://www.quora.com/Linux-Kernel/After-a-fork-where-exactly-does-the-childs-execution-start http://learnlinuxconcepts.blogspot.in/2014/03/process-management.html

+1

+1用於挖掘fork()的工作方式。但是,現在還沒有比克隆現有技術更好的方法來開始新的過程嗎?我只是沒有看到這一點。如果你想開始新的,單獨的過程,爲什麼你想先克隆現有的過程? – Petr

+0

爲了迴應您的評論,我已對我的回答進行了更改。 –

+0

如果你產生一個新的過程,你將不得不從main()開始並設置所有東西。線程通常也是這種情況,線程從自己的threadproc開始,後者必須解碼void指針提供的數據(它唯一的參數)。用叉子不需要初始化任何東西。 –

17

這是由於歷史的原因。截至https://www.bell-labs.com/usr/dmr/www/hist.html解釋,UNIX很早就沒既無fork()也不exec*(),殼執行的命令的樣子:

  • 做必要的初始化(開標準輸入/輸出)。
  • 閱讀命令行。
  • 打開命令,加載一些引導代碼並跳轉到它。
  • 引導程序代碼讀取打開的命令(覆蓋shell的內存)並跳轉到它。
  • 一旦命令結束,它會調用exit(),然後通過重新加載外殼(覆蓋命令的內存),並跳轉到它,回去工作步驟1

從那裏,fork()是一個易於添加(27條裝配線),重用其餘的代碼。

在Unix的發展階段,執行命令變成了:

  • 閱讀的命令行。
  • fork()一個子進程,並等待它(通過發送一條消息給它)。
  • 子進程加載命令(覆蓋孩子的記憶),並跳轉到它。
  • 一旦命令結束,它會調用exit(),現在更簡單了。它只是清理了它的流程條目,並放棄了控制權。

最初,fork()沒有做寫上覆制。由於這使得fork()非常昂貴,並且fork()經常用於產生新的進程(因此經常緊接着是exec*()),fork()的優化版本出現了:vfork()它共享父和子之間的內存。在那些vfork()的實施中,父母將被暫停,直到兒童exec*()'ed或_exit()'編輯,從而放棄父母的記憶。後來,fork()被優化,以便在寫入時進行復制,僅當父母和子女之間的差異開始時才複製內存頁面。後來又看到了對MMU系統端口的重新興趣(例如:如果你有一個ADSL路由器,它可能在一個MMU MIPS CPU上運行Linux),它不能進行COW優化,而且不能支持fork()'ed有效地處理。

fork()效率低下的其它來源,它最初可以複製地址空間(和頁表)的母公司,這可能使運行從龐大的計劃短期課程相對緩慢,或可能使OS否認fork()思維有可能沒有足夠的內存(要解決這個問題,可以增加交換空間,或者更改操作系統的內存過量使用設置)。作爲一個軼事,Java 7使用vfork()/posix_spawn()來避免這些問題。

另一方面,fork()使創建幾個相同過程的實例非常高效:例如:一個Web服務器可能有幾個相同的進程服務於不同的客戶端。其他平臺更傾向於使用線程,因爲產生不同進程的成本比重複當前進程的成本要大得多,這可能比產生新線程稍微大一點。這是不幸的,因爲共享的所有線程都是錯誤的誘因。

+0

在所有的答案中,這看起來應該是這裏唯一的答案:^) –

+0

鏈接已經死亡。任何正在尋找該文件的人: 標題:「Unix分時系統的演變」 作者:「Dennis M. Ritchie」 – Sidervs

0

你可以想到這有點像在Windows中產生一個線程,除了進程不共享除文件句柄,共享內存和其他明確可繼承的東西之外的資源。因此,如果您有新任務要做,則可以在克隆負責新任務時分叉和一個進程繼續其原始作業。

如果您想要執行並行計算,您的進程可以將其自身分割爲循環上方的多個克隆。每個克隆都會執行計算的一個子集,而父級則等待它們完成。操作系統確保它們可以並行運行。在Windows中,您可以需要使用OpenMP才能獲得相同的可表達性。

如果您需要閱讀或寫入文件,但無法等待,您可以分叉並且您的克隆執行I/O,同時繼續執行原始任務。在Windows上,你可能會考慮產生線程或者在很多情況下使用重疊的I/O,在Unix中一個簡單的fork就可以完成。特別是,進程並不像線程那樣具有相同的可調度性問題。這在32位系統上尤其如此。只是分叉比處理錯綜複雜的I/O更加方便。雖然進程擁有自己的內存空間,但線程仍處於相同的狀態,因此對於應該考慮放入32位進程的線程數有限制。使用fork製作32位服務器應用程序非常簡單,而使用線程製作32位服務器應用程序可能是一場噩夢。所以,如果你在32位Windows上編程,你將不得不求助於其他解決方案,如重疊I/O,這是一個PITA的工作。

因爲進程不會像線程一樣共享全局資源(例如malloc中的全局鎖),所以這是更具可擴展性的。雖然線程經常會彼此阻塞,但進程獨立運行。

在Unix上,因爲fork爲您的進程創建寫時複製克隆,所以它不會比在Windows中產生新線程更重量級。

如果您處理的是解釋型語言,通常有一個全局解釋器鎖(Python,Ruby,PHP ...),那麼賦予您fork功能的操作系統是必不可少的。否則,您利用多個處理器的能力將受到更多限制。

另一件事就是在這裏有一個安全問題。進程不共享內存空間,不能混淆每個其他內部細節。這導致更高的穩定性。如果您有一臺使用線程的服務器,則一個線程中的崩潰將導致整個服務器應用程序崩潰。分叉崩潰只會取消分叉克隆。這也使錯誤處理更加簡化。分叉克隆通常已經足夠,因爲它對原始應用程序沒有任何影響。

還有一個安全問題。如果分叉進程注入惡意代碼,則不會進一步影響父級。現代的網頁瀏覽器利用這個例如保護一個標籤與另一個標籤。如果您有叉式系統調用,所有這些都可以方便編程。

-1

其他的答案已經做了解釋爲什麼fork比它似乎更快了,怎麼了最初來存在的一個好工作。但是,保持fork + exec組合也是一個很好的例子,這就是它提供的靈活性。

通常情況下,產卵一個子進程的時候,也有執行兒童前採取的準備步驟。例如:您可以使用pipe(讀取器和寫入器)創建一對管道,然後將子進程的stdoutstderr重定向到寫入器,或者將讀取器用作進程的stdin或任何其他文件描述符。或者,您可能需要設置環境變量(但僅限於小孩)。或者使用setrlimit設置資源限制來限制孩子可以使用的資源量(不限制父母)。或用setuid/seteuid更改用戶(不更改父級)。等等等等

當然,你可以做到這一切與一個假想create_process功能。但是,這是一個需要覆蓋的東西!爲什麼不提供運行的靈活性fork,做任何你想設置的孩子,然後運行exec

此外,有時你實際上並不需要一個子進程可言。如果您當前的程序(或腳本)僅用於執行這些設置步驟中的一部分,並且它將要執行的最後一件事是運行新流程,那麼爲什麼有兩個流程呢?您可以使用exec來替換當前進程,釋放自己的內存和PID。

的分岔還允許有關只讀數據集一些有用的行爲。例如,你可以有一個父進程來收集和索引大量的數據,然後派生出子工來根據這些數據執行遍歷和計算。父母不需要將它保存在任何地方,孩子們不需要閱讀它,並且不需要對共享內存做任何複雜的工作。 (例如:有些數據庫使用這種方式將子內存數據庫轉儲到磁盤,而不會阻塞父進程。)

上面還包括任何讀取配置,數據庫,和/或一組代碼文件,然後繼續分離子進程以處理請求並更好地使用多核CPU。這包括web服務器,但也包括web(或其他)應用程序本身,特別是如果這些應用程序只是在閱讀和/或編譯更高級代碼時花費大量啓動時間。

的分岔,也可以來管理內存,並避免碎片,特別是對於使用自動內存管理(垃圾收集),並沒有對他們的記憶佈局直接控制高級語言的有效途徑。如果您的進程短暫地需要大量內存用於特定操作,則可以進行分叉並執行該操作,然後退出,釋放剛剛分配的所有內存。相比之下,如果您在父級執行操作,則可能會在整個過程中持續存在大量內存碎片 - 對於長時間運行的進程來說不是很好。

最後:一旦你接受forkexec兩個都有自己的用法,相互獨立,問題就變成了 - 爲什麼還要創建一個單獨的函數來結合這兩個函數呢?據說Unix的理念是讓它的工具「做一件事,做得很好」。通過將forkexec作爲單獨的構建塊 - 並使每個構建塊儘可能快速和高效 - 它們允許比單個功能更具靈活性。

相關問題