vfork
对于创建一个子进程,我们最常用的就是 fork,但其实 vfork 也是用来创建子进程的,但 vfork 和 fork 的差距却是非常大的。
- fork 的父子进程各自拥有独立的程序地址空间,在运行时互不干扰;而 vfork 父子进程共用一个程序地址空间,一个进程修改了内存中的内容,下一次另一个进程就会读到被修改的内容
- vfork 保证子进程先执行,只有当子进程调用 exec 族函数 或 退出时,父进程才可以被执行
用代码来验证一下:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int a = 10;
int main()
{
printf("before vfork a = %d\n", a);
pid_t pid = vfork();
if(pid == -1) {
perror("vfork failed!\n");
return 1;
} else if(pid == 0) {
a += 5;
printf("I am child: %d, a = %d\n", getpid(), a);
_exit(0);
} else {
printf("I am father: %d, a = %d\n", getpid(), a);
}
return 0;
}
运行后可以看到,当子进程修改了变量 a 后,父进程读取到的是修改后的值
在 vfork 中还有一个要注意的点是:通常情况下,子进程不允许调用 exit(),而只能调用 _exit(),这里的原因就要从这两个函数的区别来看了。
_exit 和 exit
_exit 是操作系统为我们提供的系统调用,它的原型如下,status 参数定义了进程的终止状态
#include <unistd.h>
void _exit(int status);
exit 是 glibc 库中为我们封装好的库函数,它实际上也是调用了 _exit,但在调用 _exit 之前它还做了一些其他的事情
- 执行用户通过 atexit 或 on_exit 定义的清理函数
- 关闭所有打开的流,冲刷缓冲区
- 调用 _exit
通过一张图来理解一下 _exit 与 exit 之间的关系:
明白了 _exit 和 exit 的区别后我们就能明白为什么 vfork 通常情况下是不允许调用 exit 的,因为 vfork 的机制是子进程执行完毕后才会执行父进程,并且父子进程共用同一块程序地址空间,如果子进程在退出时调用了 exit,那么它就会关闭流并且冲刷缓冲区,导致后续父进程的操作无法正常执行。
还有一种更为常见的退出方式就是 return,其实 return 调用的就是 exit 函数,执行 return n 实际上就是 执行 exit(n)。