深入淺出--UNIX多進程編程之fork()函數

http://blog.csdn.net/wallwind/article/details/6996004

0前言

上週都在看都在學習unix環境高級編程的第八章——進程控制。也就是這一章中,讓我理解了unix中一些進程的原理。下面我就主要按照進程中最重要的三個函數來進行講解。讓大家通過閱讀這一篇文章徹底明白進程這點事。希望對大家有所幫助。

1進程環境

         在學習進程之前,一定要了解一下unix的進程環境。系統如何對進程終止,和一個程序啓動終止,程序運行的原理等,這些都有助於你理解進程的運行原理。這些內容都在我的上一篇文章中,請關注:http://blog.csdn.net/wallwind/article/details/6968323。文章中講的較爲詳細。

2進程概念:

一個進程,主要包含三個元素:

a)        一個可以執行的程序;

b)        和該進程相關聯的全部數據(包括變量,內存空間,緩衝區等等);

c)        程序的執行上下文(execution context)。

不妨簡單理解爲,一個進程表示的,就是一個可執行程序的一次執行過程中的一個狀態。操作系統對進程的管理,典型的情況,是通過進程表完成的。進程表中的每一個表項,記錄的是當前操作系統中一個進程的情況。對於單 CPU的情況而言,每一特定時刻只有一個進程佔用 CPU,但是系統中可能同時存在多個活動的(等待執行或繼續執行的)進程。

3 fork()函數

         fork()函數是進程的核心函數。當調用fork的時候,系統將創建一個新的進程,成爲子進程(child process)。Fork函數定義形式如下:

  1. #include<unistd.h>  
  2.   
  3. Pid_t fork(void);//返回值:子進程返回0,父進程中返回子進程ID,出錯則返回-1  


從返回值我們可以看到,Fork函數調用了一次,但是返回兩次。其區別在於在子進程中返回值是0,而父進程的返回值則是新子進程的進程ID.

         上邊的概念可能對初學者比較模糊,那麼我們怎麼理解fork呢?

         當你看到fork的時候,你可以把fork理解成“分叉”,在分叉的同時,生成的一個子進程複製了父進程的基本是所有的東西,包括代碼、數據和分配給進程的資源。也就是子進程幾乎是和父進程是一模一樣的。但是子進程可能會根據不同情況調用其他函數。比如exec函數。

         下面我們用一個經典及比較典型簡單的例子來看看。

    

  1.     #include"apue.h"  
  2.   
  3.   
  4. int glob=6;  
  5.   
  6. char buf[]="a write to stdout\n";  
  7.   
  8.    
  9.   
  10. int main(void)  
  11.   
  12. {  
  13.   
  14.        int var=88;  
  15.   
  16.        pid_t pid;  
  17.   
  18.    
  19.   
  20.        if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1)  
  21.   
  22.                 printf("writeerror");  
  23.   
  24.    
  25.   
  26.        printf("before fork with  換行符\n");  
  27.   
  28.        printf("before fork without換行符");     
  29.   
  30.        //printf("\n");  
  31.   
  32.    
  33.   
  34.        if((pid=fork())<0){  
  35.   
  36.                 printf("fork error");  
  37.   
  38.    
  39.   
  40.        }else if(pid==0){  
  41.   
  42.                 printf("I am is  child process,pid=%d\n",getpid());  
  43.   
  44.                 printf("my parentprocess's pid=%d\n",getppid());  
  45.   
  46.                 glob++;  
  47.   
  48.                 var++;  
  49.   
  50.        }else{  
  51.   
  52.                 printf("this is parentprocess,pid=%d\n",getpid());  
  53.   
  54.                 //var++;  
  55.   
  56.        }  
  57.   
  58.    
  59.   
  60.        printf("pid=%d,glob=%d,var=%d\n",getpid(),glob,var);  
  61.   
  62.        exit(0);  
  63.   
  64. }  


輸出結果:                             

希望新手能夠將以上代碼自己敲一遍,然後自己運行一遍。

從上邊的結果我們一步步分析。首先我們看到兩行文字是隻輸出一遍的。

既”a write to stdout”

         “Befork forkwhit h換行符”。

也就是隻有一個進程執行這兩個輸出語句。我們先暫且不分析不帶換行符的。

當程序調用fork的時候,這個時候程序就出現了一個進程,也就是兩個進程在執行。

請看下邊的簡易圖形

在上邊我們已經講過fork函數會因爲不同調用返回不同的函數值:

1)在父進程中,fork返回新創建子進程的進程ID;

2)在子進程中,fork返回0;

