如果vfork()调用exec family,为什么它不是未定义的?

时间:2011-08-14 16:52:24

标签: c linux system-calls

根据vfork()手册页,如果vfork()在调用_exit或exec系列调用函数之前修改除pid_t 之外的任何数据,则行为未定义。

据我所知,如果由vfork()创建的子进程调用exec(),那么它可以修改任何数据,并且行为仍未定义。

我的问题是:

  1. 众所周知,孩子共享父地址空间,那么如果使用exec覆盖孩子,自我和父图像,行为是不是未定义?

  2. 如果孩子调用了exec,那么父母会怎么回事?父母是否开始使用由孩子使用exec创建的新副本?

6 个答案:

答案 0 :(得分:5)

exec调用用一个全新的地址空间替换孩子的整个地址空间。任何共享地址空间都将被调用完全替换。

vfork函数仅作为优化存在。对于某些操作系统,fork非常昂贵,因为子进程可能会修改映射到内存的任何页面,因此必须修改每个页面以便在写入时复制(或者,最初实际复制!)以免修改父对应的页面。一个非常常见的序列是fork,紧接着是exec,迫使这些系统重新映射所有页面,以便在一瞬间将它们全部丢弃。 vfork允许您在子进程中将映射保留为未定义状态,而不是考虑到您不打算使用它们,而不是去修改所有映射。

因此,在vfork之后做某些事情会造成混乱。但是只要你调用exec,所有未定义的映射都会消失。

在实践中,操作系统处理vfork两种方式之一:对于操作系统,其中更改所有映射以便在写入时进行复制的成本低或未实现vfork优化,vforkfork相同。对于使用vfork优化的操作系统,vfork使父级和子级完全共享大多数页面,如果子级修改它们会导致不良事件发生(它们在父级中进行修改)。

因此,对您的问题的简短回答是,如果vfork是以这种方式设计的,那么它就无法用于其唯一的预期用途。

答案 1 :(得分:4)

vfork实际上可能不会共享地址空间。具体是不确定它是否这样做。这是因为复制地址空间在现代操作系统上变得非常便宜,因此必须实现一个不会比它值得更麻烦的调用。

此外,如果vfork共享地址空间,它将共享堆栈。将一个进程弹出项目从共享堆栈中移出另一个是不太明白的。

exec为流程创建了一个全新的地址空间,并“忘记”旧流程。由于在vfork情况下可能(或可能不)是使用该地址空间的两个进程,因此对它的引用计数将递减,并且父进程将能够继续使用地址空间。

子进程无法从成功的exec“返回”。成功exec后,将创建一个新的地址空间,并在从main开始的流程中开始执行。

vfork可能会暂停父级,直到子级执行execexit为止。从这个意义上说,一个孩子可以从exec返回,因为父进程的执行会在它停止时恢复。但是,即使在共享情况下,父进程的地址空间也不会受到影响,因为execexit情况只会导致对原始(父级)地址空间的引用少一个。 / p>

答案 2 :(得分:4)

我认为你的关键误解是exec的作用:它不会用新进程“覆盖内存”。相反,它会抛弃其整个虚拟内存(无论是以前的私有映射,共享映射还是其他),并为与新进程映像(可执行文件)对应的调用进程ID创建一个全新的虚拟地址空间。除了内存管理结构上的引用计数递减(它增加vfork)之外,这与父地址空间无关。

答案 3 :(得分:1)

vfork发明作为fork + exec的优化。整个想法是,'如果您的计划是致电fork()然后exec(...)',请使用vfork,我们将尽我们所能利用这一点并加快速度。 “

限制是允许实现者具有最大的灵活性,包括执行除exec以外的任何操作时的任意意外。

孩子不能'调用exec然后返回'。 exec系列不会返回。它取代了整个图像。所以你问题的第二部分是不负责任的。

答案 4 :(得分:1)

我认为这是混淆的基本点:通常,fork通过复制父级来创建新的地址空间,exec用从可执行文件加载的新地址空间替换调用者的地址空间。磁盘。因此,如果vfork 没有重复父地址空间,那么在exec之后调用vfork如何不破坏父地址空间,离开父母无处可以恢复执行?

答案是,这会使vfork无用,所以内核会避免它。当从exec的子端调用vfork时,它会创建一个新的地址空间,将可执行文件加载到那里,并单独留下调用地址空间。然后,子进程被上下文切换到新的地址空间,父进程在其未修改的原始地址空间中继续执行。

vfork的所有危险源于孩子暂时在父母的地址空间中执行,直到它调用exec_exit为止。孩子在那里做的任何副作用都会粘住,并影响父母,可能是灾难性的。除非您使用vfork只是fork的别名的系统,否则它们不会坚持。因此,你不能指望任何一种行为,你必须避免在孩子身上做任何事情。

答案 5 :(得分:1)

vfork可能实际上并没有在一个单独的地址空间中运行分叉进程,因此它的行为更像是一个“线程”(除非没有并发执行或单独的堆栈)。这意味着除了exec或_exit之外,你必须在孩子身上做任何事情。

支持vfork的某些内核(uclinux?ELKS?)不支持fork - 例如,在无MMU的系统上,支持fork()基本上是不可能的(即使是复制页面)。每个进程都需要独立启动,因为它们都共享地址空间。

所以vfork可以在这些上正确实现,但fork不能。