【问题标题】:Releasing C++ resources and fork-exec?释放 C++ 资源和 fork-exec?
【发布时间】:2011-01-11 20:01:53
【问题描述】:

我正在尝试使用 fork-exec 从我的 C++ 项目中生成一个新进程。我正在使用 fork-exec 来创建通往子进程的双向管道。但我担心我在分叉进程中的资源不会被正确释放,因为 exec 调用将完全接管我的进程并且不会调用任何析构函数。

我尝试通过抛出异常并在 main 末尾的 catch 块中调用 execl 来规避此问题,但此解决方案不会破坏任何单例。

有什么明智的方法可以安全地实现这一目标吗? (希望避免任何 atExit 黑客攻击)

例如:以下代码输出:

We are the child, gogo!
Parent proc, do nothing
Destroying object

即使分叉的进程也有一个单例副本,在我调用 execl 之前需要将其销毁。

#include <iostream>
#include <unistd.h>

using namespace std;

class Resources
{
public:
    ~Resources() { cout<<"Destroying object\n"; }
};

Resources& getRes()
{
    static Resources r1;
    return r1;
}

void makeChild(const string &command)
{
    int pid = fork();
    switch(pid)
    {
    case -1:
        cout<<"Big error! Wtf!\n";
        return;
    case 0:
        cout<<"Parent proc, do nothing\n";
        return;
    }
    cout<<"We are the child, gogo!\n";
    throw command;
}

int main(int argc, char* argv[])
{
    try
    {
        Resources& ref = getRes();
        makeChild("child");
    }
    catch(const string &command)
    {
        execl(command.c_str(), "");
    }
    return 0;
}

【问题讨论】:

  • 你说的是哪些资源?大多数文件描述符在 exec() 中存在,您可以将其标记为 close-on-exec,以便内核为您关闭它们。 pubs.opengroup.org/onlinepubs/009695399/functions/exec.html
  • 顺便说一句,如果在分叉的子代和父代中都调用了析构函数,它最终会调用一次构造函数(在父代中)和两次析构函数(在父代和子代中) )。
  • 我相信我在这里非常接近未定义的行为,但是 Resources 类代表了几个我用来将 C 库包装到 RAII 对象中的单例类。如果 fork 真的复制了整个进程状态,那么我可能应该在调用 exec() 之前调用 RAII 析构函数。如果资源在程序外部(如数据库连接),这当然会很疯狂。但由于它们是库,我相信它们应该在父进程和子进程中发布。 [如果有帮助,我目前正在将 ncurses、nscapi 和 SDL 包装成单例]

标签: c++ linux singleton exec


【解决方案1】:

很有可能您不需要forkexec 之间调用任何析构函数。是的,fork 会复制您的整个进程状态,包括具有析构函数的对象,而exec 会删除所有这些状态。但这真的重要吗?来自程序外部的观察者——在同一台计算机上运行的另一个不相关的进程——可以告诉析构函数没有在子进程中运行吗?如果无法判断,则无需运行它们。

即使外部观察者可以判断,在子进程中运行析构函数也可能是严重错误。常见的例子是:假设你在调用fork 之前给stdout 写了一些东西,但是它被缓冲在库中,因此实际上还没有被传递到操作系统。在这种情况下,您不得在子级的stdout 上调用fclosefflush,否则输出将发生两次! (这也是为什么如果exec 失败,您几乎肯定应该调用_exit 而不是exit。)

说了这么多,有两种常见的情况,您可能需要对孩子进行一些清理工作。一种是在exec 之后不应打开的文件描述符(不要将它们与 stdio 文件或 iostream 对象混淆)。处理这些问题的正确方法是在它们打开后尽快在它们上设置FD_CLOEXEC 标志(某些操作系统允许您在open 本身中执行此操作,但这并不通用) 和/或从 3 循环到某个大数调用 close (not fclose) 在孩子中。 (FreeBSD 有 closefrom,但据我所知,没有其他人有,这很遗憾,因为它真的很方便。)

另一种情况是系统全局线程锁,这是一个棘手且标准化程度不高的区域 - 可能最终由父子双方持有,然后通过exec 继承进入一个不知道它持有锁的进程。这就是pthread_atfork 的用途,但我读过它在实践中并不可靠。我能提供的唯一建议是“当您致电fork 时不要持有任何锁”,说声抱歉。

【讨论】:

  • 谢谢!这打破了我所有的误解。 (...特别是因为它允许我良心地忽略子进程中未释放的资源=)
  • 很好的解释;在 Linux 下可以模拟 closefrom(),但无论如何,这是一个非常丑陋的 hack,我不推荐它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-15
  • 2013-05-06
  • 2015-12-01
  • 1970-01-01
  • 2014-08-12
  • 2012-01-08
相关资源
最近更新 更多