这个问题是“为什么 select() 说 stdout 和 stderr 已准备好读取并且 stdin 已准备好写入?”的后续问题,该问题作为“写入 stdin 并从 stdout 读取”的重复项而关闭(UNIX/LINUX/C 编程)'.
虽然这个问题解释了为什么我们可以从用于 stdout 和 stderr 的文件描述符中读取数据,但当前的问题是不同的。这个问题关注的是为什么 stdout 和 stderr 在从终端手动输入输入时可以在程序中读取,但在将标准输入重定向到程序时却不然。
这是重现该行为的代码。以下程序尝试检查 stdin、stdout 和 stderr 是否已准备好读取。
#include <stdio.h>
#include <sys/select.h>
int main(void)
{
struct timeval tv;
fd_set fds;
int fd;
tv.tv_sec = 10;
tv.tv_usec = 0;
FD_ZERO(&fds);
FD_SET(0, &fds);
FD_SET(1, &fds);
FD_SET(2, &fds);
select(3, &fds, NULL, NULL, &tv);
for (fd = 0; fd < 3; fd++) {
if (FD_ISSET(fd, &fds)) {
printf("fd %d ready\n", fd);
}
}
printf("tv: %ld seconds %ld microseconds\n", tv.tv_sec, tv.tv_usec);
return 0;
}
如果我等待大约 2 秒并在标准输入中输入一些数据,
select()
调用就会返回,这就是输出。
$ gcc readselect.c && ./a.out
hi
fd 0 ready
fd 1 ready
fd 2 ready
tv: 8 seconds 222132 microseconds
但是,如果我将标准输入重定向到我的程序,则程序显示只有 stdin 可供读取。
$ gcc readselect.c && (sleep 2; echo hi) | ./a.out
fd 0 ready
tv: 7 seconds 994713 microseconds
为什么现在行为发生了变化?为什么程序不再像前面的示例中那样说 stdout 和 stderr 也已准备好读取?
当你不重定向它们时,文件描述符 0、1 和 2 都是你的终端。您可以读取或写入其中任何一个 - 尝试一下! (严格来说,文件描述符不是终端,它只是指终端)
当您在终端中输入内容时,您的终端已准备好读取,因此所有 FD 0、1 和 2 都已准备好读取,因为它们都是您输入内容的同一终端。
当您重定向输入(文件描述符 0)时,即使您不输入内容,FD 0 也会立即准备好读取,因为它不会等待您输入内容。所以你的程序看到 FD 0 已准备好,读取 FD 0,然后退出。如果您在程序运行时输入内容,FD 1 和 2 会 准备就绪,但您永远没有机会这样做,因为您的程序立即退出。
在第一个示例中,文件描述符 0、1 和 2 全部连接到终端。正如您之前所发现的,当 stdin 连接到您的登录终端时,stdin 是可写的,并且 stdout 和 stderr 都是可读的。因此,当您键入
hi
时,所有三个描述符都变得可读。
在第二个示例中,配置有很大不同。标准输入是管道,几秒钟后,它从
hi
命令获取 echo
。同时,标准输出和标准错误都是终端,并且没有任何内容可读取,因为您没有输入任何内容。
如果您使用第二个示例调用,但在 2 秒睡眠结束之前输入了一些内容,您会发现文件描述符 1 和 2 可读,但文件描述符 0 不可读。
请注意,在 macOS Sierra(以及之前的 Mac OS X)上,超时值不会更改,而在 Linux 上,它会更改以指示剩余时间。
select()
的 Linux 手册页指出,根据 select()
的 POSIX 规范,这两种行为都是有效的。