You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Linux(POSIX)下转发stdin至子进程并监控内容的实现难题

解决方案

核心思路

要解决你的两个需求,关键在于让父进程仅在子进程存活且stdin有数据时才读取并转发,同时利用管道关闭来传递EOF。具体要点:

  1. 通过SIGCHLD信号监听子进程退出,避免父进程无意义阻塞。
  2. 使用poll监听stdin的可读事件,仅当有数据时才读取,而非主动阻塞读取。
  3. 当父进程从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);
}

关键细节解释

  1. SIGCHLD信号处理:

    • 子进程退出时会触发SIGCHLD,信号处理函数设置gChildExited标志,父进程循环中检查该标志,及时退出循环,避免无意义阻塞。
    • 处理函数中调用waitpid(-1, nullptr, WNOHANG)清除僵尸进程,防止信号重复触发。
  2. stdin读取控制:

    • 使用poll监听stdin的POLLIN事件,仅当有数据时才读取,避免父进程主动阻塞在read上。
    • 设置100ms超时,定期检查子进程状态,防止子进程退出后父进程仍阻塞在poll中。
  3. EOF转发:

    • 当read(STDIN_FILENO,...)返回0时,说明父进程读到了stdin的EOF,此时关闭管道的写端。子进程从管道读取时,会收到EOF(read返回0),完成转发。
  4. 管道错误处理:

    • 若子进程已退出,父进程写管道会返回EPIPE错误,此时直接退出循环,避免无效操作。

内容的提问来源于stack exchange,提问作者yhgu2000

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 12:13:09