我遇到了一种情况,我需要从一个非常大的多线程应用程序中生成一个辅助进程,而我无法完全控制该应用程序。
现在我正在使用
fork()
/exec()
。这在很多时候都有效,但在某些情况下,孩子会在 exec()
发生之前奇怪地崩溃。我怀疑这是因为 fork()
多线程应用程序通常被认为是一个非常糟糕的主意。
我真的非常非常喜欢一种原子方式启动进程的方法,而不用
fork()
处理父进程:关闭所有文件描述符,按照我想要的方式设置环境,设置 CWD 等。这应该避免 的所有恐怖fork()
我的多线程父应用程序,并处理文件描述符继承等。posix_spawn()
应该是理想的选择。不幸的是,在 Linux 上,posix_spawn()
是使用 fork()
和 exec()
... 实现的
vfork()
定义为挂起父进程,直到子进程调用 exec()
。这看起来更像是我想要的,但我的理解是,现在vfork()
通常被认为是历史遗迹,相当于fork()
--- 仍然是这样吗?
处理这个问题最不糟糕的方法是什么?
请注意:
这是在 Linux 上。涉及 Java,但所有我的代码都在 C 中。
如果您仅使用异步信号安全操作,则分叉多线程应用程序被认为是安全的。 POSIX 说:
进程应使用单线程创建。如果多线程进程调用 fork(),新进程应包含调用线程及其整个地址空间的副本,可能包括互斥体和其他资源的状态。因此,为了避免错误,子进程只能执行异步信号安全操作,直到调用 exec 函数之一为止。 Fork 处理程序可以通过 pthread_atfork() 函数建立,以便在 fork() 调用之间保持应用程序不变性。
posix_spawn() 不是最好的主意:
临时修改多线程进程的环境也很复杂,因为所有线程必须同意何时更改环境是安全的。但是,此成本仅由使用附加功能的 posix_spawn() 和 posix_spawnp() 的调用承担。由于广泛的修改并不常见,并且在时间关键的代码中尤其不可能,因此将大部分环境控制保留在 posix_spawn() 和 posix_spawnp() 之外是适当的设计。
(参见 man posix_spawn)
我猜您在从父资源复制时遇到问题。您可以使用 pthread_atfork() 处理程序清理它们(您使用 pthread,对吧?)。另一种方法是使用称为 clone() 的低级函数来创建进程。它使您几乎可以完全控制子进程应该从其父进程继承什么。
[更新]
解决这个问题最简单的方法可能就是改变你的分叉方案。例如,您甚至可以在程序初始化所有资源之前创建一个新进程(fork)。 IE。在创建所有线程之前,在 main() 中调用 fork() 。在子进程中设置信号处理程序(例如 SIGUSR2 信号)并睡眠。当父进程需要执行某个新进程时,它会向子进程发送 SIGUSR2 信号。当子进程捕获它时,它会调用 fork/exec。
如果
您将自己限制为“原始”系统调用(
fork
、syscall(SYS_fork)
等),那么调用
syscalll(SYS_execve, ...)
应该是安全的。调用任何 glibc 例程,你都会遇到很多麻烦。调用
vfork
根本不是你想要的:只有调用
vfork
的线程被挂起,其他线程将继续运行(并且与 vforked 子级在同一地址空间中)。这很可能会让你的生活变得复杂。
直接调用
clone
是可能的,但非常棘手。我们有一个实现,允许从多线程应用程序安全分叉子进程(不幸的是不是开源的)。该代码非常
棘手,而且出人意料的长。好吧,考虑一下,在Linux中,fork可以在线程内调用。但是,您必须记住一些重要的注意事项。当在多线程程序中调用fork时,只有调用线程在子进程中被复制,而所有其他线程都不会被复制。如果其他线程持有锁或正在进行关键操作,这可能会导致问题。 posix_spawn 的设计更加安全,因为它避免了与 fork 相关的陷阱。