【问题标题】:How do I ensure data is written to the physical media?如何确保将数据写入物理介质?
【发布时间】:2021-04-13 10:15:51
【问题描述】:

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

我遇到的问题是文件没有完整写入。如果它是一个 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 字节)。它似乎非常接近一个可行的解决方案,但它仍然不能保证每一个数据字节。

【问题讨论】:

  • 阅读 Raymond Chen 的this,记住存储设备是谎言。
  • 我认为重点在于,除了对文件本身进行 fsync 之外,您还必须对目录进行 fsync,而您当前的代码没有这样做。
  • 也许output的stdio缓冲区中有一些未写入的数据直到fclose调用才被写入。尝试在fsync(fd); 之前调用fflush(output);
  • 你是如何关闭电源的(“安全地,等待缓冲写入被写入”,或“危险地/快速地进行重大鲁莽”)?
  • 同步和刷新只保证数据从块缓存写入设备。它不保证设备已将数据写入媒体。这就是 SSD 上的 FUA(强制单元访问)和断电预防的用途。以上都不能保证正在运行的进程已将所有数据写入块缓存。这就是 Linux 关闭命令的用途。它告诉所有正在运行的进程终止。如果您只是关闭电源而不关闭,那么您是在故意丢失数据。

标签: c linux system-calls fsync


【解决方案1】:

在您的脚本中:

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

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

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

  • 运行shutdown -h now关闭系统

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

【讨论】:

    【解决方案2】:

    为确保将所有数据写入非易失性存储,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 模式,这会降低磁盘的转速。

    【讨论】:

      【解决方案3】:

      您是否考虑过将 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 上的这个article(现在已经很老了)也提供了一些确保数据完整性的准则。

      【讨论】:

      • 我的系统上没有可用的 O_DIRECT,但我确实尝试过使用 O_SYNC。我遇到的问题是它只会破坏性能(由于我的 fwrite 次数)即使在将所有写入批处理成各种大小的块之后,对于 4GiB 的数据,性能提升似乎在大约 7 分钟左右就结束了,不管我做了多大的积木。现在它似乎工作正常,但它就像我目前正在做的事情的一半时间,然后添加一个 10-15 秒的睡眠命令来“确保”事情被写入,所以当它工作时,它比黑客更糟糕
      猜你喜欢
      • 2010-09-27
      • 2017-09-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-11-15
      • 2011-11-23
      • 2021-08-04
      相关资源
      最近更新 更多