Unix環境高級編程(七)fork函數總結
在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函數總結