根据vfork()
手册页,如果vfork()
在调用_exit或exec系列调用函数之前修改除pid_t 之外的任何数据,则行为未定义。
据我所知,如果由vfork()
创建的子进程调用exec()
,那么它可以修改任何数据,并且行为仍未定义。
我的问题是:
众所周知,孩子共享父地址空间,那么如果使用exec覆盖孩子,自我和父图像,行为是不是未定义?
如果孩子调用了exec,那么父母会怎么回事?父母是否开始使用由孩子使用exec创建的新副本?
答案 0 :(得分:5)
exec
调用用一个全新的地址空间替换孩子的整个地址空间。任何共享地址空间都将被调用完全替换。
vfork
函数仅作为优化存在。对于某些操作系统,fork
非常昂贵,因为子进程可能会修改映射到内存的任何页面,因此必须修改每个页面以便在写入时复制(或者,最初实际复制!)以免修改父对应的页面。一个非常常见的序列是fork
,紧接着是exec
,迫使这些系统重新映射所有页面,以便在一瞬间将它们全部丢弃。 vfork
允许您在子进程中将映射保留为未定义状态,而不是考虑到您不打算使用它们,而不是去修改所有映射。
因此,在vfork
之后做某些事情会造成混乱。但是只要你调用exec
,所有未定义的映射都会消失。
在实践中,操作系统处理vfork
两种方式之一:对于操作系统,其中更改所有映射以便在写入时进行复制的成本低或未实现vfork
优化,vfork
与fork
相同。对于使用vfork
优化的操作系统,vfork
使父级和子级完全共享大多数页面,如果子级修改它们会导致不良事件发生(它们在父级中进行修改)。
因此,对您的问题的简短回答是,如果vfork
是以这种方式设计的,那么它就无法用于其唯一的预期用途。
答案 1 :(得分:4)
vfork
实际上可能不会共享地址空间。具体是不确定它是否这样做。这是因为复制地址空间在现代操作系统上变得非常便宜,因此必须实现一个不会比它值得更麻烦的调用。
此外,如果vfork
共享地址空间,它将共享堆栈。将一个进程弹出项目从共享堆栈中移出另一个是不太明白的。
exec
为流程创建了一个全新的地址空间,并“忘记”旧流程。由于在vfork
情况下可能(或可能不)是使用该地址空间的两个进程,因此对它的引用计数将递减,并且父进程将能够继续使用地址空间。
子进程无法从成功的exec
“返回”。成功exec
后,将创建一个新的地址空间,并在从main
开始的流程中开始执行。
vfork
可能会暂停父级,直到子级执行exec
或exit
为止。从这个意义上说,一个孩子可以从exec
返回,因为父进程的执行会在它停止时恢复。但是,即使在共享情况下,父进程的地址空间也不会受到影响,因为exec
或exit
情况只会导致对原始(父级)地址空间的引用少一个。 / 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不能。