Linux中进程间的write(2)/read(2)原子性

6
我可以帮您翻译成中文。以下是需要翻译的内容:

我有一个案例,其中有两个进程同时作用于同一个文件 - 一个作为写入者,另一个作为读取者。该文件是一个单行文本文件,写入者在循环中重新编写该行。读取者读取该行。伪代码如下:

写入者进程

char buf[][18] = {
"xxxxxxxxxxxxxxxx",
"yyyyyyyyyyyyyyyy"
};
i = 0;
while (1) {
 pwrite(fd, buf[i], 18, 0);
 i = (i + 1) % 2;
}

读取过程
while(1) {
  pread(fd, readbuf, 18, 0);
  //check if readbuf is either buf[0] or buf[1]
}

在运行这两个进程一段时间后,我发现readbuf要么是xxxxxxxxxxxxxxxxyy,要么是yyyyyyyyyyyyyyyyxx
我的理解是,对于512字节及以下的大小,写操作应该是原子性的。但从我的实验来看,似乎只有16字节是原子性的。
man手册没有关于普通文件原子性的说明,它仅提到了管道512字节的原子性。
我已经尝试过tmpfs和ext4,结果相同。使用O_SYNC,ext4写入变得原子化,我理解这是因为写入直到命中磁盘才返回,但O_SYNC对于tmpfs(/dev/shm)没有帮助。

可能你正在使用的文件不可寻址,请检查pread/pwrite中是否有错误。 - xae
这些文件是在tmpfs或ext4中的普通文件。代码还检查pwrite/pread错误。实际上,代码在循环内部打开、写入、关闭。因此,pwrite并不重要,因为打开操作会将偏移量设置为0。 - Vineeth Pillai
2个回答

5
POSIX对于readwrite没有任何原子操作的最小保证,除了在管道上写入(其中512字节的写入保证是原子的,但读取没有原子性保证)。readwrite的操作是以字节值描述的;除了管道外,write操作与单字节write操作的循环相比没有额外的保证。我不知道Linux会提供任何额外的保证,无论是16还是512。实际上,我希望它取决于内核版本、文件系统,可能还取决于其他因素,如底层块设备、CPU数量、CPU架构等。 O_SYNCO_RSYNCO_DSYNC(在POSIX的可选SIO功能中,对于readwrite给出了同步I/O数据完整性完成保证)并不是你所需要的。它们保证在readwrite系统调用之前将写入提交到持久存储,但不能对正在进行read操作时启动的write做任何声明。
在您的场景中,读写文件似乎并不是正确的工具集。
  • 如果你只需要传输少量的数据,请使用管道。不要太担心复制:在大多数处理或上下文切换的范围内,内存中的数据复制非常快。此外,Linux 在优化复制方面表现得相当出色。
  • 如果你需要传输大量的数据,则应该使用某种形式的内存映射:如果不需要磁盘支持,则使用共享内存段,否则使用 mmap。这并不能神奇地解决原子性问题,但很可能会提高适当同步机制的性能。为了执行同步,有两种基本方法:
    • 生产者将数据写入共享内存,然后发送通知给消费者,指示可用的确切数据。消费者仅在请求时处理数据。通知可以使用相同的通道(例如 mmap + msync)或不同的通道(例如管道)。
    • 生产者将数据写入共享内存,然后刷新写入(例如 msync)。然后,生产者向一个机器字写入一个众所周知的值(即使其原子性仅在信号或实践中得到保证的 sig_atomic_t 通常也有效,或者是一个 uintptr_t)。消费者读取那个机器字,只有在该字具有可接受的值时才处理相应的数据。

感谢详细的描述,这解决了我的疑问。由于读者和写者是完全异步的,共享内存可能不是我最好的选择。我改用事务性写入,在此过程中,写者将数据写入临时文件,然后使用原子操作rename(2)更改文件名。这样,读者可以获得一致的数据视图,虽然不总是最新的,但这是可以接受的。 - Vineeth Pillai
还可以将进程共享的互斥锁、条件变量和消息计数器放入文件映射中,以便编写者可以通知其他线程和进程中的(潜在的多个)读取器文件已更新,而读取器可以等待它。 - Maxim Egorushkin
在Linux上,PIPE_BUF为4096字节。 - Christoffer Hammarström

0

PIPE_BUF原子性要求适用于管道和FIFO。 POSIX为常规文件提供了不同的原子性要求,但Linux内核不符合。 常规文件原子性要求出现在2.9.7线程与常规文件操作的交互中。 每当write()实现返回一些正值N时,整个N字节写入都应该是原子的。(符合要求的write()实现可以选择始终返回小于或等于1的值,每次只接受一个字节,在这种情况下,原子性没有实际好处。)

虽然有些人公开争论,常规文件原子性仅适用于共享进程的线程,但是在POSIX中,当它表示“同一进程的两个线程”时,并没有先例写明“两个线程”。此外,“每当成功关闭文件描述符时也应适用,无论原因如何(例如[...]进程终止)”这部分内容,在只针对一个进程的线程的要求中是多余的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接