为什么 poll() returns 立即在 fifo 上的常规文件和块上?

Why poll() returns immediately on regular files and blocks on fifo?

我检查了这段代码好几次,不明白为什么 poll() return 立即执行?

此处文件已打开以供读取,应等待事件。如何让它等待输入?

#include <iostream>

#include <poll.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>


using namespace std;

ssize_t read_out_to_the_end(int fd){
   char chunk[1024];
   ssize_t ret = 0, n;
   while((n = ::read(fd, chunk, sizeof chunk)) > 0){
      ret += n;
      cerr << "read chunk: " << n << " | ";
      cerr.write(chunk, n);
      cerr << endl;
   }
   if (n < 0) {
       cerr << "err in read" << endl;
   }
   else if (ret == 0){
      cerr << "nothing to read" << endl;
   }
   return ret;
}

int main() {
   int bininfd = open("bin-in", O_RDONLY | O_CREAT);//, 0644/*S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH*/);
   if (bininfd < 0) {
      perror("err in open(binin)");
      return -1;
   }

   struct pollfd pollfds[] = {
         {bininfd, POLLIN, 0},
   };
   auto&[pfd] = pollfds;

   while (1) {
      pfd.revents = 0;  // cleanup, shouldn't it be redundant
      int pollret = poll(pollfds, 1, -1);
      if (pollret > 0) {
         if (pfd.revents & POLLIN) {
            cerr << "(pfd.revents & POLLIN)" << endl;
            read_out_to_the_end(pfd.fd);
         }
      } else if (pollret == 0) {
         cerr << "poll timed out" << endl;
         continue;
      } else {
         cerr << "check for error" << endl;
         continue;
      }
   }
}

输出是

(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
nothing to read
(pfd.revents & POLLIN)
............... etc ....................

live example

更新:

  1. read_out_to_the_end() 已修复。感谢@RemyLebeau
  2. 如我所料,它在 fifos 上有效(阻塞),但在常规文件上无效。为什么?

read_out_to_the_end() 有几个问题:

  • ret 未初始化。

  • while 循环递增 n 而它应该分配给它。但是如果 while 循环到达 EOF,即使在到达 EOF 之前实际读取了数据,if( n == 0) 也会为真。

  • chunk 可能以空值终止,但它也可能接收空值,具体取决于输入数据。因此不应使用 operator<< 将其写入 cerr(为什么不 cout?),而应使用 cerr.write(),以便您可以将读取的实际字节数传递给它。

试试这个:

ssize_t read_out_to_the_end(int fd){
   char chunk[1024];
   ssize_t ret = 0, n;
   while((n = ::read(fd, chunk, sizeof chunk)) > 0){
      ret += n;
      cerr << "read chunk: " << n << " | ";
      cerr.write(chunk, n);
      cerr << endl;
   }
   if (n < 0) {
       cerr << "err in read" << endl;
   }
   else if (ret == 0){
      cerr << "nothing to read" << endl;
   }
   return ret;
}

int main() {
   int bininfd = open("bin-in", O_RDONLY | O_CREAT);//, 0644/*S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH*/);
   if (bininfd < 0) {
      perror("err in open(binin)");
      return -1;
   }

   pollfd pfd = {};
   pfd.fd = bininfd;
   pfd.events = POLLIN;

   while (true) {
      pfd.revents = 0;  // cleanup, shouldn't it be redundant
      int pollret = poll(&pfd, 1, -1);
      if (pollret > 0) {
         if (pfd.revents & POLLIN) {
            cerr << "(pfd.revents & POLLIN)" << endl;
            read_out_to_the_end(pfd.fd);
         }
      } else if (pollret == 0) {
         cerr << "poll timed out" << endl;
         continue;
      } else {
         cerr << "poll error " << errno << endl;
         break;
      }
   }
}

此外,在旁注中,open() documentation 说:

The mode argument specifies the file mode bits be applied when a new file is created. This argument must be supplied when O_CREAT or O_TMPFILE is specified in flags; if neither O_CREAT nor O_TMPFILE is specified, then mode is ignored. The effective mode is modified by the process's umask in the usual way: in the absence of a default ACL, the mode of the created file is (mode & ~umask). Note that this mode applies only to future accesses of the newly created file; the open() call that creates a read-only file may well return a read/write file descriptor.

poll()select() 从不阻止常规文件。他们总是 return 作为 "ready" 的常规文件。如果你想用 poll()tail -f 做的事,那你就走错了路。

引用自 SUSv4 standard:

The poll() function shall support regular files, terminal and pseudo-terminal devices, FIFOs, pipes, sockets and [OB XSR] STREAMS-based files. The behavior of poll() on elements of fds that refer to other types of file is unspecified.

Regular files shall always poll TRUE for reading and writing.

由于在常规文件上使用 poll()select() 几乎没有用,较新的界​​面已尝试对此进行补救。在 BSD 上,您可以将 kqueue(2)EVFILT_READ 一起使用,而在 Linux 上 inotify(2)IN_MODIFY 一起使用。 Linux 上较新的 epoll(7) 界面将在您尝试观看常规文件时简单地显示 EPERM 错误。

不幸的是,这些都不是标准的。