fork(),vfork(),clone()的區別

一、fork()
1、fork()調用一次返回兩次
返回值:成功(父進程:返回子進程的PID,子進程:返回0);
失敗(父進程返回-1)
fork創建一個進程時,子進程只是完全複製父進程的資源,複製出來的子進程有自己的task_struct結構和pid,但卻複製父進程其它所有的資源。例如,要是父進程打開了五個文件,那麼子進程也有五個打開的文件,而且這些文件的當前讀寫指針也停在相同的地方。這樣得到的子進程獨立於父進程, 具有良好的併發性,但是二者之間的通訊需要通過專門的通訊機制,如:pipe,共享內存等機制, 另外通過fork創建子進程,需要將上面描述的每種資源都複製一個副本。

fork返回後,子進程和父進程都從調用fork函數的下一條語句開始執行。
這裏寫圖片描述
這裏寫圖片描述
父子進程不共享一段地址空間,修改子進程,父進程的內容並不會受影響。
這裏寫圖片描述
這裏寫圖片描述

fork是一個開銷十分大的系統調用,這些開銷並不是所有的情況下都是必須的,比如某進程fork出一個子進程後,其子進程僅僅是爲了調用exec執行另一個可執行文件,那麼在fork過程中對於虛存空間的複製將是一個多餘的過程。
現在Linux中是採取了copy-on-write(COW寫時複製)技術,爲了降低開銷,fork最初並不會真的產生兩個不同的拷貝,因爲在那個時候,大量的數據其實完全是一樣的。寫時複製是在推遲真正的數據拷貝。若後來確實發生了寫入,那意味着parent和child的數據不一致了,於是產生複製動作,每個進程拿到屬於自己的那一份,這樣就可以降低系統調用的開銷。

寫時拷貝可以推遲甚至免除拷貝數據,內核此時並不複製整個進程地址空間,而是讓父進程和子進程共享同一個拷貝。只有在需要寫入的時候,數據纔會被複制,從而使各個進程擁有各自的拷貝。也就是說,資源的複製只有在需要寫入的時候才進行,在此之前,只是以只讀方式共享。這種技術使地址空間上的頁的拷貝被推遲到實際發生寫入的時候。在頁根本不會被寫入的情況下,它們就無需複製了(fork()之後立即調用exec())。fork()的實際開銷就是複製父進程的頁表以及給子進程創建唯一的進程描述符。

2、fork採用了這種寫時複製的機制,那麼fork出來子進程後,理論上子進程和父進程那個先調度呢?
fork之後內核一般會通過將子進程放在隊列的前面,以讓子進程先執行,因爲很多情況下子進程要馬上執行exec,會清空棧、堆,這些和父進程共享的空間,加載新的代碼段。。這就避免了父進程“寫時複製”拷貝共享頁面的機會。如果父進程先調度很可能寫共享頁面,而子進程什麼也沒做,會產生“寫時複製”的無用功。所以,一般子進程先調度。避免因無意義的複製而造成效率的下降。
3、父子進程在同一個CPU或不同CPU上執行順序???
(1)如果父子進程運行在同一個CPU上,並且不能共享同一組頁表(CLONE_VM標誌被清0).那麼,就把子進程插入父進程運行隊列.
(2)並且子進程插在父進程之前.這樣做的目的是:如果子進程在創建之後執行新程序,就可以避免寫時複製機制執行不必要時頁面複製.
(3)否則,如果運行在不同的CPU上,或者父子進程共享同一組頁表.就把子進程插入父進程運行隊列的隊尾.

二、vfork()
1、爲什麼要提出vfork?
因爲以前的fork當它創建一個子進程時,將會創建一個新的地址空間,並且拷貝父進程的資源,然後將會有兩種行爲:

(1).執行從父進程那裏拷貝過來的代碼段
(2).調用一個exec執行一個新的代碼段

當進程調用exec函數時,一個新程序替換了當前進程的正文,數據,堆和棧段。這樣,前面的拷貝工作就是白費力氣了,這種情況下,聰明的人就想出了vfork。vfork並不複製父進程的進程環境,子進程在父進程的地址空間中運行,所以子進程不能進行寫操作,並且在兒子“霸佔”着老子的房子時候,要委屈老子一下了,讓他在外面歇着(阻塞),一旦兒子執行了exec或者exit後,相當於兒子買了自己的房子了,這時候就相當於分家了。

