在 UNIX 中文件附加是原子的吗?

一般来说,当我们从多个进程追加到 UNIX 中的一个文件时,我们能够理所当然地看待什么?是否可能丢失数据(一个进程覆盖另一个进程的更改) ?数据有可能被破坏吗?(例如,每个进程对一个日志文件附加一行,是否可能有两行被破坏?)如果从上述意义上讲,附加不是原子的,那么确保互斥锁的最佳方法是什么?

39994 次浏览

A write that's under the size of 'PIPE_BUF' is supposed to be atomic. That should be at least 512 bytes, though it could easily be larger (linux seems to have it set to 4096).

This assume that you're talking all fully POSIX-compliant components. For instance, this isn't true on NFS.

但是,假设您以‘ O _ APPEND’模式打开一个日志文件,并将行(包括换行)保持在‘ PIPE _ BUF’字节之下,那么您应该能够对一个日志文件进行多次写入,而不会出现任何损坏问题。任何中断都会在写之前或之后到达,而不是在中间。如果希望文件完整性在重新启动后仍然存在,那么在每次写之后还需要调用 fsync(2),但这对性能来说是很糟糕的。

Clarification: read the comments and 奥兹 · 所罗门的回答. I'm not sure that O_APPEND is supposed to have that PIPE_BUF size atomicity. It's entirely possible that it's just how Linux implemented write(), or it may be due to the underlying filesystem's block sizes.

标准是这样说的: http://www.opengroup.org/onlinepubs/009695399/functions/pwrite.html

如果设置了文件状态标志的 O_APPEND标志,那么在每次写入之前,应该将文件偏移量设置为文件的末尾,并且在更改文件偏移量和写入操作之间不应该进行任何中间的文件修改操作。

我编写了一个脚本来实际测试最大原子附加大小。用 bash 编写的脚本产生多个工作进程,这些进程都将工作者特定的签名写入同一个文件。然后它读取文件,寻找重叠或损坏的签名。您可以在这个 博客文章上看到脚本的源代码。

实际的最大原子附加大小不仅因操作系统而异,而且因文件系统而异。

在 Linux + ext3上,大小是4096,在 Windows + NTFS 上,大小是1024。有关更多尺寸,请参阅下面的评论。

编辑: 2017年8月最新 Windows 结果更新。

作为实现异步文件系统和文件 i/o C + + 库的提议 Boost.AFIO的作者,我将给出一个带有测试代码和结果链接的答案。

首先,在 Windows 上,O _ APPEND 或等效的 FILE _ APPEND _ DATA 意味着最大文件范围(文件“长度”)的增量是并发写入器下的 原子弹。POSIX 保证了这一点,Linux、 FreeBSD、 OSX 和 Windows 都正确地实现了这一点。Samba 也正确地实现了它,V5之前的 NFS 没有这样做,因为它缺乏自动附加的连接格式功能。因此,如果使用仅追加方式打开文件,除非涉及 NFS,否则应使用 在任何主要操作系统上,并发写操作不会相互撕裂

However concurrent reads to atomic appends see torn writes depending on OS, filing system, and what flags you opened the file with - the increment of the maximum file extent is atomic, but the visibility of the writes with respect to reads may or may not be atomic. Here is a quick summary by flags, OS and filing system:


没有 O _ DIRECT/FILE _ FLAG _ NO _ BUFFERING:

使用 NTFS 的 Microsoft Windows 10: update atomicity = 1 byte until and include 10.0.10240,from 10.0.14393 at least 1Mb,may never (*)。

带 ext4的 Linux 4.2.6: update atomicity = 1 byte

FreeBSD 10.2 with ZFS: update atomicity = 至少1Mb,可能是无限的(*)

O _ DIRECT/FILE _ FLAG _ NO _ BUFFERING:

Microsoft Windows 10 with NTFS: update atomicity = until and include 10.0.10240 up to 4096 byte only if page 碎片对齐时最多4096字节,否则如果 FILE _ FLAG _ WRITE _ THROUGH off,则为512字节,否则为64字节。请注意,这种原子性可能是 PCIe DMA 的一个特性,而不是在。从10.0.14393开始,至少1Mb,可能是无限的(*)。

Linux 4.2.6 with ext4: update atomicity = at least 1Mb, probably infinite (*). Note that earlier Linuxes with ext4 definitely did not exceed 4096 bytes, XFS certainly used to have custom locking but it looks like recent Linux has finally fixed this.

FreeBSD 10.2 with ZFS: update atomicity = 至少1Mb,可能是无限的(*)


您可以在 https://github.com/ned14/afio/tree/master/programs/fs-probe上看到原始的经验测试结果。注意,我们只在512字节的倍数上测试撕裂的偏移量,所以我不能说在读-修改-写周期中512字节扇区的部分更新是否会撕裂。

因此,为了回答 OP 的问题,O _ APPEND 写操作不会相互干扰,但是对 O _ APPEND 写操作的并发读操作很可能会在使用 ext4的 Linux 上看到被撕裂的写操作,除非 O _ DIRECT 是打开的,因此 O _ APPEND 写操作需要一个扇区大小的倍数。


(*)“可能无限”源于 POSIX 规范中的这些条款:

下列所有函数对于每个函数都应是原子的 其他在 POSIX.1-2008规定的效果时,他们操作 常规文件或符号链接... [许多函数] ... read () ..。 Write () ... 如果两个线程各自调用其中一个函数,则每个调用 将看到另一个调用的所有指定效果,或者 没有一个是这样的。 < a href = “ http://pubs.opengroup.org/onlinepubs/9699919799/function/V2 _ Chap02.html # tag _ 15 _ 09 _ 07”rel = “ noReferrer”> [ Source ]

还有

写可以相对于其他读和写进行序列化 文件数据的 read ()可以被证明(通过任何方式)发生在 数据的 write () ,它必须反映 write () ,即使调用 are made by different processes. [Source]

但反过来说:

POSIX.1-2008的这个卷没有指定并发的行为 从多个进程写入一个文件。应用程序应该使用一些 并发控制的形式。 < a href = “ http://pubs.opengroup.org/onlinepubs/9699919799/function/write.html”rel = “ noReferrer”> [ Source ]

你可以在这个答案中读到更多关于这些的意义