3

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

我遇到的问题是文件没有被完整写入。如果它是一个 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;
}

我最初尝试使用文件描述符构建我的FILE ,并在使用的描述符(/logs/data)上调用fsync(),但是这产生了同样的问题。根据fsync(2)的规范:

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

这导致我使用上面的代码,仅为包含我的数据文件的目录创建一个特定的文件描述符并在其上调用fsync()。然而结果是一样的。我真的不明白为什么会这样,因为fsync()应该是阻塞的:

呼叫阻塞,直到设备报告传输已完成。

另外,如您所见,我在FILE上添加了一个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 字节)。它似乎非常接近一个可行的解决方案,但它仍然不能保证每一个数据字节。

4

3 回答 3

0

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

O_DIRECT
尽量减少进出该文件的 I/O 的缓存影响。一般来说,这会降低性能,但在特殊情况下很有用,例如当应用程序进行自己的缓存时。文件 I/O 直接与用户空间缓冲区进行。O_DIRECT 标志自己努力同步传输数据,但不提供 O_SYNC 标志的保证,即传输数据和必要的元数据。为了保证同步 I/O,除了 O_DIRECT 之外,还必须使用 O_SYNC。

O_SYNC
对文件的写操作会按照同步I/O文件完整性完成的要求完成...

这篇关于 LWN 的文章(现在已经很老了)也提供了一些确保数据完整性的指南。

于 2021-01-07T21:30:59.253 回答
0

为确保将所有数据写入非易失性存储,shutdown 命令向每个磁盘发出 sd_shutdown 调用。见https://elixir.bootlin.com/linux/v4.10.17/source/drivers/scsi/sd.c#L3338

这会发出两个 SCSI 命令:SYNC_CACHE 和 START_STOP_UNIT,它们被转换为底层设备上的适当操作。对于 SATA 设备,这意味着将驱动器置于 STANDBY 模式,这会降低磁盘的转速。

于 2021-01-07T21:56:50.430 回答
0

在您的脚本中:

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

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

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

  • 运行shutdown -h now以关闭系统

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

于 2021-01-08T07:02:54.323 回答