因此,如果創建子進程是爲了調用exec執行一個新的程序的時候,就應該使用vfork(但是現在fork有了寫時拷貝,唯一的代價僅僅是複製父進程頁表的代價,所以vfork的意義就不大了)

2、vfork()保證子進程先運行,在它調用exec或exit之後父進程纔可能被調度運行。如果在調用這兩個函數之前子進程依賴於父進程的進一步動作,則會導致死鎖。
3、vfork系統調用不同於fork,用vfork創建的子進程與父進程共享地址空間,也就是說子進程完全運行在父進程的地址空間上,如果這時子進程修改了某個變量,這將影響到父進程。
這裏寫圖片描述
這裏寫圖片描述
4、vfork()不需要拷貝父進程的進程環境,在子進程沒有調用exec或exit之前,子進程與父進程共享進程環境,相當於線程的概念,此時父進程阻塞等待。如果vfork創建的子進程沒有調用exit(),子進程將不能結束。

三、clone()
1、 clone是Linux爲創建線程設計的(雖然也可以用clone創建進程)。所以可以說clone是fork的升級版本,不僅可以創建進程或者線程,還可以指定創建新的命名空間(namespace)、有選擇的繼承父進程的內存、甚至可以將創建出來的進程變成父進程的兄弟進程等等。
2、clone和fork的調用方式也很不相同,clone調用需要傳入一個函數,該函數在子進程中執行。

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);  
02.下面是flags可以取的值  
03.標誌                    含義  
04.CLONE_PARENT   創建的子進程的父進程是調用者的父進程,新進程與創建它的進程成了“兄弟”而不是“父子”  
05.CLONE_FS           子進程與父進程共享相同的文件系統,包括root、當前目錄、umask  
06.CLONE_FILES      子進程與父進程共享相同的文件描述符(file descriptor)表  
07.CLONE_NEWNS   在新的namespace啓動子進程,namespace描述了進程的文件hierarchy  
08.CLONE_SIGHAND   子進程與父進程共享相同的信號處理(signal handler)表  
09.CLONE_PTRACE   若父進程被trace,子進程也被trace  
10.CLONE_VFORK     父進程被掛起,直至子進程釋放虛擬內存資源  
11.CLONE_VM           子進程與父進程運行於相同的內存空間  
12.CLONE_PID          子進程在創建時PID與父進程一致  
13.CLONE_THREAD    Linux 2.4中增加以支持POSIX線程標準,子進程與父進程共享相同的線程羣 

這裏寫圖片描述

這裏寫圖片描述
這裏寫圖片描述
創建了一個線程(因爲子進程共享了父進程的虛存空間,沒有自己獨立的虛存空間不能稱其爲進程)。父進程被掛起,子線程釋放虛存資源後再繼續執行。

四、總結
1、 創建新進程(線程)
(1)fork創建進程;
(2) vfork創建出來的不是真正意義上的進程,而是一個線程(因爲它沒有獨立的內存資源)
(3) clone是Linux爲創建線程設計的(也可以創建進程)
2、執行順序:
(1)fork之後,父子進程的運行順序是不定的,它取決於內核的調度算法。
(2)vfork調用中,子進程先運行,父進程掛起,直到子進程調用了exec或exit之後,父子進程的執行次序纔不再有限制。
(3)clone中由標誌CLONE_VFORK來決定子進程在執行時父進程是阻塞還是運行,若沒有設置該標誌,則父子進程同時運行,設置了該標誌,則父進程掛起,直到子進程結束爲止。
3、fork()是全部複製,vfork()是共享內存,而clone()是則可以將父進程資源有選擇地複製給子進程,而沒有複製的數據結構則通過指針的複製讓子進程共享,具體要複製哪些資源給子進程,由參數列表中的clone_flags決決定。
4、調用參數
fork和vfork在調用時不需要參數,而clone調用需要傳入一個函數。
5、clone和fork最大的不同在於clone不再複製父進程的棧空間,而是自己創建一個新的。(void *child_stack)也就是第二個參數,需要分配棧指針的空間大小,所以它不再是繼承或複製,而是全新創造。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章