C++ 如何从popen()转换为fork(),而不复制进程内存?

C++ 如何从popen()转换为fork(),而不复制进程内存?,c++,pipe,exec,fork,popen,C++,Pipe,Exec,Fork,Popen,我有一个多线程C++03应用程序,目前使用popen()在一个新进程中再次调用自身(相同的二进制文件)和ssh(不同的二进制文件),并读取输出,但是,当移植到Android NDK时,这会带来一些问题,例如没有访问ssh的权限,所以我在Dropbearssh中链接到我的应用程序,试图避免这个问题。此外,我当前的popen解决方案要求将stdout和stderr合并到一个有点混乱的FD中,我不想再这样做了 我认为管道代码可以通过使用fork()来简化,但我想知道如何删除fork的子级中不需要的父级

我有一个多线程C++03应用程序,目前使用
popen()
在一个新进程中再次调用自身(相同的二进制文件)和ssh(不同的二进制文件),并读取输出,但是,当移植到Android NDK时,这会带来一些问题,例如没有访问
ssh
的权限,所以我在Dropbear
ssh
中链接到我的应用程序,试图避免这个问题。此外,我当前的popen解决方案要求将stdout和stderr合并到一个有点混乱的FD中,我不想再这样做了

我认为管道代码可以通过使用
fork()
来简化,但我想知道如何删除fork的子级中不需要的父级堆栈/内存?以下是旧工作代码的一个片段:

#include <iostream>
#include <stdio.h>
#include <string>
#include <errno.h>

using std::endl;
using std::cerr;
using std::cout;
using std::string;

