为什么要在子进程创建后立即调用 exec() 或 exit() 时使用 vfork() ?

为什么要在子进程创建后立即调用 exec() 或 exit() 时使用 vfork() ?

操作系统概念和APUE说

使用vfork(),父进程被挂起,子进程使用父进程的地址空间。由于 vfork() 不使用写时复制,因此如果子进程更改了父进程地址空间的任何页面,则一旦父进​​程恢复,更改的页面将对父进程可见。所以,vfork() 必须谨慎使用,以确保子进程不会修改父进程的地址空间。

vfork() 旨在当子进程创建后立即调用 exec() 或 exit() 时使用。

最后一句我该如何理解?

vfork()当通过调用创建的子进程时exec(),不会exec()通过加载新程序来修改父进程的地址空间吗?

vfork()当调用创建的子进程时exit()exit()终止子进程时不会修改父进程的地址空间吗?

我个人更喜欢Linux。

谢谢。

答案1

vfork()当通过调用创建的子进程时exec(),不会exec()通过加载新程序来修改父进程的地址空间吗?

否,exec()为新程序提供新的地址空间;它不会修改父地址空间。参见示例execPOSIX 中函数的讨论, 和Linux 联机execve()帮助页

当vfork()创建的子进程调用exit()时,exit()在终止子进程时是否不会修改父进程的地址空间?

简单的exit()可能 - 它运行由正在运行的程序(包括其库)安装的退出挂钩。vfork()限制性更强;因此,在 Linux 上,它规定指某东西的用途_exit()哪个调用 C 库的清理函数。

vfork()事实证明很难做到正确;它已在当前版本的 POSIX 标准中删除,并且posix_spawn()应该使用。

然而,除非你真的知道你在做什么,你应该不是使用vfork()posix_spawn();坚持好旧fork()exec()

上面链接的 Linux 联机帮助页提供了更多上下文:

然而,在过去糟糕的日子里,afork(2) 需要对调用者的数据空间进行完整的复制,这通常是不必要的,因为通常紧接着 anexec(3)就会完成。因此,为了提高效率,BSD引入了vfork() 系统调用,它并不完全复制父进程的地址空间,而是借用父进程的内存和控制线程,直到发生调用execve(2)或退出。当子进程使用其资源时,父进程被挂起。的使用 vfork()很棘手:例如,不修改父进程中的数据取决于知道寄存器中保存了哪些变量。

答案2

当您调用 时vfork(),将创建一个新进程,并且该新进程借用父进程的进程映像(堆栈除外)。子进程被赋予一个自己的新堆栈星号,但不允许return从调用的函数开始vfork()

当子进程运行时,父进程被阻止,因为子进程借用了父进程的地址空间。

无论您做什么,仅访问堆栈的所有内容都只会修改子级的私有堆栈。但是,如果您修改全局数据,则会修改公共数据,从而也会影响父级数据。

修改全局数据的事物例如:

  • 调用 malloc() 或 free()

  • 使用标准输入输出

  • 修改信号设置

  • 修改不是调用函数本地的变量vfork()

  • ...

一旦你调用_exit()(重要的是,永远不要调用exit()),子进程就会终止,并将控制权交还给父进程。

如果调用该exec*()系列中的任何函数,则会使用新程序代码、新数据和父级堆栈的一部分创建新的地址空间(见下文)。一旦准备好,子进程就不再向子进程借用地址空间,而是使用自己的地址空间。

控制权被交还给父进程,因为它的地址空间不再被其他进程使用。

重要提示:在 Linux 上,没有真正的vfork()实现。 Linux 是vfork()基于fork()1988 年 SunOS-4.0 引入的 Copy on Write 概念来实现的。为了让用户相信他们使用的是vfork()Linux,Linux 只是设置共享数据并在子级没有调用_exit()其中一项exec*()功能时挂起父级。

因此,Linux 并没有受益于实数vfork()不需要为内核中的子进程设置地址空间描述这一事实。这会导致 avfork()不比 快fork()。在实现 real 的系统上vfork(),它通常比使用- 、最近的和fork()的 shell 快 3 倍,并且会影响其性能。vfork()ksh93Bourne Shellcsh

exit()您永远不应该从ed 子级调用的原因vfork()是,exit()如果调用之前有未刷新的数据,则刷新 stdio vfork()。这可能会导致奇怪的结果。

BTW:posix_spawn()是在 之上实现的vfork(),因此vfork()不会从操作系统中删除。有人提到 Linux 不使用vfork()for posix_spawn().

对于堆栈,很少有文档,以下是 Solaris 手册页的内容:

 The vfork() and vforkx() functions can normally be used  the
 same  way  as  fork() and forkx(), respectively. The calling
 procedure, however, should not return while running  in  the
 child's  context,  since the eventual return from vfork() or
 vforkx() in the parent would be to a  stack  frame  that  no
 longer  exists. 

所以实现可以做任何它喜欢做的事情。 Solaris 实现使用共享内存作为函数调用的堆栈帧vfork()。没有实现授予从父级访问堆栈的旧部分的权限。

相关内容