以循環創建5個進程為例,給出如下代碼,分析其錯誤:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(void)
{int i;pid_t pid;printf("xxxxxxxxxxx\n");for (i = 0; i < 5; i++){pid = fork( ); if(pid == -1){printf("process of %u creat process failurely!\n",getpid( ));perror("fork");}else if(pid == 0){printf("I'am %dth child , pid = %u\n", i+1, getpid());}else{printf("I'am parent, pid = %u\n",getpid());}}printf("yyyyyyyyyy\n");return 0;
}
分析:首先在shell中執行該文件時,由終端進程fork產生一個子進程來執行該程序,然后在for循環體中,子進程在創建一個個的孫進程。在上述for循環體中,i=0時,父進程創建了一個子進程,此時父進程與子進程的i都為0(剛fork后兩個的i相等,但是以后不一定相等,它們各自獨立)。此時有兩個進程(父、子進程)都會開始向下執行,即后面的代碼都一樣的執行,各個進程一直執行到return語句后,各個進程才會自動終止(結束)。上述,在for循環體中創建的子進程,又會在下一次循環中繼續去創建子進程,因此最終并不僅僅創建的是5個進程,而是共創建了25-1個進程,總共25個進程。如果循環n次,則總共為2n個進程。
因此,需要在循環的過程,保證子進程不再執行fork ,因此當(fork() == 0)時,子進程應該立即break;才正確(即跳出循環體)。
?
練習:通過命令行參數指定創建進程的個數,如:第1個子進程休眠0秒打印:“我是第1個子進程”;第2個進程休眠1秒打印:“我是第2個子進程”;第3個進程休眠2秒打印:“我是第3個子進程。”
通過該練習掌握框架:循環創建n個子進程,使用循環因子i對創建的子進程加以區分。
//代碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>int main(int argc, char *argv[ ])
{if(argc < 2){printf("./a.out 5\n");exit(1);}int i;pid_t pid;printf("xxxxxxxxxxx\n");long int a = strtol(argv[1],NULL,10); //將字符串轉化為10進制整數for (i = 0; i < a; i++){pid = fork( ); //創建子進程if(pid == -1){printf("process of %u creat process failurely!\n",getpid( )); perror("fork");} //判斷創建進程是否成功,如果當次循環創建不成功,則不結束該進程,進行下一次循環,再次嘗試創建(這樣等效于少了一次循環)。else if(pid == 0) //如果為子進程,則跳出循環{break;}else //否則(父進程),不執行操作,進入下一次循環;}sleep(i); //通過i值來區分進程,可見父進程睡眠時間最久,為a秒,最先創建的子進程睡眠時間最少,為0秒。if(i < 5)printf("I'am the %dth child process, the ID = %u\n",i+1,getpid( )); //子進程輸出elseprintf("I'am parent process, the ID = %u\n",getpid( )); //父進程輸出return 0;
}
[root@localhost fork]# ./fork_test 5? //shell終端fork產生子進程來運行這一程序
xxxxxxxxxxx?
I'am the 1th child process, the ID = 16507
I'am the 2th child process, the ID = 16508
I'am the 3th child process, the ID = 16509
I'am the 4th child process, the ID = 16510
I'am the 5th child process, the ID = 16511
I'am parent process, the ID = 16506
[root@localhost fork]#
分析:之所以要引入sleep函數,來使各個進程睡眠,是為了確保父進程最后結束(即最后執行return),且越先創建的子進程越先能夠結束。而在創建進程后,每個進程的i值都由自己維護,都要從創建處開始執行自己的代碼,從而i值發生改變。因此就可以用i來區分是哪一個進程,從而越先創建的進程睡眠時間越少,第i個子進程睡眠時間為i-1秒。下面深度分析sleep函數,代碼如下:
//與上面的代碼相比,只是去掉了一行內容: sleep(i);? 因此不再列出,其執行結果如下:
[root@localhost fork]# ./fork_test 5
xxxxxxxxxxx
I'am parent process, the ID = 16702
I'am the 3th child process, the ID = 16705
[root@localhost fork]# I'am the 1th child process, the ID = 16703
I'am the 2th child process, the ID = 16704
I'am the 5th child process, the ID = 16707
I'am the 4th child process, the ID = 16706
pwd?? //正常執行shell中的pwd命令(前面標簽已經輸出)
/mnt/hgfs/share/01_process_test/fork
[root@localhost fork]#
?
分析:由上可以看出,在沒有sleep( )函數的控制下,每個進程的結束先后順序是隨機的,沒法控制的。在上述程序執行過程中,總共參與了7個進程:shell終端進程、父進程(由shell終端fork產生)和5個子進程(由父進程fork產生),這7個進程對CPU的搶占是公平的(隨機的),無法預測。注意一點:父進程只能夠知道其子進程是否結束,而不能直到其孫進程是什么狀態,這7個進程共同使用這一個終端,當shell中執行. /fork_test 5時,shell進程會把前臺交給其子進程使用,一旦子進程結束(執行了return后),shell進程知道并馬上收回前臺,并輸出[root@localhost fork]# 光標? 等待與用戶再次交互(此時shell進程放棄了CPU,將自己阻塞起來,等待用戶的命令),但是此時那5個子進程并不一定就結束了,因此未結束的進程將會繼續占用CPU,直到執行到return并結束。因此,這些進程的輸出結果會在 [root@localhost fork]#的后面。CPU在1s內可執行上億條指令,因此睡1s可以絕對保證進程可以按照希望的順序執行。