我正在编写一个程序,某种数据库。当我阅读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;
}
答案 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)。这样你就可以消除由于磁盘空间不足而导致失败的可能性。