select (2) no FIFO no macOS

1

No Linux, o programa incluído retorna de select e sai:

$ 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

No OS X, o select é bloqueado para sempre e o programa não sai. O comportamento do Linux corresponde à minha expectativa e parece estar de acordo com o seguinte bit da página de manual do POSIX para seleção:

A descriptor shall be considered ready for reading when a call to an input function with O_NONBLOCK clear would not block, whether or not the function would transfer data successfully. (The function might return data, an end-of-file indication, or an error other than one indicating that it is blocked, and in each of these cases the descriptor shall be considered ready for reading.)

Como a leitura (2) no final de leitura do fifo sempre retornará EOF , minha leitura diz que sempre deve ser considerado pronto por seleção.

O comportamento do macOS é bem conhecido ou esperado? Existe alguma outra coisa neste exemplo que leve à diferença de comportamento?

Uma outra observação é que, se eu remover as chamadas read , o select do macOS retornará. Este e alguns outros experimentos parecem indicar que uma vez que um EOF tenha sido lido no arquivo, ele não será mais marcado como pronto se select for chamado posteriormente.

Programa de exemplo

#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);
}
    
por Steven D 02.08.2017 / 02:48

2 respostas

0

No Macintosh, os pipes são tratados da mesma maneira que os soquetes, através da função read . Esse comportamento é induzido porque você está tentando read o arquivo select_test_tmp.fifo e bloqueia sempre que você tem entrada vazia. O EOF é, por padrão, gravado no pipe após cada operação de gravação.

Uma forma de verificar isso é executar cat select_test_tmp.fifo na sua linha de comando. Ele esperará até receber alguma entrada, antes de retornar - a menos que você a encerre primeiro.

    
por 11.08.2017 / 22:07
0

Suas hipóteses não estão corretas.

O comportamento esperado é que, uma vez que todos os escritores tenham fechado o fim de gravação do fifo que possuem, a extremidade de leitura fornece uma chamada única em read que retorna 0 bytes. Você não deve chamar read adicional depois: a extremidade de leitura deve ser fechada e aberta novamente para fazer isso, ou recriada se for um canal.

Se você seguir essa regra, terá um comportamento consistente em todas as plataformas.

Aqui está um exemplo de programa inspirado no seu que se comporta corretamente em ambas as plataformas:

#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);
}
    
por 05.01.2018 / 10:58