3)如果出現錯誤,fork返回一個負值;

現在我們看程序裏的幾個函數。Write函數是將buf內容寫到標準流中。此處只作爲輸出用。

我們關心的是getpid()函數和getppid()函數。

getpid()函數是用來輸出當前成進程的ID,getppid()則得到當前進程的父進程的ID,從輸出結果比對可以看到,子進程得到的父ID和父進程得到自己的ID是相同的。

再來看一下全局變量glob,glob是函數外的變量。子進程將glob++處理,這個時候輸出的是7,父進程沒有進行處理直接輸出6。我們可以知道,這兩個glob變量顯示是兩份的。不相干的。

局部變量var也是同樣的結果。。。。

現在我們來解釋一下爲什麼

  1. printf("before fork with  換行符\n");  
  2.   
  3. printf("before fork without換行符");     


上一句只輸出了一次,而下邊這句話就輸出了兩次。

我們可以發現printf("before fork with  換行符\n");這句帶有\n既換行符的意思。而

  1. printf("before fork without換行符");     


是沒有帶的。

這個我們要知道printf函數是有緩衝機制的,類似於我們使用的write函數,但我們將想要的東西輸出的時候,系統僅僅是把內容放到stdout標準輸出的緩衝隊列的。當遇到“\n”的時候,系統就把緩衝裏的東西給清掉,輸出到屏幕上。

        

  1. printf("beforefork with  換行符\n");  


執行後,緩衝裏沒有了數據,自然子進程再次執行的時候沒有內容可輸出了。但是printf("before fork without換行符");的時候,子進程也會把stdout的內容再次輸出來。也就是導致了內容出處了兩邊。如果換一下書序的,結果是不一樣的哦。

3 fork難度進階

csdn看到一篇比較好的講解fork的文章。較爲深入的講解。下面我就經過自己的調試和理解將呈現給大家。首先看一段代碼:

  1. #include "apue.h"  
  2.   
  3.    
  4.   
  5. int main(void)  
  6.   
  7. {  
  8.   
  9.        int i=0;  
  10.   
  11.        pid_t pid;  
  12.   
  13.    
  14.   
  15.        for(i=0;i<2;i++)  
  16.   
  17.        {  
  18.   
  19.                 if((pid=fork())<0){  
  20.   
  21.                         printf("forkerror\n");  
  22.   
  23.                 }else if(pid==0){  
  24.   
  25.                        printf("%d,childself's pid=%d,parent's pid=%d,returnid=%d\n",i,getpid(),getppid(),pid);  
  26.   
  27.                }else{  
  28.   
  29.                        printf("%d,parentself's pid=%d,parent's father's pid=%d,returnid=%d\n",i,getpid(),getppid(),pid);  
  30.   
  31.                         sleep(2);//inorder tochild output first  
  32.   
  33.                 }  
  34.   
  35.        }  
  36.   
  37.        exit(0);  
  38.   
  39. }  


輸出的結果爲:

好好的分析一下我們的結果。0開始的輸出爲2行,1開頭的輸出4行。

然後我們開始觀察PID的結果。從pid的結果我們找到一個關係

既9278-》30361-》30362-》30363

9278-》30361-》30364

要知道,fork函數子進程調用的時候返回0,而父進程調用的時候則返回子進程的ID。

程序運行第一步:

  1. if((pid=fork())<0){  
  2.   
  3.                         printf("forkerror\n");  
  4.   
  5.                 }else if(pid==0){  
  6.   
  7.                        printf("%d,childself's pid=%d,parent's pid=%d,returnid=%d\n",i,getpid(),getppid(),pid);  
  8.   
  9.                 }else{  
  10.   
  11.                        printf("%d,parentself's pid=%d,parent's father's pid=%d,returnid=%d\n",i,getpid(),getppid(),pid);  
  12.   
  13.                         sleep(2);//inorder tochild output first  
  14.   
  15.                 }  


輸出的結果爲:

這是第一次循環輸出輸出的。

Childpid和return 均爲父ID爲30361生成的30361。

此時已經有了兩個進程了。

進程ID分別爲30362和30361。

第二次循環輸出。

我們知道了此時兩個進程30362和30361。這兩個進程分別執行自己的代碼。

ü  這個時候pid爲30362的進程進行一次:

循環,得到結果爲:

    
生成了一個子進程30363

ü  30361進程再次執行自己的代碼,也就是上述循環的部分。

生成了一個子進程30364.

從程序和輸出結果可以看到,其實我們的程序得到了6份的拷貝。

