C 如何确保数据写入物理介质?

C 如何确保数据写入物理介质?,c,linux,system-calls,fsync,C,Linux,System Calls,Fsync,我有一个由脚本调用的程序。该程序将大量数据写入磁盘上的文件,然后终止。一旦脚本运行完毕,它就会切断整个系统的电源 我遇到的问题是,文件没有完整地写入。如果它是4GiB文件,那么当我稍后查看它时,磁盘上实际只有2GiB左右。我能够可靠地确保所有数据都被写入的唯一方法是,一旦程序完成,就在退出之前让它休眠一小段时间,但这是一种非常糟糕和不可靠的黑客行为,我不想使用。以下是我最新尝试涉及的一些示例代码: int main () { FILE *output; output = fope

我有一个由脚本调用的程序。该程序将大量数据写入磁盘上的文件,然后终止。一旦脚本运行完毕,它就会切断整个系统的电源

我遇到的问题是,文件没有完整地写入。如果它是4GiB文件,那么当我稍后查看它时,磁盘上实际只有2GiB左右。我能够可靠地确保所有数据都被写入的唯一方法是,一旦程序完成,就在退出之前让它休眠一小段时间,但这是一种非常糟糕和不可靠的黑客行为,我不想使用。以下是我最新尝试涉及的一些示例代码:

int main () {
    FILE *output;
    output = fopen("/logs/data", "w");

    [fwrite several GiB of data to output]

    fflush(output);

    int fdo = open("/logs", O_RDONLY);
    fsync(fdo);

    fclose(output);
    close(fdo);

    return 0;
}
我最初尝试用一个文件描述符构建我的文件,并对所使用的描述符(/logs/data)调用fsync(),但是这产生了同样的问题。根据规范:

调用fsync()不一定确保 包含该文件的目录也已到达磁盘。为此,一个 目录的文件描述符上的显式fsync()也是 需要

这让我找到了上面的代码,为包含我的数据文件的目录创建了一个特定的文件描述符,并对其调用fsync()。然而,结果是一样的。我真的不明白为什么会发生这种情况,因为fsync()应该是阻塞的:

该调用将一直阻塞,直到 设备报告传输已完成

此外,如您所见,我在文件中添加了一个fflush()认为fsync()可能只是同步了以前刷新过的数据,但这并没有对情况造成任何影响

在结束程序之前,我需要以某种方式验证数据是否已写入物理介质,我不知道如何做到这一点。我看到有一些文件,比如/sys/block/[device]/[partition]/stat,它可以告诉我还有多少脏块要写,我可以等待该值达到0,但这似乎不是解决这个简单问题的好方法。此外,如果磁盘上有任何其他程序在运行,那么我也不想等待它们同步数据,因为我只关心完整性此特定文件和stat文件之间没有区别

编辑 根据建议,我尝试了两次fsync(),首先在文件上,然后在目录上:

int main () {
    FILE *output;
    int fd = open("/logs/data", O_WRONLY | O_CREAT, 660);
    output = fdopen(fd, "w");

    [fwrite several GiB of data to output]

    fsync(fd);
    int fdo = open("/logs", O_RDONLY);
    fsync(fdo);

    fclose(output);
    close(fd);
    close(fdo);

    return 0;
}

这产生了一些有趣的结果。对于4GiB(4294967296字节)文件,磁盘上数据的实际大小为4294963200,正好与总值相差1页文件(4096字节)。它似乎非常接近于一个可行的解决方案,但仍然不能保证每个字节的数据。

您是否考虑过将O_DIRECT和/或O_SYNC标志传递给open()?从手册:

O_DIRECT
尝试最小化与此文件之间的I/O的缓存影响。一般来说,这会降低性能,但在特殊情况下,如应用程序自行运行时,这是有用的 缓存。文件I/O直接向/从用户空间缓冲区执行。O_DIRECT标志本身努力同步传输数据,但不能保证O_同步 标记传输数据和必要的元数据。为了保证同步I/O,除了O_DIRECT外,还必须使用O_SYNC

O_SYNC
文件写入操作将根据同步I/O文件完整性完成的要求完成


LWN上的这个(现在已经很老了)还提供了一些确保数据完整性的指南。

为了确保所有数据都写入非易失性存储,shutdown命令向每个磁盘发出sd\u shutdown调用。看

这将发出两个SCSI命令:SYNC_CACHE和START_STOP_UNIT,这两个命令将转换为基础设备上的相应操作。对于SATA设备,这意味着将驱动器置于待机模式,从而使磁盘向下旋转。

在脚本中:

  • 可选:运行
    /bin/sync
    将页面缓存中的更改刷新到存储器

  • 卸载目标文件系统(
    umount/mountpoint
    ),或以只读方式重新装载

    如果目标文件系统包含根(
    /
    )和/或系统二进制文件或库(
    /usr
    ),则无法卸载文件系统。在这种情况下,以只读方式重新装载目标文件系统(
    mount-oremount,ro/mountpoint

  • 立即运行
    shutdown-h
    关闭系统电源


这是确保文件系统在关闭时处于干净状态的标准顺序,并且所有更改都会命中存储介质。

请阅读Raymond Chen的文章,记住存储设备是假的。我认为关键是,除了文件本身的fsync之外,还必须对目录进行fsyncing,当前代码不执行此操作。可能在
输出
的stdio缓冲区中有一些未写入的数据,直到调用
fclose
后才写入。尝试调用
fflush(输出)
fsync(fd)。如何关闭电源(“安全地,在等待缓冲写入时”,或“危险地/快速地,非常鲁莽地”)?仅同步和刷新可确保数据从块缓存写入设备。它不保证设备已将数据写入介质。这就是固态硬盘上的FUA(强制单元访问)和电源损耗预防功能。以上任何一项都不能保证正在运行的进程已将其所有数据写入块缓存。这就是Linux shutdown命令的用途。它告诉所有正在运行的进程终止。如果您只是在不关机的情况下关闭电源,那么您是在故意丢失数据。我的系统上没有O_DIRECT可用,但我尝试了O_SYNC。我遇到的问题是,它只会破坏性能