【问题标题】:fallocate vs posix_fallocatefallocate vs posix_fallocate
【发布时间】:2017-06-30 12:35:22
【问题描述】:

我正在讨论在posix_fallocatefallocate 之间使用哪个函数。 posix_fallocate 立即写入文件(将字符初始化为 NULL)。但是,fallocate 不会更改文件大小(使用 FALLOC_FL_KEEP_SIZE 标志时)。根据我的实验,fallocate 似乎不会将 NULL 或零字符写入文件。

有人可以根据您的经验发表评论吗?感谢您的宝贵时间。

【问题讨论】:

  • fallocate 不改变文件大小?它对我有用。否则,两者之间的唯一区别是,fallocate 在 Linux 内核上的效率会比posix_fallocate 更高,虽然不是可移植的。
  • 这来自文档——“预分配块不会改变文件大小”kernel.org/doc/man-pages/online/pages/man2/fallocate.2.html
  • 你完全断章取义地引用了这个。
  • 我的意思是——当您执行 fallocate 并检查文件大小时,它不受分配字节的影响。但是,如果您使用 posiz_fallocate 并检查文件大小,则文件大小会受到影响。希望这是在讨论的背景下。我在测试中也观察到了这种行为。
  • 我知道你的意思。但“预分配块不会改变文件大小”仅在设置了FALLOC_FL_KEEP_SIZE 标志时适用。不设置标志应该给你相同的结果。

标签: c++ c posix


【解决方案1】:

文件占用的存储空间超过其显示的长度并不常见,因此除非您有充分的理由这样做(例如,您想使用文件长度来跟踪下载的距离,因为恢复它的目的),最好使用默认的 fallocate(2) 行为。 (没有 FALLOC_FL_KEEP_SIZE)。这与 posix_fallocate(3) 的语义相同。

fallocate(2) 的手册页甚至说它的默认行为(无标志)旨在作为实现 posix_fallocate(3) 的最佳方式,并指出这是一种分配空间的可移植方式。

最初的问题是关于将零写入文件。除了元数据之外,这些调用都不会写入任何内容。如果您从已预先分配但尚未写入的空间中读取,您将得到零(不是该磁盘空间之前的任何内容,这将是一个很大的安全漏洞)。您只能读取到文件的末尾(长度,由 fallocate、ftruncate 或各种其他方式设置),因此如果您有一个零长度文件并使用 FALLOC_FL_KEEP_SIZE 进行 fallocate,那么您将无法读取任何内容。与预分配无关,只是文件大小语义。

因此,如果您对 POSIX 语义满意,请使用它,因为它更便于移植。每个 GNU/Linux 系统都将支持 posix_fallocate(3),但其他一些系统也将支持。

但是,多亏了 POSIX 语义,事情并没有那么简单。如果您在不支持预分配的文件系统上使用它,它仍然会成功,但要回退到在文件的每个块中实际写入零。

测试程序:

#include <fcntl.h>
int main() {
    int fd = open("foo", O_RDWR|O_CREAT, 0666);
    if (fd < 0) return 1;
    return posix_fallocate(fd, 0, 400000);
}

在 XFS 上

$ strace ~/src/c/falloc
...
open("foo", O_RDWR|O_CREAT, 0666) = 3
fallocate(3, 0, 0, 400000)              = 0
exit_group(0)                           = ?

在 fat32 闪存驱动器上:

open("foo", O_RDWR|O_CREAT, 0666) = 3
fallocate(3, 0, 0, 400000)              = -1 EOPNOTSUPP (Operation not supported)
fstat(3, {st_mode=S_IFREG|0755, st_size=400000, ...}) = 0
fstatfs(3, {f_type="MSDOS_SUPER_MAGIC", f_bsize=65536, f_blocks=122113, f_bfree=38274, f_bavail=38274, f_files=0, f_ffree=0, f_fsid={2145, 0}, f_namelen=1530, f_frsize=65536}) = 0
pread(3, "\0", 1, 6783)                 = 1
pwrite(3, "\0", 1, 6783)                = 1
pread(3, "\0", 1, 72319)                = 1
pwrite(3, "\0", 1, 72319)               = 1
pread(3, "\0", 1, 137855)               = 1
pwrite(3, "\0", 1, 137855)              = 1
pread(3, "\0", 1, 203391)               = 1
pwrite(3, "\0", 1, 203391)              = 1
pread(3, "\0", 1, 268927)               = 1
pwrite(3, "\0", 1, 268927)              = 1
pread(3, "\0", 1, 334463)               = 1
pwrite(3, "\0", 1, 334463)              = 1
pread(3, "\0", 1, 399999)               = 1
pwrite(3, "\0", 1, 399999)              = 1
exit_group(0)                           = ?

如果文件还没有那么长,它确实可以避免读取,但是写入每个块仍然很糟糕。

如果你想要一些简单的东西,我仍然会选择 posix_fallocate。它有一个 FreeBSD 手册页,它由 POSIX 指定,因此每个 POSIX 兼容系统都提供它。一个缺点是在不支持预分配的文件系统上使用 glibc 会很糟糕。参见例如https://plus.google.com/+AaronSeigo/posts/FGtXM13QuhQ。对于处理大文件(例如种子)的程序,这可能非常糟糕。

