【问题标题】:stdio to terminal after close(STDOUT_FILENO) behavior关闭(STDOUT_FILENO)行为后到终端的 stdio
【发布时间】:2017-05-17 20:14:18
【问题描述】:

我想知道为什么取消注释以下程序中的第一个 printf 语句会改变其后续行为:

#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

int main() {
  //printf("hi from C \n");

  // Close underlying file descriptor:
  close(STDOUT_FILENO);

  if (write(STDOUT_FILENO, "Direct write\n", 13) != 13) // immediate error detected.
    fprintf(stderr, "Error on write after close(STDOUT_FILENO): %s\n", strerror(errno));

  // printf() calls continue fine, ferror(stdout) = 0 (but no write to terminal):
  int rtn;
  if ((rtn = printf("printf after close(STDOUT_FILENO)\n")) < 0 || ferror(stdout)) 
    fprintf(stderr, "Error on printf after close(STDOUT_FILENO)\n");
  fprintf(stderr, "printf returned %d\n", rtn);
  // Only on fflush is error detected:
  if (fflush(stdout) || ferror(stdout))
    fprintf(stderr, "Error on fflush(stdout): %s\n", strerror(errno));
}

如果没有第一个 printf,后续的 printf rtns 34 就好像没有发生错误,即使从标准输出用户缓冲区到底层 fd 的连接已经关闭。只有在手动 fflush(stdout) 上才会报告错误。 但是当第一个 printf 打开时,下一个 printf 会像我预期的那样报告错误。 当然,在任何一种情况下关闭 STDOUT_FILENO fd 后,都不会向终端写入任何内容(通过 printf)。

我知道close(STDOUT_FILENO) 首先在这里很愚蠢;这是我偶然发现的一个实验,我认为在这些领域知识渊博的人可能会从中看到对我们有启发性的东西..

我在 Linux 上使用 gcc。

【问题讨论】:

  • stdout 处于错误状态,因为您关闭了底层描述符 STDOUT_FILENO 事情与 fclose(stdout) 正常工作。

标签: c linux buffer stdio


【解决方案1】:

如果你strace 两个程序,似乎stdio 工作,以便在第一次 写入时,它会检查带有fstat 的描述符以找出连接到的文件类型stdout - 如果它是一个终端,那么stdout 应该是line-buffered,如果它是别的东西,那么stdout 将被做成块缓冲。如果您在第一个 printf 之前调用 close(1);,现在初始的 fstat 将返回 EBADF,并且由于 1 不是指向字符设备的文件描述符,stdout 被设置为块缓冲。

在我的计算机上,缓冲区大小为 8192 字节 - 可以缓冲许多字节以写入 stdout在发生第一次故障之前


如果你取消注释第一个printffstat(1, ...)成功并且Glibc检测到stdout连接到终端; stdout 设置为 line-buffered,因此因为 printf after close(STDOUT_FILENO)\n 以换行符结尾,缓冲区将被立即刷新 - 这将导致立即错误。

【讨论】:

  • 奇怪的是,打印 1024 个字符然后导致状态改变......循环 printf 很容易表现出这一点。
  • 是的,通过您描述的fstat 调用对缓冲区的内部设置似乎与man setbuf 所说的一致:“setvbuf() 函数只能在打开流和在对其执行任何其他操作之前。”如果我将第一个 printf 替换为 setlinebuf(stdout); 它的行为相同。
  • 所以一个类似的警告似乎适用于printf,因为它出现在man 2 write 内的注释中:“从 write() 成功返回并不能保证数据已被提交到盘。”用“printf()”替换“write()”,用“内核文件缓冲区”替换“disk”。这里fflushfsync 是类似的。我不知道close(fd) 的类似物会切断从内核文件缓冲区到磁盘的连接..
  • 在了解了其工作原理的内部原理之后,在我看来,stdio 库在首次发现底层 fd 错误时应该报告错误。因此,当对fstat 的初始调用返回EBADF 时,我认为printf 应该返回-1,而不是仅仅将stdio 缓冲区设置为块缓冲,就好像它正在写入磁盘文件一样。
猜你喜欢
  • 2021-07-12
  • 2015-11-21
  • 2018-08-19
  • 1970-01-01
  • 2013-06-21
  • 2023-03-23
  • 1970-01-01
  • 1970-01-01
  • 2014-11-23
相关资源
最近更新 更多