linux的fork和vfork
fork和vfork
区别
fork函数和vfork函数都可以用来创建一个子进程。他们有什么区别呢?
首先是fork:
- 内核会给子进程分配虚拟内存空间和id,但不会分配物理内存
- 子进程一开始是共享父进程的物理空间
- 子进程写入数据后内核会给它分配物理内存
然后是vfork:
- 子进程共享父进程的虚拟地址空间
下面通过两个图来分别展示他们的特点:
- 进程P1通过fork函数创建一个子进程P2:
分析:
如上图所示,P1创建了一个子进程P2,内核为这个子进程分配了虚拟内存空间,并且把P1的正文段、数据段、堆、栈的虚拟空间的内容直接复制到子进程相应的虚拟空间上;但是内核并没有给子进程分配物理空间,所以子进程P2的虚拟空间是直接指向了父进程P1的物理空间。
那问题来了,子进程什么时候可以拥有自己的物理空间呢?
当子进程尝试修改数据段或堆栈中的数据时,内核会通过写时拷贝技术给子进程分配新的物理空间,并把父进程相应的段的数据拷贝过来。
为什么要使用写时拷贝技术?
因为大多数情况下,创建子进程之后都是让它去执行一个新程序;如果把父进程
的数据直接拷贝过来但是又不使用就会造成浪费:浪费cpu资源、占用空间。
- 进程P1通过vfork函数创建一个子进程P2:
从上图可以看出,内核连虚拟内存空间都不分配,子进程是直接共享父进程的虚拟内存空间,所以也就共享父进程的数据。
特点
我们汇总一下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。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律