在 Linux 上,包含的程序从以下位置返回select
并退出:
$ gcc -Wall -Wextra select_test.c -o select_test
$ ./select_test
reading from read end
closing write end
first read returned 0
second read returned 0
selecting with read fd in fdset
select returned
在 OS X 上,select
永远阻塞并且程序不会退出。 Linux 的行为符合我的预期,并且似乎符合 POSIX 手册页中 select 的以下部分:
当对 O_NONBLOCK 清除的输入函数的调用不会阻塞时,无论该函数是否成功传输数据,描述符都应被视为已准备好读取。 (该函数可能返回数据、文件结束指示或指示其被阻塞的错误以外的错误,并且在每种情况下,描述符应被视为已准备好读取。)
由于 fifo 读取端的 read(2) 将始终返回EOF
,因此我的阅读表明它应该始终被 select 视为就绪。
macOS 的行为是众所周知的还是符合预期的?此示例中是否还有其他因素导致行为差异?
进一步注意的是,如果我删除这些read
调用,macOS 就会select
返回。这个实验和其他一些实验似乎表明,一旦从文件中读取了 EOF,如果select
稍后调用它,它将不再被标记为就绪。
示例程序
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define FILENAME "select_test_tmp.fifo"
int main()
{
pid_t pid;
int r_fd, w_fd;
unsigned char buffer[10];
fd_set readfds;
mkfifo(FILENAME, S_IRWXU);
pid = fork();
if (pid == -1)
{
perror("fork");
exit(1);
}
if (pid == 0)
{
w_fd = open(FILENAME, O_WRONLY);
if (w_fd == -1)
{
perror("open");
exit(1);
}
printf("closing write end\n");
close(w_fd);
exit(0);
}
r_fd = open(FILENAME, O_RDONLY);
if (r_fd == -1)
{
perror("open");
exit(1);
}
printf("reading from read end\n");
if (read(r_fd, &buffer, 10) == 0)
{
printf("first read returned 0\n");
}
else
{
printf("first read returned non-zero\n");
}
if (read(r_fd, &buffer, 10) == 0)
{
printf("second read returned 0\n");
}
else
{
printf("second read returned non-zero\n");
}
FD_ZERO(&readfds);
FD_SET(r_fd, &readfds);
printf("selecting with read fd in fdset\n");
if (select(r_fd + 1, &readfds, NULL, NULL, NULL) == -1)
{
perror("select");
exit(1);
}
printf("select returned\n");
unlink(FILENAME);
exit(0);
}
答案1
在 Macintosh 上,通过该函数,管道的处理方式与套接字相同read
。之所以会出现这种行为,是因为您正在尝试访问read
该select_test_tmp.fifo
文件,并且只要您有空输入,它就会阻塞。默认情况下,每次写操作后都会将 EOF 写入管道。
验证这一点的一种方法是cat select_test_tmp.fifo
从命令行运行。它会等到获得一些输入后才返回——除非您先终止它。
答案2
你的假设不正确。
预期的行为是,一旦所有写入器都关闭了它们所持有的 fifo 的写入端,读取端就会提供一次read
返回 0 字节的调用。您不应该read
在之后调用额外的:应该关闭并再次打开读取端来执行此操作,或者如果它是管道则重新创建。
如果您遵守此规则,您将在跨平台上获得一致的行为。
下面是一个受您启发的示例程序,它在两个平台上都能正确运行:
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/select.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define FILENAME "select_test_tmp.fifo"
int main()
{
pid_t pid;
int r_fd, w_fd;
unsigned char buffer[8192];
fd_set master_set, working_set;
char const * message = "Hello there\n";
mkfifo(FILENAME, S_IRWXU);
pid = fork();
if (pid < 0) exit(1);
if (pid == 0) {
w_fd = open(FILENAME, O_WRONLY);
if (w_fd == -1) exit(1);
write(w_fd, message, strlen(message));
close(w_fd);
exit(0);
}
r_fd = open(FILENAME, O_RDONLY);
if (r_fd < 0) exit(1);
FD_ZERO(&master_set);
FD_SET(r_fd, &master_set);
int finished = 0;
while (!finished) {
memcpy(&working_set, &master_set, sizeof(master_set));
int rc = select(r_fd + 1, &working_set, NULL, NULL, NULL);
if (rc < 1) exit(1); // No timeout so rc == 0 is also an error here
for (int fd = 0; fd < r_fd +1; ++fd) {
if (FD_ISSET(fd, &working_set)) {
if (fd == r_fd) { // Our fifo
// Read data and print it in stdout
ssize_t nb_bytes = read(r_fd, buffer, 8192);
if (nb_bytes < 0) {
exit(1);
}
else if (0 == nb_bytes) {
finished = 1;
}
else {
write(1,buffer,nb_bytes);
}
}
}
}
}
unlink(FILENAME);
exit(0);
}