概述
在linux下,可以直接调用fork()函数来生成当前进程的子进程,且子进程与父进程共享代码段。至于具体是共享全部代码段,还是自fork()之后的代码段,下面的例子中有简单的测试。
实际上,子进程相当于父进程的拷贝,二者的代码部分完全相同,可以看作是同一段代码的两次执行,他们之间可以通信,而且可以根据fork()返回值的不同对代码进行有选择地执行。
父进程中,调用fork()时,系统会生成一个新的进程,同时把父进程的当前寄存器状态等 拷贝到新的进程,来生成子进程。最后如果进程生成成功,则fork()返回这个子进程的进程id,供父进程查看与使用。然后父进程继续执行接下来的语句,而子进程想要进入CPU执行,需要排队等待。对于子进程,由于只是复制了父进程的当前栈状态,所以其相当于是与父进程共享了fork()之后的代码段。子进程实际上是从fork()返回返回值时候开始执行的,但其返回值为0.
为什么父进程与子进程的fork()返回值不同?
首先,需要明白的是,进程(process)不仅仅是程序(program),还包括了自己的数据,及与其相关的寄存器状态等。每个进程只有在进入CPU中的时候才会被执行,其数据与寄存器状态才会改变。由于相当于CPU来说,进程数很多,所以进程大部分时间并不是在执行(executing),而是处于准备(prepared)或者等待(waiting)状态。一般CPU会为每个进程分配一定长度的时间片(slice),在这个时间片内,进程会被执行,如果时间片耗尽,进程必须离开CPU,等待下一次得到时间片。当进程离开CPU时候,就需要把自己当前的状态保存下来,比如当前执行到第几行程序(PC),各变量的值,各寄存器的值等,以便再次进入CPU可以将这些数据加载,继续执行。总之,每个进程都有自己的数据,及寄存器状态,且可以在非执行状态维持不变。
函数的返回值是在寄存器exa中储存的,当父进程调用fork()并返回结果时,是取其当前exa中的数据,而这个数据在fork()中已经被更新为子进程的id了,所以父进程可以通过fork()知道其产生的子进程的id,当然所有进程都可以使用另一个函数getpid()知道自己的id.
子进程方面,通过父进程调用fork()函数,子进程对父进程的当前栈进行了复制,不过其exa寄存器状态被初始化成了0,这时候子进程还没有被CPU执行过,当其第一次被CPU执行时,fork()的返回值由其exa寄存器的值来确定,所以为0. 之后,两个进程的执行代码就相同了。
为什么需要产生子进程?
父进程产生子进程的一个好处是:可以加快程序的执行速度。
由于CPU每次分给每个进程的时间片是有限的,进程如果想要得到时间片,需要竞争(有相应的算法),这时,如果一段代码使用了生成子进程的方法,则相当于可以有两个进程执行这一段代码,把两个进程的功能分开,相当于被CPU执行的几率提高了1倍,速度变快。比如使用if-else语句,父进程执行if语句块,子进程执行else语句块。
一个例子:
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
pid_t pid;
int test;
srand((unsigned)time(0));
test=rand()%10;
sleep(2);
pid=fork();
if(0>pid)
{
printf("Creating process error! Test nr. is %d\n", test);
return -1;
}
if(0==pid)
{
printf("This is child process, %d. Test nr. is %d\n",getpid(),test);
sleep(2);
}
else
{
printf("Enter the parent process, Test nr. is %d\n",test);
wait();
printf("This is parent process, %d, the child id is %d, Test nr. is %d\n",getpid(),pid,test);
}
return 1;
}
结果如下:
dhlinux@ubuntu:~/test_c$ ./fork_process
Enter the parent process, Test nr. is 3
This is child process, 3042. Test nr. is 3
This is parent process, 3041, the child id is 3042, Test nr. is 3
解释:这段代码包括了打算测试的fork()相关的主要部分,没有通信与同步。
首先,产生随机数是为了测试子进程是否真的是从fork()处开始执行的,如果不是的话,结果处的Test nr. 值与父进程应该不同,但结果正好与之相反,同时印证了之前叙述的fork()的原理。
如果子进程生成失败,fork()的返回值为负,可以通过0>pid检测出来。此为异常处理
从结果中可以看出,父进程首先执行,直到wait()函数处,wait()函数的作用是等待直到子进程结束。然后子进程执行,最后父进程中的子进程id与子进程自己输出的id相同。
对应到具体执行过程:父进程竞争得到时间片,进入CPU,开始执行,然后调用了fork()函数,产生了子进程,此时子进程处于等待或者准备队列,仍然是父进程处于CPU中,直到遇到wait()函数,虽然分配的时间片没有用完,但遇到中断(即wait),不得不退出。直到子进程进入CPU,执行完,父进程才得以解锁,继续执行。