文件是否在UNIX中附加原子?

Laj*_*agy 99 unix file-io posix atomic atomicity

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

fre*_*eit 60

一个大小为'PIPE_BUF'的写入应该是原子的.这至少应该是512字节,虽然它可能很容易变大(linux似乎将它设置为4096).

这假设您正在谈论所有完全符合POSIX的组件.例如,在NFS上不是这样.

但假设您写入以"O_APPEND"模式打开的日志文件并将您的行(包括换行符)保留在"PIPE_BUF"字节长的情况下,您应该能够将多个写入程序写入日志文件而不会出现任何损坏问题.任何中断都将在写入之前或之后到达,而不是在中间.如果您希望文件完整性在重新启动后继续存在,您还需要fsync(2)在每次写入后调用,但这对于性能而言非常糟糕.

澄清:阅读评论和Oz Solomon的回答.我不确定O_APPEND应该具有那么PIPE_BUF大小的原子性.它完全有可能是Linux实现的方式write(),或者可能是由于底层文件系统的块大小.

  • 在理智的文件系统上,`fsync(2)`提供了与`sync(2)`一样多的保证,并没有像对性能那样大的重大影响. (11认同)
  • 究竟在.../write.html?对于O_APPEND,我没有看到PIPE_BUF,我看到承诺_"在**更改文件偏移和写入操作之间**不会发生中间文件修改操作"_但是我不确定这是否意味着写操作本身是不间断的...... (6认同)
  • 正如[这个答案](http://stackoverflow.com/a/12943431/163956)所指出的,该页面上关于"PIPE_BUF"的声明仅适用于管道和FIFO,而不适用于常规文件. (6认同)
  • 你确定吗?你能提供一些关于这种行为的链接吗?我发现它确认描述符是否是管道,但我找不到它适用于*any*文件的证据.包括普通的非NFS文件对象. (4认同)
  • 随着信号的到来,这可能会变得更糟:https://bugzilla.kernel.org/show_bug.cgi?id = 55651.为什么这甚至标记为答案?PIPE_BUF与文件无关. (3认同)

Nia*_*las 32

编辑: 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.14393至少1Mb,可能是无限(*).

Linux 4.2.6 with ext4:update atomicity = 1 byte

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

O_DIRECT/FILE_FLAG_NO_BUFFERING:

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

Linux 4.2.6 with ext4:update atomicity =至少1Mb,可能是无限(*).请注意,早期使用ext4的Linux肯定没有超过4096字节,XFS肯定用于自定义锁定,但看起来最近Linux已经解决了这个问题.

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写入可能会在Linux上使用ext4看到撕裂写入,除非O_DIRECT打开,因此您的O_APPEND写入需要是扇区大小倍数.


(*)"可能无限"源于POSIX规范中的这些子句:

当它们在常规文件或符号链接上运行时,以下所有函数在POSIX.1-2008中指定的效果中应该是原子的... [许多函数] ... read()... write( )...如果两个线程分别调用其中一个函数,则每个调用应该看到另一个调用的所有指定效果,或者没有一个.[资源]

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

但反过来说:

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

您可以在此答案中详细了解这些含义


Oz *_*mon 26

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

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

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

  • 您的测试似乎假设`echo $ line >> $ OUTPUT_FILE`将导致单次调用`write`而不管`$ line`的大小. (6认同)
  • @OzSolomon,我在Debian 7.8上使用了你的脚本,我只能在ext4分区和tmpfs mount上获得原子写入,包括1008字节(1024 - 16字节的开销?).除此之外的任何事情都会导致腐败. (3认同)

Bas*_*ard 16

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

如果O_APPEND设置了文件状态标志的标志,则在每次写入之前将文件偏移设置为文件的末尾,并且在改变文件偏移和写入操作之间不应发生中间文件修改操作.

  • "介于"之间 - 但是在写作期间的干预措施是什么呢?这对于我的理解发生在"之间"之后?(即:<change_offset_action> ..."the_between_period"... <write_action>) - 我应该理解对此没有任何保证吗? (20认同)
  • 嗯,在我写这篇评论的时候,我无法重建自己的逻辑......是的,如果你的解释是正确的,那么当然不同的记录可能是混合的.但是现在我正在重读Bastien的引用,我认为它必须意味着没有人可以在"写入期间"中断 - 否则标准中的整个段落将毫无用处,根本不提供任何保证(即使写入也会发生)最后,由于"写"步骤正在执行,因此其他人可能会移动偏移. (2认同)
  • @akavel [在“偏移更改”和“写入操作”之间没有“写入期间”或“之间”](https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_09_07) :“当对常规文件或符号链接进行操作时,以下所有函数***应在 POSIX.1-2017 中指定的效果中相对于彼此而言是原子的***:... `write()` 。 ..”因此可以保证`write()`是原子的。请注意,不同线程和不同进程之间没有***区别。 (2认同)