1. 程式人生 > >Unix環境高級編程(七)fork函數總結

Unix環境高級編程(七)fork函數總結

align 進程描述符 include cnblogs pro too unix環境 描述 sizeof

  在Unix/Linux中用fork函數創建一個新的進程。進程是由當前已有進程調用fork函數創建,分叉的進程叫子進程,創建者叫父進程。該函數的特點是調用一次,返回兩次,一次是在父進程,一次是在子進程。兩次返回的區別是子進程的返回值為0,父進程的返回值是新子進程的ID。子進程與父進程繼續並發運行。如果父進程繼續創建更多的子進程,子進程之間是兄弟關系,同樣子進程也可以創建自己的子進程,這樣可以建立起定義關系的進程之間的一種層次關系。

  程序包含位於內存的多個組成部分,執行程序的過程將根據需要來訪問這些內容,包括文本段(text segment)、數據段(data segments)、棧(stack)和堆(heap)。文本段中存放CPU所執行的命令,數據段存放進程操作的所有數據變量,棧存放自動變量和函數數據,堆存放動態內存分配情況數據。當進程被創建時,子進程收到父進程的數據副本,包括數據空間、堆、棧和進程描述符。

程序1:創建一個子進程,子進程對繼承的數據進行修改,然後分別輸出父子進程的信息。程序如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <errno.h>
 6 #include <sys/types.h>
 7 
 8 int add(int a,int b);
 9 //全局變量
10 int global = 99;
11 char buf[] = "Input a string: ";
12 
13 int main()
14 {
15     pid_t   pid;
16     int val,ret;
17     char *str;
18     val =49;
19     str = (char*)malloc(100*sizeof(char));
20     memset(str,0,100*sizeof(char));
21     if((pid = fork()) == -1)
22     {
23         perror("fork() error");
24         exit(-1);
25     }
26     if(pid == 0)   //子進程
27     {
28         printf("Child process start exec.\n");
29         global++;
30         val++;
31     }
32     if(pid >0)   //父進程
33     { 
34          sleep(10);   //等待子進程執行
35          printf("Parent process start exec.\n");
36     }
37     printf("pid=%d,ppid=%d,global=%d,val=%d\n",getpid(),getppid(),global,val);
38     write(STDOUT_FILENO,buf,strlen(buf));
39     read(STDIN_FILENO,str,100);
40     write(STDOUT_FILENO,str,strlen(str));
41     ret = add(global,val);
42     printf("global+val=%d\n",ret);
43     exit(0);
44 }
45 
46 int add(int a,int b)
47 {
48     return (a+b);
49 }

fork函數執行後程序結構圖如下:

技術分享圖片

子進程與父進程並行執行,因此在父進程中sleep(10),讓子進程先執行,然後再執行父進程。

程序執行結果如下所示:

技術分享圖片

如何創建多個子進程呢?在開發並發服務器時,用到的進程池模型需要先創建指定書目的子進程。舉個例子,假如我們現在需要創建2個子進程,很容易想到的是調用一個循環,執行fork函數2次即可。嘗試一下是否可行呢?代碼如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <errno.h>
 6 #include <sys/types.h>
 7 
 8 int main()
 9 {
10     int i;
11     pid_t pid;
12     printf("pid=%d , ppid=%d\n",getpid(),getppid());
13     //通過一個循環創建對個子進程
14     for(i=0;i<2;++i)
15     {
16         pid = fork();
17         if(pid == 0)
18         {
19             printf("create child process successfully.\n");
20             printf("pid=%d , ppid=%d\n",getpid(),getppid());
21             printf("i=%d\n",i);
22         }
23         else if(pid== -1)
24         {
25             perror("fork() error");
26             exit(-1);
27         }
28         else
29         {
30             sleep(3);
31             printf("parent process.\n");
32             printf("pid=%d , ppid=%d\n",getpid(),getppid());
33             printf("i=%d\n",i);
34         }
35     }
36 
37     exit(0);
38 }

程序執行結果如下:

技術分享圖片

從結果來看,子進程的數目不是2而是3,這是為什麽呢?先簡單的分析一下:從結果看出父進程ID為10669,子進程的ID分別為:10670、10671、10672。

