fflush和'没有磁盘空间'

时间:2010-02-07 00:30:14

标签: c fflush loss

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

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

  

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

所以我考虑在fflush(3)之前明确使用fclose(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;
}

4 个答案:

答案 0 :(得分:3)

后续fflush()操作成功的原因是没有(新)数据写入磁盘。第一个fflush()失败了;这是悲惨的,但历史。随后的fflush()无关,所以它成功了。

如果您要写入数据库,则必须小心每次写入 - 而不仅仅是在最后处理问题。根据您的数据的重要程度,您可能需要经历各种各样的回转来处理问题 - 这就是为什么DBMS很复杂,写入失败就是其中之一。

解决问题的一种方法是预先为数据分配空间。正如其他人所说,经典的Unix文件系统允许稀疏文件(有空块的文件没有为它们分配磁盘空间),所以你实际上必须在你需要分配的每个页面上写一些数据。然后你只需要在扩展空间时担心“磁盘已满”的问题 - 而且你知道什么时候这样做,你可以小心处理这个失败。

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

答案 1 :(得分:1)

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

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

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

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

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

答案 2 :(得分:1)

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

有点痛,但应该有效。

答案 3 :(得分:0)

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