【问题标题】:Spawn process from multithreaded application从多线程应用程序生成进程
【发布时间】:2011-12-30 10:13:15
【问题描述】:

我有一种情况,我需要从一个非常大的多线程应用程序中生成一个辅助进程,而我无法完全控制它。

现在我正在使用fork()/exec()。这在很多时候都有效,但在某些情况下,孩子会在exec() 发生之前奇怪地崩溃。我怀疑这是因为fork()ing 多线程应用程序通常被认为是一个非常糟糕的主意。

我真的非常喜欢一种原子启动进程的方法,而不需要fork()父进程:关闭所有文件描述符,按照我想要的方式设置环境,设置 CWD 等。这应该避免所有的恐怖fork()ing 我的多线程父应用程序,以及处理文件描述符继承等。posix_spawn() 应该是理想的。不幸的是,在 Linux 上,posix_spawn() 是使用 fork()exec() 实现的...

vfork() 被定义为暂停父进程,直到子进程调用exec()。这似乎更像我想要的,但我的理解是vfork()现在普遍被认为是历史遗迹,相当于fork()——现在还是这样吗?

处理这个问题最不坏的方法是什么?

注意:

  • 我无法在任何线程启动之前生成我的进程(因为此时我无法运行代码)
  • 由于外部要求,我无法重新设计我的应用程序以使其不需要辅助进程
  • 在生成辅助进程之前,我无法暂停所有线程,因为它们不属于我

这是在 Linux 上。涉及 Java,但所有我的代码都在 C 中。

【问题讨论】:

  • 既然您已经在使用线程,为什么不直接启动一个调用system() 并运行您需要的任何东西的新线程?
  • 在多线程程序中使用 fork() 没有固有问题,除非它对于需要大量内存和繁忙的线程应用程序来说可能效率低下。
  • system() 通过 fork()/exec() 工作!
  • 使用 fork() 和线程的最大问题是,当 fork() 发生时,所有其他线程都消失了——无论它们是否处于临界区。并且锁、信号量等的状态在子进程中变为 undefined。因此,如果一个线程在 malloc() 的中间,然后孩子调用 malloc(),就会发生可怕的事情(我认为这发生在我身上)。事实上,我已经重组了一些东西,以便我使用 vfork() 和唯一一个孩子称之为 exec() 的函数,我认为我的崩溃已经停止显现,但我会还是喜欢答案...
  • @DavidGiven:这里迟到了,但我不希望 posix_spawnsystem 在 @ 之间进行 non-async-signal-safe 调用987654338@和exec;因此它们应该可以安全地在多线程应用程序中使用,除非他们在手册页中明确说明。

标签: c linux unix process fork


【解决方案1】:

调用fork 应该是安全的如果您将自己限制为“原始”系统调用(syscall(SYS_fork)syscalll(SYS_execve, ...) 等)。调用任何 glibc 例程,你会遇到很多麻烦。

调用vfork根本不是你想要的:只有调用vfork的线程被挂起,其他线程将继续运行(并且在与vforked相同的地址空间中)孩子)。这很可能会使您的生活复杂化。

直接调用clone 是可能的,但非常棘手。我们有一个实现,允许从多线程应用程序安全地分叉子进程(不幸的是不是开源的)。这段代码非常很棘手,而且长得惊人。

【讨论】:

  • fork() 然后直接exec() 没有任何干预代码似乎工作 --- 或者至少,还没有崩溃......谢谢。
【解决方案2】:

如果您只使用异步信号安全操作,则分叉多线程应用程序被认为是安全的。 POSIXsays:

应使用单个线程创建进程。如果多线程进程调用 fork(),则新进程应包含调用线程及其整个地址空间的副本,可能包括互斥锁和其他资源的状态。因此,为避免错误,子进程只能执行异步信号安全操作,直到调用其中一个 exec 函数。可以通过 pthread_atfork() 函数建立 fork 处理程序,以便在 fork() 调用中保持应用程序不变量。

posix_spawn() 不是最好的主意:

临时修改多线程进程的环境也很复杂,因为所有线程必须同意何时可以安全地更改环境。但是,此成本仅由使用附加功能的 posix_spawn() 和 posix_spawnp() 调用承担。由于广泛的修改不是通常的情况,并且在时间关键的代码中特别不可能,因此将大部分环境控制排除在 posix_spawn() 和 posix_spawnp() 之外是合适的设计。

(见man posix_spawn

我猜你在从父资源复制时遇到了问题。您可以使用 pthread_atfork() 处理程序清理它们(您使用 pthread,对吗?)。另一种方法是使用称为clone() 的进程创建低级函数。它使您几乎可以完全控制子进程应该从其父进程继承的确切内容。

[更新]

可能解决此问题的最简单方法是更改​​您的分叉方案。例如,您甚至可以在程序初始化所有资源之前创建一个新进程(fork)。 IE。在创建所有线程之前在 main() 中调用 fork()。在子进程中设置一个信号处理程序(例如用于 SIGUSR2 信号)和睡眠。当父进程需要执行某个新进程时,它会向您的子进程发送 SIGUSR2 信号。当 child 捕获它时,它会调用 fork/exec。

【讨论】:

  • @Shahbaz 嘿,看来你在我更新的同时更新了它,你的更改没有保存(已修复)。
  • 谢谢,但是如果您查看我的问题,您会发现我明确表示我无法在线程开始之前创建我的新进程。如果您想要混乱的细节,那是因为我的代码作为动态加载的 JNI 对象运行,而 Java 甚至在我的代码加载之前就已经创建了一百个线程......
  • @DavidGiven 我不是 JNI 方面的专家,我希望有办法做到这一点。如果没有办法尽早创建一个新进程,那就是一个问题,因为除了克隆(这是一个复杂的)之外,没有“线程安全”替代 fork/exec 的方法。如果没有其他方法可以完成任务,我建议要么编写执行 fork/exec 的单独守护程序并使用 IPC 与之对话,要么编写启动用户空间进程的 linux 内核模块(请参阅ibm.com/developerworks/linux/library/l-user-space-apps/… )。当然我提到的方法是黑客......
猜你喜欢
  • 2010-10-22
  • 1970-01-01
  • 2015-09-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-28
  • 1970-01-01
相关资源
最近更新 更多