从`forkpty`子进程读取:`ls /`输出产生`EIO`

时间:2017-03-30 03:49:56

标签: c ipc execvp pty

我正在尝试使用forkptyexecvp一个ls /,然后从父进程读取其输出并写入stdout。

我得到了它,并且在Valgrind下运行它也没有显示任何错误。

编辑:Valgrind没有显示内存错误。 Valgrind下的最后read仍会产生EIO

但是,上一个read始终返回-1并将errno设置为EIO。我一直在研究和阅读手册页,但我仍然不明白为什么会发生这种情况:

  EIO    I/O  error.  This will happen for example when the process is in
         a background process group, tries to read from  its  controlling
         terminal,  and  either it is ignoring or blocking SIGTTIN or its
         process group is orphaned.  It may also occur when  there  is  a
         low-level I/O error while reading from a disk or tape.

我还注意到,如果我抓住SIGCHLD信号,我可以知道ls何时退出,但是如果我列出了一个更大的目录,例如/usr/bin,那么我得到的信号是ls子进程,即使成功后有很多read个进程。

以下是输出示例:

$ ./a.out
bin    dev         initrd.img.old  libx32      opt   sbin  usr
boot   etc         lib             lost+found  proc  srv   var
build  home        lib32           media       root  sys   vmlinuz
core   initrd.img  lib64           mnt         run   tmp   vmlinuz.old
# read: Input/output error

我错过了一些明显的东西吗?

#include <pty.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

int main(int argc, char* argv[]) {
    int master_fd;
    pid_t child_pid = forkpty(&master_fd, NULL, NULL, NULL);

    if (child_pid == -1) {
        perror("# forkpty");
        return EXIT_FAILURE;
    }
    else if (!child_pid) { /* CHILD */
        char* ls_argv[] = {
            "ls",
            "/",
            NULL,
        };

        execvp(ls_argv[0], ls_argv);
        perror("# execvp");
        abort();
    }
    else { /* PARENT */
        uint8_t buffer[BUFSIZ];
        ssize_t read_count;

        while ((read_count = read(master_fd, buffer, BUFSIZ)) > 0) {
            uint8_t* ptr = buffer;
            size_t ptr_len = (size_t) read_count;

            while (ptr_len > 0) {
                ssize_t write_count = write(STDOUT_FILENO, ptr, ptr_len);

                if (write_count == -1) {
                    perror("# write");
                    return EXIT_FAILURE;
                }

                ptr += write_count;
                ptr_len -= write_count;
            }
        }

        if (read_count == -1) {
            perror("# read");
            return EXIT_FAILURE;
        }

        if (close(master_fd) == -1) {
            perror("# close");
            return EXIT_FAILURE;
        }

        int child_status;

        if (waitpid(child_pid, &child_status, 0) == -1) {
            perror("# waitpid");
            return EXIT_FAILURE;
        }

        if (!WIFEXITED(child_status)) {
            fprintf(stderr, "# main: subprocess did not exit normally\n");
            return EXIT_FAILURE;
        }

        return child_status;
    }
}

2 个答案:

答案 0 :(得分:1)

pty驱动程序调用内核函数使slave pty一方无效,从那时起返回EIO,直到从属端关闭。这种情况总是在主控方被控制过程关闭时发生。

此过程是当今所有unix变体的标准。这是一种解决方法,可以避免在主端离开后继续使用设备的进程。即使您使用新进程再次附加主端,旧的从进程也会被阻塞,直到它关闭打开的描述符。

答案 1 :(得分:1)

这个问题似乎有三个部分:

a)在这种情况下(上次阅读时)EIO是否是有效错误?

b)为什么SIGCHLD在预期的I / O完成之前收到的很多,

c)为什么Valgrind不会发生错误?

a)由于读取类似于子进程已退出的损坏管道上的读取,因此预计最后一次读取将失败。与EIO相比,EOF似乎是一个更有意义的错误。(当从磁盘或磁带读取时出现低级I / O错误时,也可能出现错误。)

b)CPU处理远远超过I / O引起了这种情况。收到SIGCHLD(并设置了一个标志以表明孩子已经退出),父进程应该在读完孩子写入slave-pty的信息并因此安全地忽略EIO后,期望读取失败。

c)我不确定这一点。虽然Valgrind可能会减慢一些时间来为I / O处理提供足够的时间,但最终的读取仍然会失败(返回-1),在本例中是EIO。