POSIX 的 read() 和 write() 系统调用是原子的吗?

pyo*_*yon 6 concurrency posix b-tree database-indexes

我正在尝试根据Lehman 和 Yao 在本文中建议的数据结构(B链接树)和算法来实现数据库索引。在第 2 页,作者指出:

磁盘分区为固定大小的部分(物理页;在本文中,这些对应于树的节点)。这些是进程可以读取或写入的唯一单元。[强调我的](...)

(...) 允许进程锁定和解锁磁盘页面。这个锁赋予该进程对该页面的独占修改权;此外,进程必须锁定页面才能修改该页面。(...)不会阻止其他进程读取锁定的页面。[强调我的]

我不完全确定我的解释是正确的(我不习惯阅读学术论文),但我认为可以从强调的句子中得出结论,作者的意思是读取和写入页面的操作被假定为“原子” ,从某种意义上说,如果进程 A 已经开始读取(相应地写入)页面,则另一个进程 B 可能不会开始写入(相应地读取)同一页面,直到 A 完成其读取(相应地写入)操作. 多个进程同时读取同一个页面当然是一个合法的条件,因为多个进程同时在不同的页面上执行任意操作(页面 P 上的进程 A,页面 Q 上的进程 B,页面 R 上的进程 C,等等。 )。

  1. 我的解释正确吗?

  2. 我可以假设 POSIX'read()write()系统调用在上述意义上是“原子的”吗?我是否可以依靠这些具有一些内部逻辑的系统调用来根据文件描述符的位置和要读取或写入的块的指定大小来确定是否应该暂时阻止特定read()write()调用?

  3. 如果上述问题的答案是“否”,我应该如何推出自己的锁定机制?

Cel*_*ada 6

我不相信您引用的文本暗示任何此类内容。它甚至没有提到read()orwrite()或 POSIX。事实上,read()write()不能指望是原子的。POSIX 说的唯一一件事是,write()如果写入的大小小于PIPE_BUF字节,则它必须是原子的,即使这仅适用于管道。

我没有阅读您引用的论文部分的上下文,但听起来您引用的段落说明了必须对实现施加约束才能使算法正常工作。换句话说,它声明该算法的实现需要锁定。

如何进行锁定取决于您(实现者)。如果我们正在处理一个常规文件和多个独立进程,您可以尝试使用fcntl(F_SETLKW)样式锁定。如果您的数据结构在内存中并且您在同一个进程中处理多个线程,那么其他可能是合适的。


Nia*_*las 5

答案:

  1. 根据操作系统、文件系统以及您打开文件时使用的标志,并发读取到写入可能会看到写入撕裂。下面是标志、操作系统和文件系统的快速摘要。

  2. 您可以在使用 POSIX 上的 fcntl() 或 Windows 上的 LockFile() 访问它们之前锁定文件中的字节范围。


没有 O_DIRECT/FILE_FLAG_NO_BUFFERING:

带有 NTFS 的 Microsoft Windows 10:更新原子性 = 1 字节

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

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

O_DIRECT/FILE_FLAG_NO_BUFFERING:

带 NTFS 的 Microsoft Windows 10:更新原子性 = 最多 4096 字节(仅当页面对齐时),否则 512 字节(如果 FILE_FLAG_WRITE_THROUGH 关闭),否则为 64 字节。请注意,这种原子性可能是 PCIe DMA 的一个特性,而不是在 (*) 中设计的。

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

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


您可以在https://github.com/BoostGSoC13/boost.afio/blob/master/fs_probe/fs_probe_results.yaml查看原始经验测试结果。结果是由在所有平台上使用异步文件 i/o 编写的程序生成的。请注意,我们仅在 512 字节倍数上测试撕裂的偏移量,所以我不能说 512 字节扇区的部分更新是否会在读取-修改-写入周期中撕裂。