【问题标题】:Do I need to close a file before calling syncfs()在调用 syncfs() 之前是否需要关闭文件
【发布时间】:2023-03-29 19:38:01
【问题描述】:

在我的嵌入式系统上,我想确保在关闭文件时数据被安全写入 - 如果系统报告数据已保存,用户应该能够立即断电。

我知道执行此操作的正确方法是目录上的fsync()fclose()fsync() (cfr.this blog entry)。但是,在我的情况下,获取目录的文件描述符有点棘手(我必须通过/proc/self/fd 来找回文件名并从那里派生目录)。对我来说,在整个文件系统上执行 syncfs() 会简单得多 - 我知道无论如何这是文件系统上唯一打开的文件。

现在我的问题是:

  • syncfs()就够了吗?
  • 我需要先fclose()FILE *(以使目录条目保持最新)吗?或者fflush() 够用吗?
  • 如果需要关闭,是否可以在关闭前将文件描述符dup()直接用于syncfs()

【问题讨论】:

    标签: linux filesystems fsync


    【解决方案1】:

    首先,不要将标准库<stdio.h> 调用(如fprintf(3)fopen(3))与系统调用(如open(2)close(2)sync(2))混用,因为前者是库例程使用进程内的缓冲区来存储系统不知道的临时数据,其他的是操作系统接口,使系统从现在开始负责数据维护。你会很容易区分它们,前者使用FILE *描述符进行操作,而最后使用int整数描述符进行操作。

    因此,如果您使用系统调用来确保您的数据正确同步到磁盘,绝对有必要先 fflush(3) 处理文件系统之前的进程缓冲区数据 sync(2)fsync(2) 打电话。

    保证sync(2) 不会发生在fclose(3) 甚至close(2) 时间,或者在您的进程在exit() 之前执行的atexit() 回调中。
    出于性能原因,操作系统缓冲区被延迟写入,close(2) 不是使其触发此类事件的事件。试想一下,许多进程可以同时读取和写入同一个文件,而每个close(2) 触发文件系统刷新可能很难实现。操作系统会定期触发此类调用,包括umount(2) 系统调用、系统关闭以及对sync(2)fsync(2) 系统调用的特定调用。

    如果您需要保持FILE *fd 描述符打开,只需对该描述符执行fflush(fd),以确保操作系统首先拥有用于fwrite(3)d 或fprintf(3)ed 数据的所有缓冲区。

    最后,如果您使用<stdio.h> 函数,首先对您写入的所有FILE * 描述符执行fflush(),或调用fflush(NULL); 告诉stdio 在一次调用中同步所有描述符。然后执行sync(2)fsync(2) 调用以确保您的所有数据物理上都在磁盘上。无需关闭任何东西。

    FILE *fd;
    ...
    fflush(fd);
    fsync(fileno(fd));
    /* here you know that up to the last write(2) or fwrite(3)...
     * data is synced to disk */
    

    顺便说一句,你去/dev/fd/<number> 获取描述符(你以前有)的方法是错误的,原因有两个:

    • 关闭描述符后,/dev/fd/<number> 不再是您想要的描述符。通常,它甚至不存在。试试这个:

      #include <string.h>
      #include <stdlib.h>
      #include <fcntl.h>
      #include <unistd.h>
      #include <stdio.h>
      #include <errno.h>
      
      int main()
      {
          int fd;
          char fn[] = "/dev/fd/1";
      
          close(1); /* close standard output */
          fd = open(fn, O_RDONLY); /* try to reopen from /dev/fd */
          if (fd < 0) {
              fprintf(stderr,
                      "%s: %s(errno=%d)\n",
                      fn,
                      strerror(errno),
                      errno);
              exit(EXIT_FAILURE);
          }
          exit(EXIT_SUCCESS);
      } /* main */
      
    • 您无法仅通过文件描述符获取打开文件所属的目录。在多链接文件中,可能有数千个目录指向它。 inode(或打开的文件结构)上没有任何内容可让您获取用于打开该文件的路径。使用临时文件的一种常见方法是创建它们并立即unlink(2) 它们,因此没有人可以再次打开它。只要您保持文件打开,您就可以访问它,但不再有指向它的路径。

    【讨论】:

    • 还要小心,因为在使用闪存的嵌入式系统中强制同步会导致有缺陷的 SD 卡过早地达到使用寿命。
    • 感谢您冗长的回答,但我询问的是syncfs(),而不是sync() 或fsync()。对于文件数据没有问题,我可以只做 fflush() + fsync() (顺便说一句,这里没有混合 FILE* 和 fd 的替代方法,因为 FILE* 接口不提供同步操作)。问题实际上是目录条目,当您执行sync()时它不会同步,并且当您关闭()fd时仍然可能会更新(我不确定)。你实际上可以通过 /proc/self/fd 来获取目录,当然前提是它没有被 rename()d。
    • @Arnout,只需阅读手册页,syncfs(2) 与 BSD fsync(2) 的功能相同,用于在辅助存储上同步与打开文件相关的数据。昨天我在一个从 BSD 派生的 Mac OS X 上,并且无法访问 linux 系统来检查手册页。 FILE * 替代方案为您提供了一个fileno(3) 函数,使您可以访问普通文件系统描述符,因此所有系统调用都在那里可用。无论如何,目录条目永远不会先同步(条目总是在文件之后同步,以确保文件系统完整性)并且与您拥有的文件无关......
    • 写到此为止。恐怕你没有多少选择。
    • 使用fflush(),您可以确保操作系统只知道您的进程数据,而不是刷新到磁盘。也就是说,库确保调用write(2) 系统调用以确保缓冲区数据进入系统,但不确保它将物理存储在磁盘上。确保操作系统已知的数据物理存储在磁盘上的唯一方法是调用syncfs()sync()fsync()syncfs() 是特定于 linux 的,其他是 UNIX 普遍存在的。 sync() 与文件无关,不需要文件来描述必须写入的内容,只需同步所有内容。其他的是……
    【解决方案2】:

    在您的文件系统(/etc/fstab)中启用“同步”标志,默认为“异步”(禁用)。启用此标志后,对相应文件系统的所有更改都会立即刷新到磁盘。这会使您的整个文件系统变慢,但根据您的嵌入式系统要求,这可能是一个不错的选择。

    【讨论】:

    • 谢谢,但这不是一个选择。它会使每个 write() 阻塞相对较长的时间,我们负担不起。文件关闭时是同步的好时机。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-12
    • 1970-01-01
    • 2010-09-21
    • 2011-03-08
    • 1970-01-01
    • 2021-07-10
    • 1970-01-01
    相关资源
    最近更新 更多