当我调用vfork()函数时,我是否可以调用任何exec*()函数,还是必须调用execve()函数?

7

来自Linux man页面:

vfork()函数的效果与fork(2)相同,但如果该进程调用任何其他函数而未成功调用exec(3)函数族中的任何一个,则其行为是未定义的。

这表明在vfork()之后调用任何exec*()函数都是可以接受的。然而,在man页面的后面,它特别指出:

特别是,程序员不能依赖于父进程保持阻塞状态,直到子进程调用execve(2)为止[...]。

execve(2)在man页面中被反复使用,其用法表明它是在vfork()之后唯一可接受的exec类型函数。

那么为什么要在这里选择execve,我是否可以安全地调用其他exec类型函数(如execlp)?

3个回答

5
你必须调用 execve。不能保证其他 exec 家族函数在 vfork 后不会执行不安全的操作。例如:
  • execl 可能为参数列表分配内存。它需要异步信号安全,这意味着它不可能使用 malloc,但即使它没有这样做,也无法在底层 execve 发生后释放分配的内存(该内存在父进程的内存空间中),因此它最多只能在父进程中泄漏内存,除非它设法在堆栈上构造参数列表。

  • execvp 需要访问环境来执行路径搜索,并且还需要构造连接的路径名以传递给 execve。后者可能需要分配,前者可能会执行各种不安全的操作-vfork 后(注意: execvp 甚至不是异步信号安全的)。

等等。

实际上,你应该简单地不使用 vfork。几乎不可能使其使用安全。特别是在使用信号处理器的程序中,它是不安全的,因为除非你阻止所有信号(在这种情况下,子进程在执行后会继承完全阻止的信号掩码,这几乎肯定不是你想要的),否则信号处理器可能在子进程中运行,同时它正在共享父进程的内存。

如果你正在寻找比 fork 更高效的替代方案,请使用 posix_spawn


1
那么Linux的vfork不符合POSIX标准吗?难道man手册不应该更新以删除具有误导性的部分(“exec(3)函数族之一”)吗? - Cornstalks
POSIX很久以前就删除了vfork。也许它确实满足那些旧的要求,但我不一定会期望它能够满足。你可能需要阅读源代码,即使这样也可能会发生变化,所以我不会这么做。不要使用vfork。它是危险和相当无用的。 - R.. GitHub STOP HELPING ICE
vfork 在你的程序需要通过例如 cygwinmingw 移植到微软的 Windows 操作系统时非常有用,因为在微软的操作系统上 fork 非常非常非常慢。然而,我建议找一个库,在单个调用中执行 pipevforkexec 等操作,以避免混乱。 - ctrl-alt-delor
@richard:换句话说,为cygwin/mingw找到一个适当实现的posix_spawn。或者你可以尝试使用midipix,在这里fork不会极其缓慢和有问题。 - R.. GitHub STOP HELPING ICE
这个答案是错误的,因为它没有考虑堆栈内存。man signal-safety中列出的大多数函数都可以与vfork()一起安全使用,只有少数像read()这样的函数需要使用pread()代替。posix_spawn是一个令人不快的API,xnu是我见过唯一支持它的内核。学习vfork(),因为它非常快速且像C语言的其他部分一样得到了广泛支持。查看Almquist Shell的源代码(在Linux和FreeBSD上是/bin/sh),以了解哪些行为是事实上保证的。 - Justine Tunney

1
阅读手册后,有两种关于vfork的描述: POSIX标准描述说,在vfork之后,必须调用exec(3)函数之一。 Linux描述说,在vfork之后,只能调用execve(2)(且仅限execve)。
我不确定POSIX标准描述是否要求符合规范的实现允许调用任何一个exec函数。标准描述的一个可能解释是,实现可以决定允许哪些exec函数(并且只需要在vfork之后允许其中至少一个)。
无论如何,很明显Linux允许在vfork之后调用execve(且仅限execve)。POSIX标准可能允许其他exec函数,但Linux不允许。

当然,它也可以调用_exit,但在这个问答中我忽略了_exit


1
在Linux上,所有的exec*函数实际上都是基于execve系统调用的包装库函数。因此,通过调用execlp,您实际上也在调用execve

1
有没有相关的参考文档或者只是一种神秘的知识?如果这种行为没有明确记录在某个地方,vfork()文档提到execve似乎有些奇怪。 - Cornstalks
3
如果库包装器分配了动态内存(例如,用于构建数组),该怎么办? - Kerrek SB
2
@Cornstalks 只有 execve 函数在 Linux 手册的第 2 节(系统调用)中,其他的 exec* 函数都在第 3 节(库函数调用)中。 - ouah
@KerrekSB,与“fork”相比,这会改变什么吗? - ouah
2
@ouah: 在 fork 之后,进程的映像被替换,所以一切都很好(主要是除了多线程分配器的问题)。但是使用 vfork,在 execve 系统调用之前仍然位于父进程的内存中,因此该内存会泄漏。你需要确保库包装器不会执行任何操作。 - Kerrek SB
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接