【问题标题】:fflush and 'no disk space left'fflush 和 '没有剩余磁盘空间'
【发布时间】:2011-01-13 23:52:39
【问题描述】:

我正在编写一个程序,某种数据库。当我阅读fclose(3) 的手册时,我发现它调用fflush(3)FILE* 缓冲区刷新到磁盘(实际上是操作系统缓冲区,但现在没关系,我们总是可以调用fsync(2))。

因为我正在编写数据库,所以很明显我想防止数据丢失。如果没有磁盘空间并且fclose(3) 中的fflush(3) 失败——我们将丢失我们的数据,因为

fclose() 出现错误后使用FILE* 将导致未定义的行为

所以我考虑在fclose(3) 之前明确使用fflush(3),警告用户磁盘空间不足并在一段时间后调用fflush(3)

我已阅读 C 标准并认为这是一个好主意。实际上,在 fflush 失败后,第二次调用总是返回 0(没有错误),但实际上什么也不做。 fsync 没有帮助我(我认为数据可能保存在 RAM 中)。

在这种情况下如何防止数据丢失?也许有一些经验法则。

这是我的测试代码:

#include <stdio.h>
int main()
{
    FILE *a = fopen("/tmp/1", "wb")
    if ( !a )
        perror("fopen");

    if ( fwrite("test", 1, 4, a) != 4 )
        perror("fwrite");  // always OK, cause data is buffered


    while( fflush(a) )  // ...second call will always return 0!
    {
        perror("fflush");  // if there is no disk space, I will get this perror, but ...
    }


    if ( fclose(a) )  // always ok, because calls only close(2)
        perror("fclose"); 

    return 0;
}

【问题讨论】:

  • 你也可以看看其他开源程序,看看他们是如何解决这个问题的,例如 SQLite。

标签: c fflush loss


【解决方案1】:

后续 fflush() 操作成功的原因是没有(新)数据要写入磁盘。第一个 fflush() 失败;这是悲惨的,但也是历史。后面的 fflush() 什么都没有做,就这么成功了。

如果您正在写入数据库,则必须小心每次写入 - 而不仅仅是在最后处理问题。根据您的数据的重要性,您可能需要通过各种方式来处理问题 - DBMS 之所以复杂是有原因的,而失败的写入就是其中之一。

解决问题的一种方法是为数据预先分配空间。正如其他人所指出的,经典的 Unix 文件系统允许稀疏文件(有空块且没有为其分配磁盘空间的文件),因此您实际上必须在需要分配的每个页面上写入一些数据。然后,您只需在扩展空间时担心“磁盘已满”问题 - 您知道何时执行此操作,并且可以谨慎处理该故障。

在基于 Unix 的系统上,有多种系统调用可以帮助您同步磁盘上的数据,以及“打开”选项等。其中包括“O_DSYNC”和相关值。但是,如果您正在扩展文件,即使使用花哨的同步选项,它们仍然会导致“空间不足”失败。当您确实遇到该故障时,您必须等待空间可用(因为您可能要求用户告诉您何时可用),然后再次尝试写入。

【讨论】:

    【解决方案2】:

    您可以预先分配一些合理数量的磁盘空间。写入、刷新和 fsync 一些二进制零(或其他),然后返回您所在的位置。必要时冲洗并重复。并记得在必要时截断。

    有点痛,但应该可以。

    【讨论】:

      【解决方案3】:

      fflush 只会将 C 库内部缓冲区刷新到操作系统,因此 fflush 不能保证不会丢失数据。

      重复调用 fflush(没有中间 fwrite)将无济于事,因为您已经将数据刷新到操作系统一次。第二个 fflush 调用将返回 SUCCESS,因为没有 nothing 可以刷新到操作系统。如果 fflush 由于硬盘已满而失败,则您已经丢失了一些数据。

      要将数据刷新到磁盘,您需要使用 fsync。

      如果硬盘已满,那你就不走运了。防止数据丢失的唯一方法是现在保持您的进程处于活动状态(以及内存中的数据:在用户空间/内核文件缓冲区中),直到您在磁盘上找到一些空间来进行 fsync 。现在,如果断电,您丢失数据。

      简而言之,如果您的硬盘已满,您无法保证不丢失数据。

      【讨论】:

      • 我做了一个测试。 fflushed 到整个磁盘,然后清理它并调用 fsync() — 测试失败,我丢失了数据。
      • 我的意思是,如果您的硬盘已满,您无法保证不会丢失数据。 fsync 仅在有空间可以刷新时才起作用。这就像试图在已经装满的玻璃杯中添加更多的水。如果 fflush 失败,您就已经丢失了数据。所以 fsync 是否失败是另一个问题。
      【解决方案4】:

      在执行任何操作之前,您可以 fseek(3) 到文件末尾(假设您知道长度)。这样您就可以消除由于磁盘空间不足而导致失败的可能性。

      【讨论】:

      • 许多文件系统允许稀疏文件,因此查找实际上并不会分配存储空间。
      • lseek() 实际上并没有分配任何空间。您需要在支持它的文件系统上使用 fallocate()。
      • fallocate() 在 Cygwin 和微软的 _chsize_s 上真的很慢。我通过使用 fwrite 写入 8M 块来预分配磁盘空间——它快了 3 倍。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-03-31
      • 2020-11-28
      • 1970-01-01
      • 2020-01-14
      • 2010-12-10
      • 2010-11-06
      相关资源
      最近更新 更多