Linux C,为什么在 STDIN 上执行 fcntl 会影响 STDOUT 和 STDERR?

4
我在使用函数fcntl处理stdin时遇到了问题,当我将stdin的FD状态标志设置为O_NONBLOCK时,它可以正常工作,但是带有副作用,即stdout和stderr的状态标志也已更改为O_NONBLOCK
我调查了函数fcntlSYSCALL_DEFINE3do_fcntl的源代码,但没有任何有用的信息。我已经在Stackoverflow和Google上搜索过,但也没有找到答案。我认为这可能与内核或glibc实现有关。
我的电脑是安装了gcc 4.6.3的x86_64 Ubuntu 12.04。
  int flag = 0;
  int value = O_NONBLOCK;
  int fd = open("./tmp", O_RDONLY);

  if(-1 == (flag = fcntl(fd, F_GETFL)))
      fprintf(stdout, "%d:%s\n", errno, strerror(errno));

  flag = fcntl(stdin->_fileno, F_GETFL);
  flag = fcntl(stderr->_fileno, F_GETFL);
  if(-1 == (flag = fcntl(stdout->_fileno, F_GETFL)))
      fprintf(stdout, "%d:%s\n", errno, strerror(errno));

  flag = fcntl(stdout->_fileno, F_SETFL, flag | O_NONBLOCK);

  flag = fcntl(fd, F_GETFL);
  flag = fcntl(stdin->_fileno, F_GETFL);
  flag = fcntl(stdout->_fileno, F_GETFL);
  flag = fcntl(stderr->_fileno, F_GETFL);

  flag = fcntl(stdin->_fileno, F_SETFL, flag | O_APPEND);

  flag = fcntl(fd, F_GETFL);
  flag = fcntl(stdin->_fileno, F_GETFL);
  flag = fcntl(stdout->_fileno, F_GETFL);
  flag = fcntl(stderr->_fileno, F_GETFL);

  close(fd);

这是我为这个问题编写的代码。

贴一些代码怎么样? - Crowman
你尝试过在不使用tty的情况下运行程序吗?(例如:echo "" | ./your_program - SheetJS
是的。谢谢!我只是在终端中输入 gdb a.out 命令,并显示标志,当将 O_NONBLOCK 设置为标准输出时,我发现标准输入和标准错误流也都变成了 O_NONBLOCK。这有点让人困惑。~ - Incarnation P. Lee
2个回答

5

登录过程(或终端打开过程)通常使用的一种“技巧”是以读写模式打开文件描述符0(标准输入),然后将其复制到文件描述符1和2(标准输出和标准错误)。这意味着:

  1. 三个标准文件描述符共享相同的打开文件描述符。
  2. 您可以向标准输入写入,并从标准输出或标准错误读取。
  3. 更改一个文件描述符的文件描述符信息会更改其他文件描述符的信息。

fcntl()函数的F_GETFL和F_SETFL选项与打开文件描述符相关。F_GETFD和F_SETFD选项与文件描述符相关。

给定的打开文件描述符可能有多个与之关联的文件描述符,可以在单个进程中(经过dup()dup2()操作)或跨进程中(因为fork()操作)产生。


是的。谢谢,CSAPP说有三种打开的文件类型,文件描述符、xxx(我忘了它的名字,它保存引用计数和偏移量)和vnode。只有一个打开的文件描述符确切地说。非常感谢,帮了大忙。 - Incarnation P. Lee

2

在Jonathan的答案之后,这里有一些更为合理的代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

void show_nonblock_status(void) {
    char streams[3][7] = {"stdin", "stdout", "stderr"};

    for ( int i = 0; i < 3; ++i ) {
        int flag = fcntl(i, F_GETFL);
        if ( flag == -1 ) {
            perror("error getting flags");
            exit(EXIT_FAILURE);
        }

        if ( flag & O_NONBLOCK ) {
            printf("O_NONBLOCK is set for %s\n", streams[i]);
        } else {
            printf("O_NONBLOCK is not set for %s\n", streams[i]);
        }
    }
}

int main(void) {
    show_nonblock_status();

    int flag = fcntl(1, F_GETFL);
    if ( flag == -1 ) {
        perror("error getting flags");
        exit(EXIT_FAILURE);
    }

    flag = fcntl(1, F_SETFL, flag | O_NONBLOCK);
    if ( flag == -1 ) {
        perror("error getting flags");
        exit(EXIT_FAILURE);
    }

    show_nonblock_status();

    return 0;
}

以下是在各种用途下的输出结果:

paul@local:~/src/c/scratch$ ./fct
O_NONBLOCK is not set for stdin
O_NONBLOCK is not set for stdout
O_NONBLOCK is not set for stderr
O_NONBLOCK is set for stdin
O_NONBLOCK is set for stdout
O_NONBLOCK is set for stderr
paul@local:~/src/c/scratch$ cat testfile | ./fct
O_NONBLOCK is not set for stdin
O_NONBLOCK is not set for stdout
O_NONBLOCK is not set for stderr
O_NONBLOCK is not set for stdin
O_NONBLOCK is set for stdout
O_NONBLOCK is set for stderr
paul@local:~/src/c/scratch$ cat testfile | ./fct 2>/dev/null
O_NONBLOCK is not set for stdin
O_NONBLOCK is not set for stdout
O_NONBLOCK is not set for stderr
O_NONBLOCK is not set for stdin
O_NONBLOCK is set for stdout
O_NONBLOCK is not set for stderr
paul@local:~/src/c/scratch$

在第一种情况下,文件描述符相同,因此所有三个都设置了O_NONBLOCK
在第二种情况下,一个文件被管道传输到stdin,因此它与已经设置了O_NONBLOCKstdoutstderr有不同的文件描述符。
在第三种情况下,一个文件会被管道传输到stdin,而stderr则被重定向到/dev/null,所以所有3个文件描述符都不同,且只为stdout设置了O_NONBLOCK

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