gpt4 book ai didi

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

转载 作者:行者123 更新时间:2023-11-30 03:57:02 28 4
gpt4 key购买 nike

我有一个多线程 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";
}
}

到目前为止,这就是我为转换为 fork() 所做的粗略操作,但是 fork 不必要地复制了整个父进程内存空间。此外,它不太有效,因为父级从未在它从 pipe() 读取的 outFD 上看到 EOF。我还需要在哪里关闭 FD 才能使其正常工作?我如何在不提供二进制路径(在 Android 上不易获得)的情况下执行类似 execlp() 的操作,而是使用相同的二进制文件和带有新参数的空白图像重新开始?

#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";
}
}

在我的应用程序中有两种以这种方式创建的具有不同任务的子进程:

  1. SSH 到另一台机器并调用服务器,该服务器将与充当客户端的父级进行通信。
  2. 使用 rsync 计算签名、增量或合并文件。

最佳答案

首先,popen 是在 fork() 之后的 exec() [和一些对pipedup 等来管理管道的末端]。

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

这确实意味着,当然,操作系统必须创建一个内存映射,每 4KB [在典型情况下] 有 4-8 个字节(可能加上一些内部操作系统数据来跟踪该页面和内容的拷贝数量 -但只要页面与父进程保持相同,子页面就会使用父进程的内部数据)。与创建新进程和将可执行文件加载到新进程中所涉及的所有其他事情相比,这只是时间的一小部分。由于您几乎立即执行 exec,不会触及父进程的大部分内存,因此那里几乎不会发生什么。

我的建议是,如果 popen 有效,请继续使用 popen。如果 popen 由于某种原因没有完全按照您的意愿执行,则使用 fork + exec - 但请确保您知道原因是什么这样做是。

关于c++ - 如何从 popen() 转换为 fork() 而不是复制进程内存?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/28204644/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com