您可以感谢 POSIX 语义要求 glibc 执行此操作,因为它没有为“文件系统不支持预分配”定义错误代码。 http://pubs.opengroup.org/onlinepubs/009695399/functions/posix_fallocate.html。它还保证如果调用成功,后续对分配区域的写入不会因磁盘空间不足而失败。因此,posix 设计没有提供一种方法来处理调用者关心效率/性能/碎片而不是磁盘空间保证的情况。这迫使 POSIX 实现执行读写循环,而不是将其作为需要磁盘空间保证的调用者的选项。感谢 POSIX...

我不知道当文件系统不支持预分配时,posix_fallocate 的非 GNU 实现是否同样会退回到极其缓慢的读写行为。 (FreeBSD,Solaris?)。显然 OS X (Darwin) 没有实现 posix_fallocate,除非它是最近的。

如果您希望支持跨许多平台的预分配,但如果操作系统有办法尝试预分配,但又不退回到先读后写,那么您必须使用任何可用的特定于平台的方法。例如查看 https://github.com/arvidn/libtorrent/blob/master/src/file.cpp

搜索文件::set_size。它有几个 ifdeffed 块,具体取决于编译目标支持的内容,从 windows 代码开始加载 DLL 并在那里执行操作,然后是 fcntl F_PREALLOCATE 或 fcntl F_ALLOCSP64,然后是 Linux fallocate(2),然后回退到使用 posix_fallocate。另外,找到了 2007 年 OS X Darwin 的列表帖子:http://lists.apple.com/archives/darwin-dev/2007/Dec/msg00040.html

【讨论】:

  • latest version of POSIX 在 EINVAL 的含义中添加了“...或者底层文件系统不支持此操作”,因此 glibc 和其他人现在可以以合理的方式实际实现 posix_fallocate。理论上。
  • 不错!但是,在具有 write-zeros 回退功能的 Linux 发行版退出使用之前,还需要很多年,因此即使对于新软件,假设良好的行为也是不安全的。维护者也可能决定一个 GNU 系统应该总是有一个工作的posix_fallocate,并保留他们的后备。
  • 完整的 Linux 发行版不太可能切换到为 posix_fallocate 返回 EINVAL,因为 glibc 将无限期地保持当前行为以实现兼容性目的。有关详细信息,请参阅包含 sourceware.org/ml/libc-alpha/2015-10/msg00138.htmlsourceware.org/ml/libc-alpha/2015-05/msg00062.html 的线程。
  • NFS4.2 支持fallocate?很好,我没有意识到。但是,从孔或未写入的范围读取仍然会通过线路发送零。 :(
【解决方案2】:

我认为您没有查看说明

的文档
   The mode argument determines the operation to be performed on the given range.
   Currently only one flag is supported for mode:

   FALLOC_FL_KEEP_SIZE
          This flag allocates and initializes to zero the disk space within the
          range specified by offset and len.  After a successful call, subsequent
          writes into this range are guaranteed not to fail because of lack of
          disk space.  Preallocating zeroed blocks beyond the end of the file is
          useful for optimizing append workloads.  Preallocating blocks does not
          change the file size (as reported by stat(2)) even if it is less than
          offset+len.

   If FALLOC_FL_KEEP_SIZE flag is not specified in mode, the default behavior is
   almost same as when this flag is specified.  The only difference is that on
   success, the file size will be changed if offset + len is greater than the
   file size.  This default behavior closely resembles the behavior of the
   posix_fallocate(3) library function, and is intended as a method of optimally
   implementing that function.

posix_fallocate() 的手册页似乎没有提到相同的内容,但相反,查看源代码here,它似乎写入了文件的每个块(第 88 行)。

man fallocate man posix_fallocate

【讨论】:

  • “当前模式只支持一个标志”——这在技术上可能不正确。还有一些支持的标志——man7.org/linux/man-pages/man2/fallocate.2.html
  • 嗯,所以我想互联网上大量手册页的问题之一是你永远不知道什么时候找到了正确的......但是,我的 Fedora Core 16 人-page 表示与引用文本相同的一个标志,所以我不确定哪个更准确。这里似乎有两个可用:lxr.linux.no/linux+*/fs/open.c#L228 - 它似乎是在 2.6.38 及更高版本的内核中引入的。
  • glibc 的 posix_fallocate(3) 仅在 fallocate(2) 因 EOPNOTSUPP 失败时退回到先读后写行为,例如在 fat32 fs 或 aufs 上。如果您只需要性能提示而不是磁盘空间保证,而不直接使用 fallocate(2) 或 fcntl,而不是可移植的 posix_fallocate(3),则无法禁用回退行为。不知道其他系统上的 posix_fallocate 实现是做什么的(FreeBSD、OS X、Solaris?)。请参阅我的答案以获取一些链接,包括检查多种方法可用性的代码。
【解决方案3】:

至少有一点信息来自 fallocate(2) 手册页:

int fallocate(int fd, int mode, off_t offset, off_t len);

DESCRIPTION
   This is a nonportable, Linux-specific system call.

虽然系统调用文档没有说,但 fallocate(1) 程序手册页说:

As of the Linux Kernel v2.6.31, the fallocate system call is supported
by the btrfs, ext4, ocfs2, and xfs filesystems.

这对我来说很有意义,因为 NTFS、FAT、CDFS 和大多数其他常见文件系统在磁盘上没有支持调用的内部机制。我认为对这些的支持将由内核缓冲,并且该设置不会在系统启动后持续存在。

【讨论】:

  • 实际上Linux只是在文件系统不支持该操作时返回-1并将errno设置为ENOSYS。这至少记录在手册页的某些版本中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-03-18
  • 1970-01-01
  • 2018-08-27
  • 1970-01-01
  • 2017-08-09
  • 2021-04-04
相关资源
最近更新 更多