【问题标题】:How can I wait for both a file-descriptor and child state change simultanously?如何同时等待文件描述符和子状态更改?
【发布时间】:2022-07-02 14:46:52
【问题描述】:

在 Linux 中,可以使用 selectpollepoll 等待任何 FD。 也可以使用waitwaitpidwaitid 等待子进程更改状态。 但是,我想不出一种组合这些操作的方法,即阻止调用进程,直到 某些 FD 准备好子进程更改状态。 p>

我可以使用轮询,通过反复调用非阻塞epoll 然后waitid,但这很浪费。

可以为子进程创建pidfd(被epoll 接受),但pidfd 只支持等待子进程终止,而我希望等待任何状态变化(特别是对于 ptrace 停止)。

这在 Linux 中是不可能的吗?

【问题讨论】:

  • ptrace 向进程发送信号。您可以从信息开始并详细说明。 Linux 也有signalfd 机制来捕捉信号。当然你知道sigaction接口有一种返回子进程状态的方法。

标签: linux epoll ptrace waitpid


【解决方案1】:

您可以使用 signalfd() 等待任何子状态更改并进行虚拟读取,然后使用 waitpid() 获取实际状态:

sigset_t mask, old_set;
sigemptyset(&mask);
sigaddset(&mask, SIGCHLD);
sigprocmask(SIG_BLOCK, &mask, &old_set);

int sigfd = signalfd(-1, &mask, SFD_CLOEXEC);
if (sigfd == -1) {
    perror("signalfd");
    return 1;
}

for (int i = 0; i < 10; ++i) {
    pid_t pid = fork();
    if (pid == -1) {
        perror("fork");
    }
    if (pid == 0) {
        // Child process: restore blocked signals before exec() etc
        sigprocmask(SIG_SETMASK, &old_set, NULL);
        sleep(i % 3);
        switch (i % 3) {
            case 0:
                raise(SIGSTOP);
                break;
            case 1:
                raise(SIGABRT);
                break;
        }
        exit(i);
    }
    printf("Spawned child %i with pid %u\n", i, pid);
}

for (;;) {
    struct pollfd fds[] = {
        { .fd = STDIN_FILENO, .events = POLL_IN },
        { .fd = sigfd,        .events = POLL_IN }
    };
    if (poll(fds, sizeof(fds)/sizeof(*fds), -1) == -1) {
        perror("poll");
        break;
    }

    if (fds[0].revents & POLL_IN) {
        char buf[4096];
        int ret = read(STDIN_FILENO, buf, sizeof(buf));
        printf("Data from stdin: ");
        fflush(stdout);
        write(STDOUT_FILENO, buf, ret);
    }

    if (fds[1].revents & POLL_IN) {
        struct signalfd_siginfo fdsi;

        read(sigfd, &fdsi, sizeof(fdsi));

        for (;;) {
            int status;
            pid_t pid = waitpid(-1, &status, WNOHANG | WUNTRACED | WCONTINUED);
            if (pid == -1) {
                if (errno != ECHILD) {
                    perror("waitpid");
                }
                break;
            }
            if (pid == 0) {
                break;
            }

            printf("Child %u ", pid);
            if (WIFEXITED(status)) {
                printf("exited with status %i\n", WEXITSTATUS(status));
            } else if (WIFSIGNALED(status)) {
                printf("terminated by signal %i\n", WTERMSIG(status));
            } else if (WIFSTOPPED(status)) {
                printf("stopped by signal %i\n", WSTOPSIG(status));
            } else if (WIFCONTINUED(status)) {
                printf("continued\n");
            } else {
                printf("status unknown\n");
            }
        }
    }
}

close(sigfd);

【讨论】: