0

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

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

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

所以我考虑过明确使用fflush(3)before 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

4 回答 4

3

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

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

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

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

于 2010-02-07T01:05:03.227 回答
1

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

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

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

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

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

于 2010-02-07T00:56:30.017 回答
1

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

有点痛苦,但它应该工作。

于 2010-02-07T01:02:55.050 回答
0

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

于 2010-02-07T00:49:21.793 回答