通過這個例子,fork了三次,產生了三個子進程,輸出了6次,你可以深刻的理解了fork工作原理了。

再來一個例子:

這個是典型的循環例子:

  1. #include <unistd.h>  
  2.   
  3. #include<stdio.h>  
  4.   
  5.    
  6.   
  7. int main(void)  
  8.   
  9. {  
  10.   
  11.        pid_t pid;  
  12.   
  13.        int i=0;  
  14.   
  15.        int c_cout=0;  
  16.   
  17.        int p_cout=0;  
  18.   
  19.        for(i=0;i<5;i++)  
  20.   
  21.        {  
  22.   
  23.                 if((pid=fork())<0){  
  24.   
  25.                         printf("forkerror\n");  
  26.   
  27.                 }else if(pid==0){  
  28.   
  29.                         c_cout++;  
  30.   
  31.                }else{  
  32.   
  33.                         p_cout++;  
  34.   
  35.                 }  
  36.   
  37.    
  38.   
  39.        }  
  40.   
  41.        printf("c_cout=%d,p_cout=%d,pid=%d\n",c_cout,p_cout,getpid());  
  42.   
  43.    
  44.   
  45. }  


輸出結果爲:c_cout=5,p_cout=0,pid=1559

c_cout=4,p_cout=1,pid=1558

c_cout=4,p_cout=1,pid=1560

c_cout=3,p_cout=2,pid=1557

c_cout=4,p_cout=1,pid=1562

c_cout=3,p_cout=2,pid=1561

c_cout=4,p_cout=1,pid=1566

c_cout=3,p_cout=2,pid=1565

c_cout=3,p_cout=2,pid=1569

c_cout=2,p_cout=3,pid=1568

c_cout=4,p_cout=1,pid=1574

c_cout=3,p_cout=2,pid=1573

c_cout=3,p_cout=2,pid=1577

c_cout=2,p_cout=3,pid=1576

c_cout=3,p_cout=2,pid=1581

c_cout=2,p_cout=3,pid=1580

c_cout=2,p_cout=3,pid=1584

c_cout=1,p_cout=4,pid=1583

c_cout=3,p_cout=2,pid=1563

c_cout=2,p_cout=3,pid=1556

c_cout=2,p_cout=3,pid=1564

c_cout=2,p_cout=3,pid=1570

c_cout=1,p_cout=4,pid=1555

c_cout=3,p_cout=2,pid=1575

c_cout=2,p_cout=3,pid=1572

c_cout=2,p_cout=3,pid=1578

c_cout=1,p_cout=4,pid=1571

c_cout=2,p_cout=3,pid=1582

c_cout=1,p_cout=4,pid=1579

c_cout=1,p_cout=4,pid=1585

c_cout=0,p_cout=5,pid=1554

通過推理:引用網絡文章可得到:

設f(n)表示程序中循環會執行n次時整個程序會產生的進程數,很容易得到遞推公式:

f(n)=1+f(n-1)+f(n-2)+f(n-3)+…+f(0)

比如for i=0;i<N;I++< p>

因爲i=0時fork()的子進程下次會繼續循環n-1次,i=1時 fork()的子進程下次會僅需循環n-2 次。。。。

其中常數1是進程本身。

邊界條件,f(0)=1

這樣,我們就得到了問題的答案:

f(n)=1+f(n-1)+f(n-2)+…+f(0)

f(0)=1

這個可以求出閉形式:

f(0)=1

f(1)=2

f(2)=4

用數學歸納法可以得到f(n)=2^n

所以對於程序一,會打印出2^5-1=31行信息。

對於程序二,總共會產生2^5=32個進程。

不過,我還是不知道爲什麼兩個變量的結果怎麼是那種形式的輸出,求指導啊!!!

下面貼一下比較有意思的一段代碼:

  1. #include <stdio.h>   
  2.   
  3. #include <unistd.h>   
  4.   
  5. int main(int argc, char* argv[])   
  6.   
  7. {   
  8.   
  9. fork();   
  10.   
  11. fork() && fork() || fork();         
  12.   
  13. fork();   
  14.   
  15. return 0;   
  16.   
  17. }   


我們要知道&& 和||運算符。&&有個短路現象。

A&&B,如果A=0,就沒有必要繼續執行&&B了;A非0,就需要繼續執行&&B。
A||B,如果A非0,就沒有必要繼續執行||B了,A=0,就需要繼續執行||B。

通過畫圖我們可以得到如下

加上前面的fork和最後的fork,總共4*5=20個進程,除去main主進程,就是19個進程了。


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