linux的fork和vfork

fork和vfork

区别

fork函数和vfork函数都可以用来创建一个子进程。他们有什么区别呢?

首先是fork:

  • 内核会给子进程分配虚拟内存空间和id,但不会分配物理内存
  • 子进程一开始是共享父进程的物理空间
  • 子进程写入数据后内核会给它分配物理内存

然后是vfork:

  • 子进程共享父进程的虚拟地址空间

下面通过两个图来分别展示他们的特点:

  1. 进程P1通过fork函数创建一个子进程P2:
    1234

分析:
如上图所示,P1创建了一个子进程P2,内核为这个子进程分配了虚拟内存空间,并且把P1的正文段、数据段、堆、栈的虚拟空间的内容直接复制到子进程相应的虚拟空间上;但是内核并没有给子进程分配物理空间,所以子进程P2的虚拟空间是直接指向了父进程P1的物理空间。

那问题来了,子进程什么时候可以拥有自己的物理空间呢?
当子进程尝试修改数据段或堆栈中的数据时,内核会通过写时拷贝技术给子进程分配新的物理空间,并把父进程相应的段的数据拷贝过来。

为什么要使用写时拷贝技术?
因为大多数情况下,创建子进程之后都是让它去执行一个新程序;如果把父进程
的数据直接拷贝过来但是又不使用就会造成浪费:浪费cpu资源、占用空间。

  1. 进程P1通过vfork函数创建一个子进程P2:
    22234567

从上图可以看出,内核连虚拟内存空间都不分配,子进程是直接共享父进程的虚拟内存空间,所以也就共享父进程的数据。

特点

我们汇总一下fork函数和vfork函数的特点

函数 特点
fork 1、使用写时拷贝技术 2、子进程创建之后父子进程执行的顺序具有不确定性
vfork 1、直接共享父进程的地址空间和数据 2、子进程创建之后可以确定子进程先执行,当它调用exit退出或者调用exec函数执行新程序后父进程才可能被调度

简单代码示例

fork示例

#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/wait.h>


int main(int argc, char **argv)
{
	int count = 0;
	pid_t pid;
	int wstatus;
	char *p = NULL;

	pid = fork();
	if(pid < 0)
	{
		printf("creat a child fail");
		return -1;
	}
	else if(pid == 0)
	{
		count++;
		printf("child's id:%d,[%p]count:%d\n", (int)getpid(),&count,count);
		exit(0);
	}
	else
	{
		printf("father's id:%d,[%p]count:%d\n",(int)getpid(), &count, count);
		
		pid = waitpid(pid, &wstatus, 0);
		if((int)pid < 0)
		{
			printf("wait child fail\n");
			return -1;
		}

		exit(0);
	}
}

运行结果如下:

zzc@zzc-virtual-machine:~/share/example/process$ ./p3
father's id:2447,[0x7fff152556bc]count:0
child's id:2448,[0x7fff152556bc]count:1

分析:由于父子进程count变量的地址相同,所以可以确定子进程复制了父进程的虚拟空间;而且count的值不同,可以确定子进程通过写时拷贝拥有了自己的物理空间数据。
另外,父子进程的执行顺序具有不确定性,从实际测试来看一般是父进程先执行。

vfork示例

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <stdlib.h>



int main(int argc, char **argv)
{
	int count = 0;
	pid_t pid;

	pid = vfork();
	if(pid < 0)
	{
		printf("creat a child fail");
		return -1;
	}
	else if(pid == 0)
	{
		count++;
		printf("child's id:%d,[%p]count:%d\n", (int)getpid(),&count,count);
		
		if(execlp("ps", "ps", "-t", NULL)<0)
		{
			perror("exec a new program fail");
			exit(1);
		}
	}
	else
	{
		printf("father's id:%d,[%p]count:%d\n",(int)getpid(), &count, count);
	}
}

运行结果如下:

zzc@zzc-virtual-machine:~/share/example/process$ ./p1
child's id:2557,[0x7ffdaa834c80]count:1
father's id:2556,[0x7ffdaa834c80]count:1
zzc@zzc-virtual-machine:~/share/example/process$     PID TTY      STAT   TIME COMMAND
   1650 pts/0    Ss+    0:00 -bash
   2557 pts/0    R      0:00 ps -t

zzc@zzc-virtual-machine:~/share/example/process$ 

分析:
从结果来看,可以确定子进程先执行,父进程后执行;count变量的地址和值都相同,可以确定子进程共享了父进程的地址空间和数据。子进程执行完新程序ps后退出了,然后父进程执行最后退出。

总结

fork和vfork各有其特点,为了节省空间和资源,可以考虑使用vfork。

posted @ 2022-05-16 19:33  李星云姬如雪  阅读(54)  评论(0编辑  收藏  举报