init進程將系統啟動后,init將成為此后所有進程的祖先,此后的進程都是直接或間接從init進程“復制”而來。完成該“復制”功能的函數有fork()和clone()等。 一個進程(父進程)調用fork()函數后將會把自己復制一份,而這個被復制出來的新進程稱為子進程,就這么簡單地完成了新進程的創建。fork函數幾乎完整地復制了父進程,除了幾個特殊的方面外(至少pid不一樣吧,具體的查看這里有一個完整的列表) fork函數原型:pid_t fork(void); 其包含在 unistd.h 頭文件中,其中pid_t是表示“type of process id”的32位整數, 至於函數的返回值,取決於在哪個進程中來檢測該值,如果是在新創建的進程中,其為0;如果是在父進程中(創建新進程的進程),其為新創建的進程的id; 如果創建失敗,則返回負值。 #include <stdio.h> #include <unistd.h> int main () { printf("app start...\n"); pid_t id = fork(); if (id<0) { printf("error\n"); }else if (id==0) { printf("hi, i'm in new process, my id is %d \n", getpid()); }else { printf("hi, i'm in old process, the return value is %d\n", id); } return 0; } (上面使用了getpid函數,其返回當前進程的pid。) 程序輸出為: app start... hi, i'm in old process, the return value is 5429 hi, i'm in new process, my id is 5429 新進程被創建后將被放入“可執行隊列”而進入“TASK_RUNNING”狀態,並從父進程當前位置獨立地運行。看下面的DEMO: #include <stdio.h> #include <unistd.h> int main () { printf("app start...\n"); int counter = 0; fork(); counter++; printf("the counter value %d\n", counter); return 0; } 輸出為: app start... the counter value 1 the counter value 1 畫個圖就很能容易解釋了: 新進程得到的是父進程的副本,所以,父子進程counter變量不會相互影響 fork函數將復制父進程的地址空間給子進程,但為了提高效率,復制過程並不會真正的進行物理內存的完整復制,而是采用“寫拷貝(copy-on-write)”技術讓父子進程盡可能地長久地共享該物理內存,僅僅是復制內存頁入口地址並標記寫拷貝對應的頁面,當修改真正發生時才真正復制。 再來一個demo: #include <stdio.h> #include <unistd.h> int main () { printf("app start..."); fork(); return 0; } 輸出為: app start...app start... 好奇怪是吧?情況是這樣的: 當你調用printf時,字符串被寫入stdout緩沖區(還沒刷到屏幕上),然后fork,子進程復制了父進程的緩沖區,所以子進程的stdout緩沖區中也包含了“app start ...”這個字符串,然后父子進程各自運行,當他們遇到return語句時,緩沖器會被強制刷新,然后就分別將“app start...”刷到了屏幕上。如果想避免,在fork前,調用fflush強制刷新下緩沖區就可以了,在字符串后面加上“\n”也可以,因為stdout是按行緩沖的。更多的,參考我的一篇博文。 與fork()函數非常類似的還有一個交vfork()的函數,它需要一點exec的知識,可以先閱讀完“運行新程序”后再回頭來看。 我們知道,調用fork函數后,新進程會復制父進程的內存空間並繼續運行,也就是說子進程仍然運行着和父進程相同的程序代碼,在大多數情況下這並非我們的本意,我們一般會fork一個新的進程,然后調用exec族函數(族函數表示由幾個功能類似的函數組成的一組函數)來運行新的程序,exec族函數會用新的進程映像重寫原復制過來的內存空間,比如用新程序的代碼去覆蓋原來代碼段的內容等。很明顯,fork時的復制工作白干了。所以,在這種情形下(fork后立即exec),一個聰明的做法是,fork時不復制父進程的內存空間而是共享(占用)父進程的內存空間以暫時在父進程的內存空間內運行(類似於線程),等到調用exec族函數后便擁有了自己的內存空間,然后脫離父進程獨立運行,而這正是vfork所做的事情,也是vfork和fork的主要區別。 在調用exec前,由於子進程共享了父進程的內存空間,如果子進程篡改父進程數據結果將不可預期,而這是不允許的,同時,父進程會等待子進程調用exec函數后(或子進程調用_exit()退出,比如exec失敗時)才繼續運行。如果不按此約定編寫代碼則可能會引起死鎖或其它不可。預期的情況。 還有一個相對較復雜的方法來創建新進程:clone()。我們知道fork()創建的進程和父進程是獨立的,兩者之間沒有干擾,並且需要專門的“進程間通訊(IPC)”機制來進行溝通。其實,在某些情況之下我們並不希望父子進程之間顯得那么獨立,因為那可能帶來更多的通訊成本和資源復制帶來的浪費。clone()這個系統調用便允許我們選擇性地繼承(共享)父進程資源,比如我們共享父進程的內存空間的話,那么創造出來的新進程實際上就是一個線程了。 clone 函數: int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... ); 第1個參數是一個函數指針,表示新進程要做的工作。 第2個參數指向新進程所需的堆棧空間。 第3個參數,指示子進程如何與父進程共享資源,可選項很多,請參考這里。但一些常用的如下所示: Ÿ CLONE_SIGHAND 子進程與父進程共享相同的信號處理(signal handler)表。 Ÿ CLONE_VM 子進程與父進程運行於相同的內存空間。 Ÿ CLONE_FILES 子進程與父進程共享相同的文件描述符(file descriptor)表 Ÿ CLONE_FS 子進程與父進程共享相同的文件系統,包括root、當前目錄、umask。 Ÿ CLONE_PARENT 創建的子進程的父進程是調用者的父進程,新進程與創建它的進程成了“兄弟”而不是“父子” 另外,為了更好的理解fork()和clone()的關系: fork的實現: do_fork(CLONE_SIGCHLD,...) clone的實現: do_fork(CLONE_VM|CLONE_FS|CLONE_FILES|CLONE_SIGCHLD,...)