父子進程之間的關系如下:

技術分享圖片

ID為10670的子進程也調用fork函數,創建了一個進程。因為fork函數創建的進程是父進程的一份拷貝,保存了當前的數據空間、堆、棧及共享代碼區域。正確的方式應該是在子進程中跳出,停止繼續fork。改進的代碼如下:

 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <string.h>
 4 #include <unistd.h>
 5 #include <errno.h>
 6 #include <sys/types.h>
 7 
 8 int main()
 9 {
10     int i;
11     pid_t pid;
12     printf("pid=%d , ppid=%d\n",getpid(),getppid());
13     for(i=0;i<2;++i)
14     {
15         pid = fork();
16         if(pid == 0)
17         {
18             printf("create child process successfully.\n");
19             printf("pid=%d , ppid=%d\n",getpid(),getppid());
20             printf("i=%d\n",i);
21             //子進程跳出循環,防止子進程繼續創建子進程
22             break;
23         }
24         else if(pid== -1)
25         {
26             perror("fork() error");
27             exit(-1);
28         }
29         else
30         {
31             sleep(3);
32             printf("parent process.\n");
33             printf("pid=%d , ppid=%d\n",getpid(),getppid());
34             printf("i=%d\n",i);
35             //父進程繼續創建子進程
36             continue;
37         }
38     }
39 
40     exit(0);
41 }

程序執行結果如下:

技術分享圖片

從結果可以看出這父進程(ID為10789)創建了兩個子進程(ID分別為:10790、10791)。

現有有這樣一個面試題,程序如下:

技術分享圖片
 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <stdlib.h>
 4 #include <sys/types.h>
 5 
 6 int main()
 7 {
 8     pid_t   pid1;
 9     pid_t   pid2;
10 
11     pid1 = fork();
12     pid2 = fork();
13 
14     printf("pid1=%d,pid2=%d\n",pid1,pid2);
15     exit(0);
16 }
技術分享圖片

要求如下:
已知從這個程序執行到這個程序的所有進程結束這個時間段內,沒有其它新進程執行。
1、請說出執行這個程序後,將一共運行幾個進程。
2、如果其中一個進程的輸出結果是“pid1:1001, pid2:1002”,寫出其他進程的輸出結果(不考慮進程執行順序)。

  這個題目考查fork函數的理解。fork的作用是復制一個與當前進程一樣的進程。新進程的所有數據(變量、環境變量、程序計數器等)數值都和原進程一致,但是是一個全新的進程,並作為原進程的子進程,父子進程並行的執行剩下的部分。

程序的執行過程如下:

(1)程序開始執行時候系統分配一個進程進行執行,稱該進程為主進程P,進程ID題目未給,

(2)主進程執行到第一個fork函數的時候,創建一個新的子進程P1,有題目可知進程ID為1001,fork函數有兩個返回值,返回pid=0代表子進程P1,pid1>0代表父進程P。

(3)現在有兩個進程P和P1,分別執行剩下部分,

(4)P進程(父進程,所以pid1=1001)調用fork創建子進程P2,返回兩個值中pid2=1002表示P2的進程ID返回給父進程P,pid2=0子進程P2本身,所以輸出pid1=1001,   pid2=1002和pid1=1001,pid2=0。

(5)P1進程(子進程,所以pid1=0)調用fork創建子進程P3,進程ID類推為1003,返回兩個值中pid2=1003表示P3的進程ID返回給父進程P1,pid2=0標識進程P3本身。所以輸出pid1=0,pid2=1003和pid1=0,pid2=0。

(6)執行整個結束。

根據以上分析可知答案:

1、一共執行了四個進程。(P0, P1, P2, P3)

2、另外幾個進程的輸出分別為:

pid1:1001, pid2:0

pid1:0, pid2:1003

pid1:0, pid2:0

上機測試如下:

技術分享圖片

測試結果如下:

技術分享圖片

測試結果雖然不是1001,但是可以看出理論分析過程是正確的。

題目來自:http://www.cnblogs.com/leoo2sk/archive/2009/12/11/talk-about-fork-in-linux.html

Unix環境高級編程(七)fork函數總結