为什么在没有编写者的命名管道上进行选择会无限期地阻塞?

Pri*_*tic 4 c++ linux select posix nonblocking

我在 read_fds 中使用单个命名管道 fd 调用 select。该命名管道没有写入器,并且仅以非阻塞、只读模式打开。我希望 select 返回,命名管道 fd 标记为准备读取,并且尝试从管道读取返回 0:

从联机帮助页中读取:

当尝试从空管道或 FIFO 读取时:

  • 如果没有进程打开管道进行写入,则 read() 应返回 0 > 指示文件结束。

但是,只能无限期地选择块。为什么会这样呢?

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

#include <stdexcept>
#include <thread>
#include <iostream>

int main()
{
    char buf[4096];

    // Create a named pipe
    auto err = mkfifo("/tmp/whatever",0666);
    if(err) {
        throw std::runtime_error(
                    std::string("Failed to create fifo ")+
                    strerror(errno));
    }

    std::thread reader_thread(
                [&](){
        auto fd = open("/tmp/whatever",O_RDONLY|O_NONBLOCK);
        if(fd < 0) {
            throw std::runtime_error("Failed to open fifo");
        }

        fd_set fds;
        while(1) {
            FD_ZERO(&fds);
            FD_SET(fd,&fds);
            std::cerr << "calling select" << std::endl;
            auto retval = select(fd+1,&fds,nullptr,nullptr,nullptr);
            if(retval < 0) {
                std::runtime_error("Failed to call select");
            }

            if(FD_ISSET(fd,&fds)) {
                auto read_bytes = read(fd,buf,4096);
                std::cerr << "read " << read_bytes << std::endl;
                if(read_bytes==0) {
                    break;
                }
            }
        }

        close(fd);
    });

    reader_thread.join();

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

too*_*ite 5

来自 POSIX 文档select

当对 O_NONBLOCK 清除的输入函数的调用不会阻塞时,无论该函数是否成功传输数据,描述符都应被视为已准备好读取。(该函数可能会返回数据、文件结束指示或指示其被阻塞的错误以外的错误,并且在每种情况下,描述符应被视为已准备好读取。

...

如果没有一个选定的描述符已准备好执行请求的操作,则 pselect() 或 select() 函数将阻塞,直到至少一个请求的操作准备就绪、发生超时或被信号中断。

pipe(7)联机帮助页(这是 FIFO 的基础对象):

如果引用管道写入端的所有文件描述符都已关闭,则尝试从管道读取(2)将看到文件结尾(读取(2)将返回0)。

注意现在完成时的使用!这意味着必须首先在两侧打开 FIFO 然后在写入器侧(对于您的应用程序)关闭以生成条件EOF

那么,除非 fifo 最终被作者关闭,否则为什么要select返回呢?(fifo-) 文件本身的设置是无关紧要的,这是有充分理由的:当使用最有效的方法一次读取多个字节时,它会在两侧打开之间引入竞争条件。这是命令管道的正常方式:启动读取器进程,然后启动写入器(使用命名管道时,这通常是完全不相关的程序)。

如果您想select早点返回,请使用timeout参数。但通常情况下,我们使用一个单独的线程,该线程可以通过信号终止(select有关更多信息,请参阅手册页)。

作为旁注:Linux/POSIX 的一个好处是,无论您使用 FIFO 还是文件或麦克风驱动程序,这并不重要。