void
doPipe()
{
  // Redirect stderr to stdout with '2>&1' so that we see any error messages
  // in the pipe output.
  const string selfCmd = "/path/to/self/binary arg1 arg2 arg3 2>&1";
  FILE *fPtr = ::popen(selfCmd.c_str(), "r");
  const int bufSize = 4096;
  char buf[bufSize + 1];

  if (fPtr == NULL) {
    cerr << "Failed attempt to popen '" << selfCmd << "'." << endl;
  } else {
    cout << "Result of: '" << selfCmd << "':\n";

    while (true) {
      if (::fgets(buf, bufSize, fPtr) == NULL) {
        if (!::feof(fPtr)) {
          cerr << "Failed attempt to fgets '" << selfCmd << "'." << endl;
        }
        break;
      } else {
        cout << buf;
      }
    }

    if (pclose(fPtr) == -1) {
      if (errno != 10) {
        cerr << "Failed attempt to pclose '" << selfCmd << "'." << endl;
      }
    }

    cout << "\n";
  }
}
以这种方式创建的两种子进程在我的应用程序中具有不同的任务:

  • SSH连接到另一台机器并调用服务器,该服务器将与作为客户端的父级通信
  • 使用rsync计算签名、增量或合并文件

  • 首先,
    popen
    是一个非常薄的包装,位于
    fork()
    之上,然后是
    exec()
    [还有一些调用
    pipe
    dup
    等来管理管道的末端]

    其次,内存仅以“写时复制”内存的形式复制,这意味着除非其中一个进程写入某个页面,否则实际的物理内存将在两个进程之间共享

    当然,这确实意味着操作系统必须创建每4KB 4-8字节的内存映射[在典型情况下](可能加上一些内部操作系统数据,以跟踪该页面和内容的副本数量-但只要页面与父进程保持相同,子页面就使用父进程内部数据)。与创建新流程并将可执行文件加载到新流程中所涉及的所有其他工作相比,这只占了相当少的时间。由于您几乎马上就要执行
    exec
    ,因此父进程的内存不会有太多变化,因此几乎不会发生什么变化


    我的建议是,如果
    popen
    有效,请继续使用
    popen
    。如果由于某种原因,
    popen
    不能完全实现您想要的功能,请使用
    fork
    +
    exec
    ——但请确保您知道这样做的原因。

    内存空间很便宜。复制它很便宜<代码>叉子很便宜。(也许你把内存空间和内存搞混了?)你认为
    popen
    是如何创建新流程的?你的另一个选择是某种,但很难说,因为你还没有真正解释孩子要做什么。它会运行其他程序吗?如果我的进程分配了500 MB的堆空间,我调用fork,那么子进程会得到父进程堆空间的副本吗?不能复制空间。子项以与父项相同的地址映射开始。映射的页面将被共享,直到任何一个进程修改它们。因此,如果父进程在子进程运行时接触大多数页面,那么您将不必要地得到每个此类页面的两个副本,原始页面(现在在子进程中未使用)和父进程中修改的页面。如果可能的话,我希望避免这种潜在的开销,因为孩子只需要很少的父内存。如果您可以在调用
    fork
    后不久调用
    exec
    (或相关函数),那么您就可以避免这种内存污染。您甚至可以使用命令行参数告诉自己以某种特定模式运行(即,当前正在运行的同一可执行文件)。
    #include <iostream>
    #include <stdio.h>
    #include <string>
    #include <errno.h>
    
    using std::endl;
    using std::cerr;
    using std::cout;
    using std::string;
    
    int
    selfAction(int argc, char *argv[], int &outFD, int &errFD)
    {
      pid_t childPid; // Process id used for current process.
    
      // fd[0] is the read end of the pipe and fd[1] is the write end of the pipe.
      int fd[2];      // Pipe for normal communication between parent/child.
      int fdErr[2];   // Pipe for error  communication between parent/child.
    
      // Create a pipe for IPC between child and parent.
      const int pipeResult = pipe(fd);
    
      if (pipeResult) {
        cerr << "selfAction normal pipe failed: " << errno << ".\n";
    
        return -1;
      }
    
      const int errorPipeResult = pipe(fdErr);
    
      if (errorPipeResult) {
        cerr << "selfAction error pipe failed: " << errno << ".\n";
    
        return -1;
      }
    
      // Fork - error.
      if ((childPid = fork()) < 0) {
        cerr << "selfAction fork failed: " << errno << ".\n";
    
        return -1;
      } else if (childPid == 0) { // Fork -> child.
        // Close read end of pipe.
        ::close(fd[0]);
        ::close(fdErr[0]);
    
        // Close stdout and set fd[1] to it, this way any stdout of the child is
        // piped to the parent.
        ::dup2(fd[1],    STDOUT_FILENO);
        ::dup2(fdErr[1], STDERR_FILENO);
    
        // Close write end of pipe.
        ::close(fd[1]);
        ::close(fdErr[1]);
    
        // Exit child process.
        exit(main(argc, argv));
      } else { // Fork -> parent.
        // Close write end of pipe.
        ::close(fd[1]);
        ::close(fdErr[1]);
    
        // Provide fd's to our caller for stdout and stderr:
        outFD = fd[0];
        errFD = fdErr[0];
    
        return 0;
      }
    }
    
    
    void
    doFork()
    {
      int argc = 4;
      char *argv[4] = { "/path/to/self/binary", "arg1", "arg2", "arg3" };
      int outFD = -1;
      int errFD = -1;
      int result = selfAction(argc, argv, outFD, errFD);
    
      if (result) {
        cerr << "Failed to execute selfAction." << endl;
    
        return;
      }
    
      FILE *outFile = fdopen(outFD, "r");
      FILE *errFile = fdopen(errFD, "r");
    
      const int bufSize = 4096;
      char buf[bufSize + 1];
    
      if (outFile == NULL) {
        cerr << "Failed attempt to open fork file." << endl;
    
        return;
      } else {
        cout << "Result:\n";
    
        while (true) {
          if (::fgets(buf, bufSize, outFile) == NULL) {
            if (!::feof(outFile)) {
              cerr << "Failed attempt to fgets." << endl;
            }
            break;
          } else {
            cout << buf;
          }
        }
    
        if (::close(outFD) == -1) {
          if (errno != 10) {
            cerr << "Failed attempt to close." << endl;
          }
        }
    
        cout << "\n";
      }
    
      if (errFile == NULL) {
        cerr << "Failed attempt to open fork file err." << endl;
    
        return;
      } else {
        cerr << "Error result:\n";
    
        while (true) {
          if (::fgets(buf, bufSize, errFile) == NULL) {
            if (!::feof(errFile)) {
              cerr << "Failed attempt to fgets err." << endl;
            }
            break;
          } else {
            cerr << buf;
          }
        }
    
        if (::close(errFD) == -1) {
          if (errno != 10) {
            cerr << "Failed attempt to close err." << endl;
          }
        }
    
        cerr << "\n";
      }
    }