2

我正在尝试利用自管道技巧来获得我的应用程序的可移植实现(跨 Solaris、MacOSX、Linux、BSD)。

因此,除了两个管道用于获取分叉子节点stderrstdout输出(我在子节点中使用 no exec,子节点执行与父节点相同的代码)之外,我还有信号管道(enum {SIG_PIPE, STDOUT_PIPE, STDERR_PIPE, MAX_PIPE}提供符号名称)。

  • O_NONBLOCK在调用之前设置在管道上handle_child_output()
  • 孩子拥有stderrandstdout管道的写端并继续使用printf()和朋友,有效地写入每个相应的管道(setvbuf用于关闭孩子内部的缓冲)。

接下来的代码有点长,就像一般的自管道技巧一样。已经是浓缩的形式了。所以让我试着解释一下应该发生什么以及它在哪里卡住了。

需要收集退出状态,并且必须能够找出孩子是通过信号终止还是通过退出终止。这些情况的处理在其他地方进行。相关的是返回指向 byhandle_child_output()内的孩子的退出代码。intpstatus

外层do-while循环 inhandle_child_output()将设置FD_SETselect调用中使用。它添加了信号管道读取端以及子管道的读取stderrstdout

然后if(FD_ISSET(fds[SIG_PIPE], &rd))检查信号管道是否包含任何新内容并将其排出,处理任何潜在信号。

之后的for循环遍历剩余的文件描述符以查看是否有任何待处理的内容,然后通过模仿它在父级各自stdio通道上获得的内容进行处理。

第二个循环中的读取调用是它停止的地方。

症状是父母卡在读取调用中:

(gdb) bt 1
#0  0x00007f2daaa9e530 in __read_nocancel () from /lib64/libpthread.so.0

就好像在读取信号管道和其他管道之间存在竞争条件。在检查文件描述符时,孩子似乎已经退出,因此最终成为僵尸,因为父母仍然卡在其中read()并且永远不会到达其中一个wait()调用。

我究竟做错了什么?if(exitloop) break;在循环之前添加一个for以跳出外do循环是否合法while?在我看来,这可能会在管道中留下未读内容,不是吗?


#define __MAX__(x,y) ((x) > (y) ? (x) : (y))
int childpid;
typedef enum { READ, WRITE, BOTH } pipefd_type_t;

static void avoid_zombie(int* pstatus)
{
    int temp;
    pstatus = (pstatus) ? pstatus : &temp;
    if(0 > childpid && 0 != childpid) {
        kill(childpid, SIGKILL); /* kill the child */
    }
    wait(pstatus); /* wait to avoid lingering zombies */
}

static void select_signal_handler(int sig)
{
    int savedErrno = errno;
    const int sigw = sig;
    if(0 > write(sigpipe[WRITE], &sigw, sizeof(sigw))) {
        avoid_zombie(NULL);
        _exit(EXIT_FAILURE); /* actual code also shows error etc */
    }
    errno = savedErrno;
}

