当我尝试posix_spawn
在 MacOS 上启动一个进程(最初是 Chrome)时,遇到了一种情况。此代码应该也适用于 Linux,但我尚未测试。
我想为描述符 1(STDOUT)、2(STDERR)以及 3(写入)和 4(读取)创建管道。
它不适用于 Chrome,我正在尝试用一个简单的代码来测试这种情况:
#!/usr/bin/env python3
import os
import sys
import time
import fcntl
print("fcntl 3:", fcntl.fcntl(3, fcntl.F_GETFD))
print("fcntl 4:", fcntl.fcntl(4, fcntl.F_GETFD))
fd4 = os.fdopen(4, 'wb')
while True:
print('Cycle')
time.sleep(2)
while chunk := os.read(3, 1024):
print('REQ:', chunk.decode('utf8', errors='backslashreplace'), file=sys.stderr)
fd4.write(b'FOO')
我正在尝试检查描述符是否可访问。描述符 3 已准备好读取,并且可以从中读取数据。
但是描述符 4 无法访问,尝试打开它或使用 fcntl 进行测试会导致错误“OSError: [Errno 9] Bad file descriptor”。Chrome 也会在同样的尝试中失败。
posix_spawn
以下是导致这种情况的C++ 代码(稍微简化了) :
#include "pipes.h"
#include <cerrno>
#include <cstring>
#include <spawn.h>
#include <stdio.h>
#include <sys/poll.h>
#include <sys/wait.h>
extern "C" char** environ;
constexpr int CDP_WRITE_FILENO = 3;
constexpr int CDP_READ_FILENO = 4;
void RunBrowser(const char* exePath, const std::vector<const char*>& launchArgs) {
posix_spawn_file_actions_t childFdActions{};
posix_spawn_file_actions_init(&childFdActions);
DEFER(&childFdActions) {
posix_spawn_file_actions_destroy(&childFdActions);
};
int stdoutPipe[2];
int stderrPipe[2];
int cdpWritePipe[2];
int cdpReadPipe[2];
if (pipe(stdoutPipe) || pipe(stderrPipe) || pipe(cdpWritePipe) || pipe(cdpReadPipe)) {
throw TBrowserError() << "Could not create a pipe: " << std::strerror(errno);
}
posix_spawn_file_actions_addclose(&childFdActions, stdoutPipe[0]);
posix_spawn_file_actions_addclose(&childFdActions, stderrPipe[0]);
posix_spawn_file_actions_addclose(&childFdActions, cdpWritePipe[1]);
posix_spawn_file_actions_addclose(&childFdActions, cdpReadPipe[0]);
posix_spawn_file_actions_adddup2(&childFdActions, stdoutPipe[1], STDOUT_FILENO);
posix_spawn_file_actions_adddup2(&childFdActions, stderrPipe[1], STDERR_FILENO);
posix_spawn_file_actions_adddup2(&childFdActions, cdpWritePipe[0], CDP_WRITE_FILENO);
posix_spawn_file_actions_adddup2(&childFdActions, cdpReadPipe[1], CDP_READ_FILENO);
posix_spawn_file_actions_addclose(&childFdActions, stdoutPipe[1]);
posix_spawn_file_actions_addclose(&childFdActions, stderrPipe[1]);
posix_spawn_file_actions_addclose(&childFdActions, cdpWritePipe[0]);
posix_spawn_file_actions_addclose(&childFdActions, cdpReadPipe[1]);
pid_t childPid{};
if (const int ret = posix_spawn(&childPid, exePath,
&childFdActions, nullptr,
const_cast<char* const*>(launchArgs.data()),
environ)) {
throw TBrowserError() << "Failed to spawn a process: " << std::strerror(ret);
}
close(stdoutPipe[1]);
close(stderrPipe[1]);
close(cdpWritePipe[0]);
close(cdpReadPipe[1]);
int status;
do {
const int ret = waitpid(childPid, &status, WUNTRACED | WCONTINUED);
if (ret == -1) {
kill(childPid, SIGKILL);
throw TBrowserError() << "waitpid error: -1";
}
if (WIFEXITED(status)) {
const auto retCode = WEXITSTATUS(status);
if (retCode == 0) {
break;
}
throw TBrowserError() << std::format("Browser process failed with exit code {}", retCode);
}
if (WIFSIGNALED(status)) {
throw TBrowserError() << "Browser process killed by signal " << WTERMSIG(status);
}
if (WIFSTOPPED(status)) {
std::cerr << "Browser process stopped by signal " << WSTOPSIG(status) << std::endl;
}
if (WIFCONTINUED(status)) {
std::cerr << "Browser process is continued" << std::endl;
}
} while (WIFEXITED(status) || WIFSIGNALED(status));
}
这只是一种猜测:
首先,
pipe(stdoutPipe)
占用父进程中的文件描述符 3 和 4。(其他pipe()
调用占用后面的文件描述符,但这并不相关。)然后在客户端:
p_s_f_a_addclose(&childFdActions, stdoutPipe[0]);
关闭描述符 3。p_s_f_a_adddup2(&childFdActions, cdpWritePipe[0], CDP_WRITE_FILENO);
使描述符 3 可用。p_s_f_a_adddup2(&childFdActions, cdpReadPipe[1], CDP_READ_FILENO);
使描述符 4 可用。p_s_f_a_addclose(&childFdActions, stdoutPipe[1]);
关闭描述符 4。最后一个操作使得文件描述符在客户端不可用。
作为一种解决方案,您可以
adddup2
先进行调用,然后添加所有addclose
调用,但前提是它们不会关闭文件描述符 0 到 4 之一。