本文是涉及到fork,vfork,exec和進程通信,父子進程數據共享這幾個方面的討論。
第一點,
Linux中,創建進程的方式,只有一種,那就是調用fork(或者vfork)。 當然,系統的交換進程,init進程除外,它們是操作系統自舉時用特殊方式創建的最初的進程。
第二點,
舉個例子,父進程A 創建子進程B 後,進程B 就擁有了A 的所有數據(包括父進程的數據空間、堆和棧)的相同副本,並且共享代碼片段(正文段)。父子進程的運行路線僅靠fork的返回值來區分。子進程從調用fork 之後的代碼行繼續執行,這點很重要。它意味著,之前的代碼只有父進程會執行,fork之後的代碼靠其返回值導向不同的流向。當然,如果fork 外部嵌套了結構控制語句,情況可能會更麻煩一點。這種情況下,父子進程可能仍有機會在跳出fork語句塊之後,執行一段相同的代碼。
第三點,
我們之所以用fork 調用,大多數情況是在子進程中調用exec 函數來啟動另一個新的程序。即運行另一個可能是其他人所編寫的程序(含有main函數的完整程序)。這時,子進程B 若調用了exec,那麼就意味著進程B 被kill了,進程B 從進程A 那裡繼承的代碼從調用exec 那行開始都無效,所有的數據也被清空。所以,在調用exec 後所編寫的代碼都是毫無意義的。唯一保留的是B的pid號,exec 所調用的那個程序會繼承B 的pid 繼續執行,直到結束。
第四點,
有了上面的這些概念,下面就開始說說vfork和父子進程的數據共享。這是我現在所關心的。首先,vfork產生的子進程共享父進程的所有地址空間(無論是正文段還是數據段),這就是程序中我使用了vfork的重要原因。因為,你要知道,若不是這樣,父子進程之間的通信和數據共享的代碼就又夠我折騰一陣子了。不用vfork的話,我可能就不得不借助於管道通信,共享內存,乃至多線程的編寫來解決這個問題。另外,vfork和fork之間的另一個區別是:vfork保證子進程先運行,在它調用exec或exit之後父進程才可能被調度運行。有一點要提醒的是,網上有人說,在Linux中vfork已經喪失了其功能,變得和fork功能一樣,還好,在我實驗的RedHat 和Debian版本中vfork 還保留著共享父進程數據的能力。(實際上,UNIX系統都還保留著兩者的不同之處)
這篇文章是自己在編寫一個Linux小程序時,遇到問題有感而發的。
vfork用於創建一個新進程,而該新進程的目的是exec一個新進程,vfork和fork一樣都創建一個子進程,但是它並不將父進程的地址空間完全復制到子進程中,不會復制頁表。因為子進程會立即調用exec,於是也就不會存放該地址空間。不過在子進程中調用exec或exit之前,他在父進程的空間中運行。
為什麼會有vfork,因為以前的fork當它創建一個子進程時,將會創建一個新的地址空間,並且拷貝父進程的資源,而往往在子進程中會執行exec調用,這樣,前面的拷貝工作就是白費力氣了,這種情況下,聰明的人就想出了vfork,它產生的子進程剛開始暫時與父進程共享地址空間(其實就是線程的概念了),因為這時候子進程在父進程的地址空間中運行,所以子進程不能進行寫操作,並且在兒子“霸占”著老子的房子時候,要委屈老子一下了,讓他在外面歇著(阻塞),一旦兒子執行了exec或者exit後,相當於兒子買了自己的房子了,這時候就相當於分家了。
vfork和fork之間的另一個區別是: vfork保證子進程先運行,在她調用exec或exit之後父進程才可能被調度運行。如果在調用這兩個函數之前子進程依賴於父進程的進一步動作,則會導致死鎖。
由此可見,這個系統調用是用來啟動一個新的應用程序。其次,子進程在vfork()返回後直接運行在父進程的棧空間,並使用父進程的內存和數據。這意味著子進程可能破壞父進程的數據結構或棧,造成失敗。
為了避免這些問題,需要確保一旦調用vfork(),子進程就不從當前的棧框架中返回,並且如果子進程改變了父進程的數據結構就不能調用exit函數。子進程還必須避免改變全局數據結構或全局變量中的任何信息,因為這些改變都有可能使父進程不能繼續。
通常,如果應用程序不是在fork()之後立即調用exec(),就有必要在fork()被替換成vfork()之前做仔細的檢查。
用fork函數創建子進程後,子進程往往要調用一種exec函數以執行另一個程序,當進程調用一種exec函數時,該進程完全由新程序代換,而新程序則從其main函數開始執行,因為調用exec並不創建新進程,所以前後的進程id 並未改變,exec只是用另一個新程序替換了當前進程的正文,數據,堆和棧段。