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


106

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

Answers:


65

小于“ PIPE_BUF”大小的写入应该是原子的。该长度至少应为512字节,尽管它可以轻易变大(Linux似乎将其设置为4096)。

假设您正在谈论所有完全符合POSIX的组件。例如,在NFS上并非如此。

但是,假设您写入在“ O_APPEND”模式下打开的日志文件并将行(包括换行符)的长度保持在“ PIPE_BUF”字节以下,则应该能够使多个写入者写入日志文件而不会出现任何损坏问题。任何中断都将在写入之前或之后到达,而不是在中间。如果希望文件完整性在重新启动后仍然存在,则还需要fsync(2)在每次写入后都调用它,但是这样会降低性能。

说明:阅读评论和Oz Solomon的回答。我不确定O_APPEND应该具有这种PIPE_BUF原子性。这完全有可能是Linux的实现方式write(),或者可能是由于底层文件系统的块大小所致。


11
在健全的文件系统上,可以fsync(2)提供尽可能多的保证sync(2),并且对性能的影响不大。
短暂

4
你确定吗?您能否提供有关该行为的一些链接?我发现它已确认描述符是否为管道,但找不到任何文件可使用的证据。包括普通的非NFS文件对象。
艾伦·弗朗佐尼

6
... / write.html中到底在哪里?对于O_APPEND,我没有提到PIPE_BUF,而且我保证更改文件偏移量和写操作之间不会发生任何介入的文件修改操作”,但是我不确定这是否意味着写操作本身就是不间断...
akavel 2012年

6
正如该答案指出的那样,PIPE_BUF该页面上的有关语句仅适用于管道和FIFO,不适用于常规文件。
格雷格·伊诺泽姆采夫

3
随着信号的到达,情况可能变得更糟:bugzilla.kernel.org/show_bug.cgi?id=55651。为什么这甚至被标记为答案?PIPE_BUF与文件无关。
变细了

35

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

作为拟议的Boost.AFIO的作者,我将为您提供测试代码和结果的链接的答案,该文件实现了异步文件系统和文件I / O C ++库。

首先,Windows上的O_APPEND或等效的FILE_APPEND_DATA表示在并发编写器下,最大文件范围的增量(文件“长度”)是原子的。POSIX保证了这一点,Linux,FreeBSD,OS X和Windows都正确实现了它。Samba也可以正确实现它,而v5之前的NFS则不正确,因为它缺少自动附加的有线格式功能。因此,如果您仅使用追加文件打开文件,则在任何主要操作系统上并发写操作都不会彼此撕裂除非涉及NFS,否则。

但是,对原子追加的并发读取可能会发现写入中断,具体取决于操作系统,文件系统以及打开文件时使用的标志-最大文件范围的增量是原子的,但写入相对于读取的可见性可能会或可能不会是原子的。以下是标志,操作系统和归档系统的快速摘要:


否O_DIRECT / FILE_FLAG_NO_BUFFERING:

具有NTFS的Microsoft Windows 10:更新原子性= 1个字节,直到10.0.10240(包括10.0.10240)从10.0.14393起至少1Mb,可能是无限(*)。

带有ext4的Linux 4.2.6:更新原子性= 1字节

具有ZFS的FreeBSD 10.2:更新原子性=至少1Mb,可能是无限(*)

O_DIRECT / FILE_FLAG_NO_BUFFERING:

带有NTFS的Microsoft Windows 10:仅当页面对齐时,更新atomicity =直到并包括10.0.10240时最多4096字节;否则如果FILE_FLAG_WRITE_THROUGH关闭则为512字节,否则为64字节。请注意,这种原子性可能是PCIe DMA的一项功能,而不是设计在内。自10.0.14393起,至少1Mb,可能是无限(*)。

带有ext4的Linux 4.2.6:更新原子性=至少1Mb,可能是无限(*)。请注意,带有ext4的早期Linux肯定不超过4096字节,XFS当然曾经具有自定义锁定,但是看起来最近的Linux终于解决了这个问题。

具有ZFS的FreeBSD 10.2:更新原子性=至少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( )...如果两个线程各自调用这些函数之一,则每个调用应要么看到另一个调用的所有指定作用,要么都不看到。[资源]

可以针对其他读取和写入对写入进行序列化。如果可以证明(通过任何方式)在数据的write()之后发生了文件数据的read(),则即使调用是由不同的进程进行的,它也必须反映出write()。[资源]

但是相反:

POSIX.1-2008的此卷没有指定从多个进程并发写入文件的行为。应用程序应使用某种形式的并发控制。[资源]

您可以在此答案中了解有关这些含义的更多信息


29

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

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

在Linux + ext3上,大小为4096,在Windows + NTFS上,大小为1024。有关更多大小,请参见下面的注释。


您在Linux上使用哪种文件系统进行了测试?我想知道它是否基于文件系统块大小。
2015年

我相信@freiheit当时我在ext3上对其进行了测试。如果您在另一个FS上运行它并获得不同的结果,请发表评论。
Oz Solomon

3
@OzSolomon,我在Debian 7.8上使用了您的脚本,但在ext4分区和tmpfs挂载上,我只能获得原子写入,包括1008个字节(1024-16个字节的开销?)。除此之外,每次都会导致腐败。
埃里克·普鲁伊特

6
您的测试似乎假设无论的大小如何,echo $line >> $OUTPUT_FILE都会导致一次调用。write$line
托马斯

16

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

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


20
“介于”之间-但是写过程中的干预又如何呢?据我所知,干预发生在“介于”之后?(即:<change_offset_action> ...“ the_between_period” ... <write_action>)-我可以理解吗?
akavel

@akavel同意;无法保证写入本身是原子的。但是我很困惑:基于您报价中提供的保证,看来我们可以得出结论,附加相同文件的多线程应用程序不会混合不同书面记录的一部分。但是,从OzSolomon报告的实验中,我们看到甚至违反了该假设。为什么?
最大

@max对不起,我恐怕没有收到您的问题:首先,OzSolomon的实验是多进程的,而不是多线程(单进程)的应用程序;其次,我不明白您是如何得出“多线程应用程序不会混合”的结论的,正如我在评论中提到的,Bastien的报价并不能保证这一点。你能澄清你的问题吗?
akavel

2
嗯,我在写评论时无法重构自己的逻辑……是的,如果您的解释正确,那么当然可能会混合使用不同的记录。但是,既然我重新阅读了Bastien的报价,我认为这必须意味着没有人可以在“写入过程中”打断-否则标准中的整个段落将毫无用处,根本无法提供任何保证(甚至连写入都不会发生)最后,由于其他人可能会随着“写”步骤的执行而移动偏移量
最高有效时间
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.