void handle_child_output(int *pstatus)
{
    enum {SIG_PIPE, STDOUT_PIPE, STDERR_PIPE, MAX_PIPE};
    fd_set rd;
    int ready, n = 0, fds[MAX_PIPE];
    size_t i, exitloop, sigint;
    struct sigaction sa;
    struct {
        int sig;
        struct sigaction oldsa;
    } old_sigactions[3];
    old_sigactions[0].sig = SIGINT;
    old_sigactions[1].sig = SIGCHLD;
    old_sigactions[2].sig = SIGQUIT;
    /* fds have been initialized at this point (read-ends) */

    for(i = 0; i < sizeof(old_sigactions)/sizeof(old_sigactions[0]); i++) {
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = SA_RESTART;
        sa.sa_handler = select_signal_handler;
        if(0 > sigaction(old_sigactions[i].sig, &sa, &old_sigactions[i].oldsa)) {
            avoid_zombie(pstatus);
            _exit(EXIT_FAILURE); /* actual code also shows error etc */
        }
    }

    do {
        sigint = 0;
        exitloop = 0;
        FD_ZERO(&rd);
        for(i = 0; i < MAX_PIPE; i++) {
            if(fds[i] >= FD_SETSIZE) {
                avoid_zombie(pstatus);
                _exit(EXIT_FAILURE); /* actual code also shows error etc */
            }
            FD_SET(fds[i], &rd);
            n = __MAX__(n, fds[i]);
        }

        while(0 > (ready = select(n+1, &rd, NULL, NULL, NULL)))
            if (EINTR == errno) continue;

        if(0 > ready) {
            avoid_zombie(pstatus);
            _exit(EXIT_FAILURE); /* actual code also shows error etc */
        }

        if(FD_ISSET(fds[SIG_PIPE], &rd)) {
            do { /* drain the signal pipe */
                int sig = -1;
                if(0 > read(fds[SIG_PIPE], &sig, sizeof(sig))) {
                    if(EAGAIN == errno)
                        break;
                    else {
                        avoid_zombie(pstatus);
                        _exit(EXIT_FAILURE); /* actual code also shows error etc */
                    }
                }
                switch(sig) {
                case SIGINT:
                    if(0 > childpid && 0 != childpid) {
                        kill(childpid, SIGINT); /* pass to child */
                        wait(pstatus);
                    }
                    sigint++; exitloop++;
                    break;
                case SIGCHLD:
                    exitloop++;
                    break;
                }
            } while(1);
        }

        for(i = STDOUT_PIPE; i < MAX_PIPE; i++) {
            if(FD_ISSET(fds[i], &rd)) {
                do {
                    const size_t max_tries = 5;
                    char buf[0x1000];
                    ssize_t bytesWritten, bytesRead = read(fds[i], buf, sizeof(buf));
                    if(0 == bytesRead)
                        break;
                    int outchan = STDERR_FILENO;
                    if(0 > bytesRead) {
                        /* Retry (inner do-while loop) if we get an EAGAIN */
                        if(EAGAIN == errno) break;
                        /* fatal error */
                        avoid_zombie(pstatus);
                        _exit(EXIT_FAILURE); /* actual code also shows error etc */
                    }
                    if(STDOUT_PIPE == i)
                        outchan = STDOUT_FILENO;
                    bytesWritten = write(outchan, buf, bytesRead);
                    if(0 > bytesWritten) {
                        /* Retry if we get an EAGAIN */
                        if(EAGAIN == errno) {
                            size_t tries;
                            for(tries = 0; tries < max_tries; tries++) {
                                bytesWritten = write(outchan, buf, bytesRead);
                                if((0 > bytesWritten) && (EAGAIN == errno))
                                    continue;
                                break;
                            }
                        }
                        if(0 > bytesWritten) {
                            avoid_zombie(pstatus);
                            _exit(EXIT_FAILURE); /* actual code also shows error etc */
                        }
                    }
                    if(bytesWritten < bytesRead) {
                        const ssize_t bytesToWrite = bytesRead - bytesWritten;
                        /* try to write the remainder */
                        ssize_t bytesWritten2 = write(outchan, &buf[bytesWritten], bytesToWrite);
                        if((0 > bytesWritten2) || (bytesWritten2 != bytesToWrite)) {
                            /* fatal error */
                            avoid_zombie(pstatus);
                            _exit(EXIT_FAILURE); /* actual code also shows error etc */
                        }
                    }
                } while(1);
            }
        }
    } while(0 == exitloop);
    /* restore old signal handlers */
    for(i = 0; i < sizeof(old_sigactions)/sizeof(old_sigactions[0]); i++) {
        if (sigaction(old_sigactions[i].sig, &old_sigactions[i].oldsa, NULL) == -1) {
            avoid_zombie(pstatus);
            _exit(EXIT_FAILURE); /* actual code also shows error etc */
        }
    }
    avoid_zombie(pstatus);
}
4

0 回答 0