进程创建回顾
通过 fork() 创建子进程,然后通过 execve(...) 将子进程的进程空间替代为 path 所指定程序的进程空间,随后执行 path 所指定的程序
问题
进程创建是否只能依赖于 fork() 和 execve(...) ?
再轮进程创建
fork() 通过完整复制当前进程的方式创建新进程,execve() 根据参数覆盖进程数据 (一个不留)
我们在 fork() 之后,立即执行 execve() 的话,fork() 复制父进程进程空间的操作是多余的,因为 execve() 会覆盖复制出来的父进程进程空间
pid_t vfork(void);
vfork() 用于创建子进程,然而不会复制父进程中的数据
vfork() 创建出的子进程直接使用父进程空间 (没有完整独立的进程空间)
vfork() 创建的子进程对数据 (变量) 的修改会直接反馈到父进程中
vfork() 是为了 execve() 系统调用而设计
下面的程序运行后会发生什么?
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
pid_t pid = 0;
int var = 88;
printf("parent = %d\n", getpid());
if( (pid = vfork()) < 0 )
{
printf("vfork error\n");
}
else if( pid == 0 )
{
printf("pid = %d, var = %d\n", getpid(), var);
var++;
printf("pid = %d, var = %d\n", getpid(), var);
return 0; /* destroy parent stack frame */
}
printf("parent = %d, var = %d\n", getpid(), var);
return 0;
}
程序运行结果如下图所示:
程序出现了段错误,崩溃了
程序崩溃是因为 vfork() 后,子进程和父进程共享同一个进程空间,第 31 行,子进程 return 0 之后,会从创建点返回,破坏栈结构,使得父进程的栈空间被破坏掉,从而导致程序崩溃
vfork() 深度解析
vfork() 使得子进程共享父进程的代码和数据
vfork() 要点分析
vfork() 成功后,父进程将等待子进程结束
子进程可以使用父进程的数据 (堆,栈,全局)
子进程可以从创建点调用其他函数,但不要从创建点返回
- 当 子进程执行流 回到创建点 / 需要结束 时,使用 _exit(0) 系统调用
- 如果使用 return 0 那么将破坏栈结构,导致后续父进程执行出错
vfork() 的正确使用
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
int main(int argc, char* argv[])
{
pid_t pid = 0;
int var = 88;
printf("parent = %d\n", getpid());
if( (pid = vfork()) < 0 )
{
printf("vfork error\n");
}
else if( pid == 0 )
{
printf("pid = %d, var = %d\n", getpid(), var);
var++;
printf("pid = %d, var = %d\n", getpid(), var);
// return 0; /* destroy parent stack frame */
_exit(0);
}
printf("parent = %d, var = %d\n", getpid(), var);
return 0;
}
vfork() 后,子进程共享父进程的地址空间,父进程会等待子进程运行结束后,再向下运行
第 23 行,将 var 的值加一,此时 var = 89,这个变量的改动会影响到父进程,所以父进程中的 var = 89
第 27 行,_exit(0),使得子进程结束运行,并且不会从创建点返回
程序运行结果如下图所示:
fork() 的现代化技术
Copy-on-Write
- 多个任务访问同一资源,在写入操作修改资源时,复制资源的原始副本
fork() 引入 Copy-on-Write 之后,父子进程共享相同的地址空间
- 当父进程或子进程的其中之一修改内存数据,则实时复制进程空间
- fork() + execve() <=> vfork() + execve()