【问题标题】:why fclose() is not always flushing the data to the disk?为什么 fclose() 并不总是将数据刷新到磁盘?
【发布时间】:2020-07-02 17:24:36
【问题描述】:

我在尝试将数据写入C.<br/> 中的文件时得到一些奇怪的结果 我认为fclose()*FILEflushes 的数据从其缓冲区关闭到file.<br/> 但由于某种原因,它有时只刷新我程序中的数据,而其他时候则不这样做。

例如:我会运行这段代码,在我的文件中我可以看到这两个字符串。 (完美,正是我想要的)
但是当我接下来运行代码 4 次时,它并没有改变我文件中的任何内容。然后当我再次运行它时,突然出现了 10 个额外的字符串(我上次运行程序时有 8 个,现在有 2 个)
(这4次只是一个例子,有时是5、8、10,甚至只是2次才看到输出出现)

我真的不明白这个? 每次运行程序后数据不应该可见吗? 在我运行程序的不同时间之间,这个缓冲区甚至保存在哪里,因为程序每次都完成,所以内存被释放,对吧?

(顺便说一句,我在 fclose() 之前和之后也尝试过fflush(fd),但这并没有解决问题)

#include <stdio.h>


int main(int argc, char const *argv[]) {
  FILE * fd;
  fd = fopen("test_file.txt", "a");
  fprintf(fd, "String 1\n");
  fprintf(fd, "String 2\n");
  fclose(fd);
  return 0;
}

【问题讨论】:

  • 如何查看生成的文件?您使用什么操作系统?什么文件系统?
  • 也许您打开用于查看文件内容的文本编辑器,而 那个 没有得到更新。您可能需要重新加载文件,并且某些编辑器会警告您文件内容已更改。
  • 测试返回值:if (fclose(fd)) perror("fclose");。当没有错误时,fclose() 包含等效于 fflush():请参阅 C11 7.21.5.1
  • 在这种情况下,理想情况下,您应该检查所有四个 i/o 语句的结果,以验证它们是否应该成功。
  • @WeatherVane 指出的关于检查 I/O 语句的返回值的内容至关重要。在发布的代码中,您无法知道 ANY 的调用是否有效。另请注意,fprintf() 语句中的数据在调用 fclose() 之前实际上可能不会写入文件,并且在您的所有其他调用似乎都成功之后,fclose() 可能会失败,因为数据被缓冲,直到fclose() 刷新它才真正写入。如果您的磁盘已满或发生其他问题,则所有之前的“成功”fwrite() 调用都将被清除。

标签: c file fclose fflush


【解决方案1】:

感谢 michael kerrisk,我们在linux man pages 中获得了我们需要的所有答案。如果您深入手册页并查看注释部分(在我的回答中列出),您会理解这种行为。

int fclose(FILE *stream); - 关闭流指向的文件流。

描述

fclose() 函数刷新stream 指向的流 (使用fflush() 写入任何缓冲的输出数据)并关闭 底层文件描述符。

注意事项

请注意,fclose() 仅刷新由提供的 user-space buffers C 库。确保数据物理存储在磁盘上 内核缓冲区也必须刷新,例如,sync()fsync().

所以它不足以确保写入和刷新缓冲区,我们还需要刷新内核缓冲区。如何 ?见下文:确保您也在代码中阅读我的 cmets!

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

int main(int argc, char const *argv[]) {
  FILE * fd;
  fd = fopen("test_file.txt", "a");
  fprintf(fd, "String 1\n");
  fprintf(fd, "String 2\n");
  
  if(fclose(fd) != 0){
/*********************************************************************
fclose failed and errno will be set to endicate the error. be aware 
that we shall 
not call fsync or any other stream manipulation on fd in this case 
because we will get undefined behavior !!!
**********************************************************************/
      ... do work but no more work on this stream (fd)...
  }

  /* ~~ be aware that this is a blocking call ! (see details below) ~~*/
  if(fsync(fd) == -1){
      /*fsync fails*/
      ....
  } 
  return 0;
}

fsync() 传输 ("flushes") 所有修改的内核数据 (i.e. 修改的缓冲区缓存页用于)文件所引用的文件 描述符fd 到磁盘设备(或其他永久存储设备) 以便可以检索所有更改的信息即使系统 崩溃或重新启动。这包括写入或刷新 磁盘缓存(如果存在)。 The call blocks 直到设备报告 传输已完成

更重要的是,总是更喜欢打电话给fsync(fd)而不是打电话给void sync(void);!为什么?

因为sync() 会导致所有对文件系统元数据的待修改和 要写入到底层文件系统的缓存文件数据。通常我们不想要这种行为(这会触发内核做一些不必要的额外工作!所以不要调用它。

此页面和答案不够大,无法包含所有详细信息和特殊情况以及所有错误代码!请参考手册页和下面的链接:

fclose

fsync

【讨论】:

  • fsync 采用文件描述符,而不是 FILE 指针。此外,在现代写时复制文件系统上,fsync 最终会一直刷新到根目录,因为元数据更新陷入风暴。出于这个原因,它实际上是对 zfs、hsfs 和许多其他工具的无操作。
猜你喜欢
  • 2014-10-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-07-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多