O_NONBLOCK是设置在文件描述符还是底层文件上的属性?

24
根据我在The Open Group网站上阅读fcntlopenreadwrite的内容,我的印象是无论是否在文件描述符上设置了O_NONBLOCK,也就是使用了非阻塞I/O,这应该是文件描述符的属性而不是底层文件的属性。作为文件描述符的属性意味着,例如,如果我复制一个文件描述符或打开另一个指向同一文件的描述符,则我可以在一个描述符上使用阻塞I/O,在另一个描述符上使用非阻塞I/O。
然而,通过对FIFO进行实验,似乎不可能同时拥有阻塞I/O描述符和非阻塞I/O描述符(因此是否设置了O_NONBLOCK是底层文件[即FIFO]的属性)。
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char **argv)
{
    int fds[2];
    if (pipe(fds) == -1) {
        fprintf(stderr, "`pipe` failed.\n");
        return EXIT_FAILURE;
    }

    int fd0_dup = dup(fds[0]);
    if (fd0_dup <= STDERR_FILENO) {
        fprintf(stderr, "Failed to duplicate the read end\n");
        return EXIT_FAILURE;
    }

    if (fds[0] == fd0_dup) {
        fprintf(stderr, "`fds[0]` should not equal `fd0_dup`.\n");
        return EXIT_FAILURE;
    }

    if ((fcntl(fds[0], F_GETFL) & O_NONBLOCK)) {
        fprintf(stderr, "`fds[0]` should not have `O_NONBLOCK` set.\n");
        return EXIT_FAILURE;
    }

    if (fcntl(fd0_dup, F_SETFL, fcntl(fd0_dup, F_GETFL) | O_NONBLOCK) == -1) {
        fprintf(stderr, "Failed to set `O_NONBLOCK` on `fd0_dup`\n");
        return EXIT_FAILURE;
    }

    if ((fcntl(fds[0], F_GETFL) & O_NONBLOCK)) {
        fprintf(stderr, "`fds[0]` should still have `O_NONBLOCK` unset.\n");
        return EXIT_FAILURE; // RETURNS HERE
    }

    char buf[1];
    if (read(fd0_dup, buf, 1) != -1) {
        fprintf(stderr, "Expected `read` on `fd0_dup` to fail immediately\n");
        return EXIT_FAILURE;
    }
    else if (errno != EAGAIN) {
        fprintf(stderr, "Expected `errno` to be `EAGAIN`\n");
        return EXIT_FAILURE;
    }

    return EXIT_SUCCESS;
}

这让我想到:是否可能将非阻塞I/O描述符和阻塞I/O描述符分配给同一文件,如果可能的话,是否取决于文件类型(普通文件、FIFO、块特殊文件、字符特殊文件、套接字等)?

我对这个问题感到疑惑,因为如果 O_NONBLOCK 的设置是底层文件的属性,则使用未在 oflags 中设置 O_NONBLOCK 的选项调用打开文件的操作,仍然可能返回带有 O_NONBLOCK 标志的文件描述符。 - Daniel Trebbien
1个回答

39

O_NONBLOCK是打开文件描述符的一个属性,而不是文件描述符本身或底层文件的属性。

是的,你可以为同一文件打开不同的文件描述符,其中一个是阻塞的,另一个是非阻塞的。

你需要区分FIFO(使用mkfifo()创建)和管道(使用pipe()创建)之间的区别。

请注意,阻塞状态是“打开文件描述符”的属性,在最简单的情况下,文件描述符和打开文件描述符之间存在一对一映射关系。 open()函数调用创建一个新的打开文件描述符和一个引用该打开文件描述符的新文件描述符。

当你使用dup()时,你有两个共享一个打开文件描述符的文件描述符,并且属性属于打开文件描述符。 fcntl()的说明指出,F_SETFL影响与文件描述符相关联的打开文件描述符。请注意,lseek()调整与文件描述符相关联的打开文件描述符的文件位置 - 因此它会影响从原始文件描述符复制的其他文件描述符。

将代码中的错误处理移除以缩短代码,你得到:

int fds[2];
pipe(fds);
int fd0_dup = dup(fds[0]);
fcntl(fd0_dup, F_SETFL, fcntl(fd0_dup, F_GETFL) | O_NONBLOCK);
现在,由于 dup()fd0_dupfds[0] 都引用相同的打开文件描述符,因此 fcntl() 操作会影响两个文件描述符。
if ((fcntl(fds[0], F_GETFL) & O_NONBLOCK)) { ... }
因此,这里观察到的行为是POSIX所要求的。

3
好的,所以"file descriptor"和"file description"是有区别的。两个文件描述符可以共享同一个文件描述符,标志位是描述符的属性。重新查看open的文档,函数会“创建一个打开的文件描述符”。我现在明白了。 - Daniel Trebbien
3
@DanielTrebbien说,一些标志位属于文件描述符,比如 FD_CLOEXEC - jfs
“O_NONBLOCK是打开文件描述符的属性,而不是文件描述符或底层文件的属性。”我不理解“打开文件描述符”、“文件描述符”和后面提到的“打开文件描述”的区别。它们都是独立的吗? - TomE
2
@TomE:存在“打开文件描述符”和“打开文件描述符”,两者是不同的。然而,第一句话中存在一个错误,我即将修复它。该错误是由于2021年01月08日在修订版6上进行的编辑引入的,我已经回滚了该编辑,因为它完全颠覆了我的意思。我不确定当时是如何错过这个编辑的,可能是我被通知了。现在已经重新修复了它。 - Jonathan Leffler
2
您可以在答案中的链接中找到有关“打开文件描述符”和“打开文件描述符”的信息,特别是指向open()的POSIX规范的链接。 - Jonathan Leffler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接