创建新进程在Linux的下是由父进程来完成的,创建完成的新进程是子进程。
新进程的地址空间有两种可能性:
在Linux下的fork函数用于创建一个新的进程,使用fork函数来创建一个进程时,子进程只是完全复制父进程的资源。这样得到的子进程和父进程是独立的,具有良好的并发性。但是进程间通信需要专门的机制。
fork函数调用一次,会返回两个函数值,对于父进程而言,返回的是子进程的PID(因为一个父进程可能有多个子进程,并且没有一个函数可以使父进程获取其所有的子进程ID),对于子进程返回值是0(这样就能区分父子进程,子进程是可以通过getppid来获取父进程的ID),如果进程创建失败,那么返回给父进程-1。
现在的Linux操作系统采用了copy-on-write技术(COW),即:如果父进程和子进程中任意一个尝试修改某些区域的值,那么内核会为修改区域的那部分内存制作一个副本,一般都是虚拟内存的一页。否则不进行复制操作,比如在fork的子进程中只是调用exec函数来执行另外一个可执行文件,那么事实上就没有必要复制父进程的资源,这样会造成大量的开销浪费。
fork()函数创建的子进程和父进程的执行顺序理论上是不确定的(因为取决于OS的调度策略)。
总结一下:fork函数创建的子进程是父进程的复制,子进程和父进程并发执行 来段代码测试一下。
#include
#include
#include
#include
int main()
{
int num = 3;
pid_t pid = 0;
pid = fork(); //创建一个子进程,fork()函数没有参数。
printf("pid is %d\n",getpid()); //获取进程的pid
if (0 < pid) //父进程得到的pid大于0,这段代码是父进程中执行的
{
num++;
printf("I am parent!,num is %d\n",num);
}
else if(0 == pid) //子进程得到的返回值是0,这段代码在子进程中执行
{
num--;
printf("I am son!,num is %d\n",num);
}
else //创建进程失败
{
//有两种情况会失败:
//1.进程数目达到OS的最大值
//2.进程创建时内存不够了。
printf("fork error!\n");
exit(-1);
}
return 0;
}
运行结果如下所示:
从运行结果可以看到,父子进程的PID是不同的,说明我们确实创建了一个进程。另外父子进程中的变量是独立的,这也说明了子进程是父进程的复制。当然,这样带来的坏处是进程间的通信必须使用专门的通信机制。此处使用的的if-else语句才能真正使得创建一个新进程有意义,否则父子进程将会执行一模一样的代码,这没有意义。
除了fork之外,Linux的系统还提供了vfork的函数来建立一个新进程.vfork建立的新进程和fork的不同之处在于:
我们上代码来看看实际的运行效果。
#include
#include
#include
#include
int main()
{
int num = 3;
pid_t pid;
pid = vfork(); //使用vfork创建子进程
if(0 < pid)
{
num++;
printf("I am parent!,num is %d\n",num);
}
else if(0 == pid)
{
sleep(2); //将子进程挂起2秒
num--;
printf("I am son!,num is %d\n",num);
}
else
{
printf("fork error!\n");
exit(-1);
}
return 0;
}
运行结果如下: (等待了两秒后,输出如下)
我们看到父进程中打印的不对,而且发生了段错误(段错误就是内存越界了)。这就是和fork的不同之处,vfork的函数必须使用exit来结束进程。否则就会出现错误。更改以后的代码输出如下所示:
#include
#include
#include
#include
int main()
{
int num = 3;
pid_t pid;
pid = vfork(); //使用vfork创建子进程
if(0 < pid)
{
num++;
printf("I am parent!,num is %d\n",num);
}
else if(0 == pid)
{
sleep(2); //将子进程挂起2秒
num--;
printf("I am son!,num is %d\n",num);
exit(0); //加上进程结束后,程序就能正常执行了
}
else
{
printf("fork error!\n");
exit(-1);
}
return 0;
}
可以看到子进程和父进程是共享数据的。而且子进程在父进程之前执行。
如果你的应用程序想要可移植,那么就不应该使用vfork函数。由于历史局限性,当年的Unix系统是没有写时拷贝技术的,如前所述,在fork的子进程中如果只是调用exec函数来执行另外一个可执行文件,那么事实上就没有必要复制父进程的资源,这样将会造成大量的开销浪费。为了避免这个问题,当年的Unix系统设计者就设计了vfork函数,它的主要目的就是exec一个新的程序。现在有了写时拷贝技术,vfork函数也就渐渐地被弃用了。