fork函数全解析
從最簡單(基礎)的一個例子說起,應該說是最基礎而不是簡單,下面的這個最基礎的例子其實并不簡單,因為有很多細節(jié)。
我們需要從fork函數的定義開始說起:
第一次看的時候非常的奇怪,一個函數返回兩次?是的,在調用fork后,fork函數后面的所有代碼會執(zhí)行兩遍。下面通過一個例子來解釋fork函數定義的含義。
#include <stdio.h> #include <stdlib.h> #include <unistd.h> /***最基礎的fork例子**/ int main(int argc, char const *argv[]) {pid_t pid;//判斷1if ((pid=fork()) < 0){perror("fork error");}//判斷2else if (pid == 0)//子進程{printf("child getpid()=%d\n", getpid());}//判斷3else if(pid > 0)//父進程{printf("parent getpid()=%d\n", getpid());}return 0; }這是一個最基本的例子。我們先運行一下代碼。
parent getpid()=13725 child getpid()=13726非常的神奇,兩個判斷的代碼都執(zhí)行了。這是非常不可思議的,但fork函數確實實現了這樣的功能。也就是在fork函數后面的代碼都會執(zhí)行2遍。 這就是為什么兩個判斷都會被執(zhí)行的原因。
現在來梳理一下成功fork的執(zhí)行流程
第一步: pid=fork(),如果成功那么pid就有一個非0正值。否則返回-1。
第二步: 因為pid>0,所以進入判斷3。這是在父進程。
第三步: 父進程的代碼執(zhí)行完了,程序又會把fork后面的函數再執(zhí)行一遍,此時pid的值變?yōu)?,所以進入判斷2。
這里要解釋下getpid()函數,先看下他的定義:
man手冊官方定義: DESCRIPTIONThe getpid() function shall return the process ID of the calling process.RETURN VALUEThe getpid() function shall always be successful and no return value is reserved to in‐dicate an error.getpid()獲取調用他的進程的id,如果失敗不會有返回值。也就是說哪個進程調用getpid,就返回這個進程的pid。所以如果你想要獲得子進程的pid,那么只要在判斷2里面調用getpid就可以了。
令人迷惑的pid_t pid變量
還有一個需要解釋的就是我們自己定義的這個pid_t pid變量。這個變量非常具有迷惑性。因為在很多書上都取這個名字,好像這個變量就是進程的pid。這是錯誤的。
這個變量的真正含義應該是return value of the fork(),也就是fork函數的返回值,而且返回值并不一定就是pid,也可能是錯誤值-1。
下面是這個變量的一種錯誤用法,試圖用這個變量來輸出父子進程的pid。
這個一個錯誤的例子,程序的目的是試圖通過pid變量來獲取父子進程的pid。
輸出結果:
這種做法是完全錯誤的,不要這么干!因為這個pid變量的命名實在是太有迷惑性了。判斷2里面的pid會永遠輸出0,而判斷3里面的pid并不是父進程的pid,實際上是子進程的pid。正確的做法是通過第一個例子的getpid函數來獲取。
pid_t pid這個變量的唯一作用就是用來做三個條件判斷。
pid_t pid這個變量的唯一作用就是用來做三個條件判斷。
pid_t pid這個變量的唯一作用就是用來做三個條件判斷。
不要拿他做別的事情。也許取名叫process_status會比較好。
父子進程的調用流程
前面的例子展示了fork最基本的用法。下面通過一個例子來解釋fork函數的調用細節(jié)。
int main(){fork();//fork1fork();//fork2printf("hello\n");return 0; }問printf一共打印了幾次?創(chuàng)建了幾個子進程?
第一個問題非常好回答,執(zhí)行一下程序就知道了。一共是輸出了4次hello字符串。為什么是4次呢?可以做下面的圖分析:
假設我們的main進程pid是1001,注意看左邊的1,2,4進程其實都是main進程1001。進程3,6是同一個進程1002。所有一共有1001,1002,1003,1004四個進程。也就是只要數葉子節(jié)點就行了。其中1個是main進程,其它3個是子進程。有多少個進程就輸出多少次hello字符串。也就是只有4,5,6,7執(zhí)行了printf。
int main(){fork();//fork1fork();//fork2fork();//fork3printf("hello\n");return 0; }如果程序改成這樣,結果是類似的,一共有8個進程,其中一個main進程,7個子進程。如果在程序最后加上sleep函數讓進程一直存在,那么你可以在進程管理器里面查看到對應的進程和pid,如下圖。
進程管理
既然生成了子進程,那么就需要管理這些子進程,那么誰來管呢?當然是誰生成誰負責。這其中有非常多的細節(jié)。看下面這個基本例子。通過getppid(有兩個p)獲取父進程的pid。
int main(){fork();//fork1fork();//fork2printf("ppid is %d\n",getppid());printf("hello\n");return 0; }輸出結果
ppid =4564 hello ppid =26134 hello ppid =26135 ppid =26134 hello hello這個結果順序是隨機的,我們發(fā)現第一個ppid好像有點奇怪,另外三個pid都是差不多的。這個進程實際是main進程,他的parent是shell,因為我們的程序是在shell里面執(zhí)行的。而shell的pid是4564(每次系統(tǒng)啟動可能發(fā)生變化)。這還是比較好理解的。但有時候輸出可能是下面這種情況。
ppid =4564 hello ppid =26570 ppid =26570 hello hello ppid =1 hello最后一個ppid是1,這是怎么回事呢?這是因為父進程在子進程結束之前先結束了。子進程沒有了父進程,變成了孤兒進程。這時候init進程就會把這個孤兒進程收為他的“養(yǎng)子”,而init進程就成了孤兒進程的養(yǎng)父。在Linux系統(tǒng)中,init進程的id為1。這也就是ppid為1的原因。
可見父進程是沒有辦法在自己消亡的時候回收子進程的。
參考:Unix/Linux fork前傳
總結
- 上一篇: 入职两周工作总结
- 下一篇: web逻辑思维题目_逻辑思维训练500题