在 macOS 上的 FIFO 上选择(2)

在 macOS 上的 FIFO 上选择(2)

在 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。之所以会出现这种行为,是因为您正在尝试访问readselect_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);
}

相关内容