Используя self-pipe, как я могу избежать остановки цикла чтения в read ()?

Я пытаюсь использоватьсамостоятельно трубы хитрость, чтобы получить портативную реализацию (через Solaris, MacOSX, Linux, BSD) моего приложения.

Так в дополнение к двум трубам дляstderr а такжеstdout который я использую, чтобы получить вывод раздвоенного ребенка (я не используюexec у ребенка ребенок выполняет тот же код, что и родитель), у меня есть канал для сигналов (enum {SIG_PIPE, STDOUT_PIPE, STDERR_PIPE, MAX_PIPE} обеспечивает символические имена).

O_NONBLOCK устанавливается на трубы, до вызоваhandle_child_output().у ребенка есть концы записиstderr а такжеstdout трубы и продолжает использоватьprintf() и друзья, эффективно пишущие в каждую соответствующую трубу (setvbuf используется для отключения буферизации внутри дочернего элемента).

Код, который нужно выполнить, немного длинен, как и трюк с самоконтролем в целом. Это уже сжатая форма. Итак, позвольте мне попытаться объяснить, что должно произойти и где оно застряло.

I необходимость чтобы получить статус выхода, и я должен быть в состоянии выяснить, был ли ребенок закончен с сигналом или через выход. Обработка этих условий в другом месте. Что важно, так этоhandle_child_output() возвращает код выхода ребенка внутриint указал наpstatus.

Внешнийdo-while зациклитьсяhandle_child_output() создастFD_SET использовать вselect вызов. Он добавляет конец чтения сигнальной трубы плюс конец чтенияstderr а такжеstdout трубка от ребенка.

Тогда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);
}

Ответы на вопрос(0)

Ваш ответ на вопрос