Linux(POSIX)下转发stdin至子进程并监控内容的实现难题
解决方案
核心思路
要解决你的两个需求,关键在于让父进程仅在子进程存活且stdin有数据时才读取并转发,同时利用管道关闭来传递EOF。具体要点:
- 通过
SIGCHLD信号监听子进程退出,避免父进程无意义阻塞。 - 使用
poll监听stdin的可读事件,仅当有数据时才读取,而非主动阻塞读取。 - 当父进程从stdin读到EOF时,关闭管道写端,子进程会从管道读取到EOF。
修正后的代码实现
#include <iostream> #include <vector> #include <unistd.h> #include <poll.h> #include <sys/wait.h> #include <signal.h> #include <cstring> volatile sig_atomic_t gChildExited = 0; void sigchldHandler(int sig) { gChildExited = 1; // 清除僵尸进程,避免信号重复触发 waitpid(-1, nullptr, WNOHANG); } int main(int argc, char* argv[]) { // 假设gCmd和newArgv已提前初始化 std::string gCmd = "cat"; std::vector<char*> newArgv = {const_cast<char*>("cat"), nullptr}; int childStdin[2]; if (pipe(childStdin) == -1) { std::cerr << "PIPE CREATION FAILED\n"; return -1; } // 设置SIGCHLD信号处理 struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = sigchldHandler; sigaction(SIGCHLD, &sa, nullptr); auto pid = fork(); if (pid == -1) { std::cerr << "HIJACKER FORK FAILED\n"; return -1; } if (pid == 0) { close(childStdin[1]); dup2(childStdin[0], STDIN_FILENO); close(childStdin[0]); // 复制后关闭原fd return execvp(gCmd.c_str(), newArgv.data()); } close(childStdin[0]); FILE* stdinSave = fopen("stdin_dump.txt", "w"); // 假设用于保存原始stdin if (!stdinSave) { std::cerr << "FAILED TO OPEN STDIN SAVE FILE\n"; close(childStdin[1]); waitpid(pid, nullptr, 0); return -1; } while (true) { // 检查子进程是否已退出 if (gChildExited) { break; } // 监听stdin是否有数据可读 pollfd pfd = {STDIN_FILENO, POLLIN, 0}; int pollRet = poll(&pfd, 1, 100); // 100ms超时,定期检查子进程状态 if (pollRet == -1) { if (errno == EINTR) { continue; // 被信号中断,重试 } std::cerr << "POLL FAILED\n"; break; } else if (pollRet == 0) { continue; // 超时,重新检查子进程状态 } // stdin有数据可读 char buf[4096]; ssize_t bytesRead = read(STDIN_FILENO, buf, sizeof(buf)); if (bytesRead == -1) { if (errno == EINTR) { continue; } std::cerr << "READ STDIN FAILED\n"; break; } else if (bytesRead == 0) { // 读到EOF,关闭管道写端,转发EOF给子进程 close(childStdin[1]); break; } // 保存原始stdin数据 fwrite(buf, 1, bytesRead, stdinSave); // 将数据写入子进程stdin管道 ssize_t bytesWritten = write(childStdin[1], buf, bytesRead); if (bytesWritten == -1) { if (errno == EPIPE) { // 子进程已退出,管道读端关闭 break; } std::cerr << "WRITE TO CHILD FAILED\n"; break; } } fclose(stdinSave); close(childStdin[1]); int status; if (waitpid(pid, &status, 0) == -1) { std::cerr << "HIJACKER WAIT FAILED\n"; return -1; } return WEXITSTATUS(status); }
关键细节解释
SIGCHLD信号处理:
- 子进程退出时会触发
SIGCHLD,信号处理函数设置gChildExited标志,父进程循环中检查该标志,及时退出循环,避免无意义阻塞。 - 处理函数中调用
waitpid(-1, nullptr, WNOHANG)清除僵尸进程,防止信号重复触发。
- 子进程退出时会触发
stdin读取控制:
- 使用
poll监听stdin的POLLIN事件,仅当有数据时才读取,避免父进程主动阻塞在read上。 - 设置100ms超时,定期检查子进程状态,防止子进程退出后父进程仍阻塞在
poll中。
- 使用
EOF转发:
- 当
read(STDIN_FILENO,...)返回0时,说明父进程读到了stdin的EOF,此时关闭管道的写端。子进程从管道读取时,会收到EOF(read返回0),完成转发。
- 当
管道错误处理:
- 若子进程已退出,父进程写管道会返回
EPIPE错误,此时直接退出循环,避免无效操作。
- 若子进程已退出,父进程写管道会返回
内容的提问来源于stack exchange,提问作者yhgu2000
相关产品推荐
相关产品推荐

