以循环创建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( )); //子进程输出
else
printf("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可以绝对保证进程可以按照希望的顺序执行。