5

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

作家进程

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]
}

在运行这两个进程一段时间后,我可以看到readbufisxxxxxxxxxxxxxxxxyyyyyyyyyyyyyyyyyyxx.

我的理解是,对于最大 512 字节的大小,写入将是原子的。但从我的实验来看,原子性似乎只有 16 个字节。

手册页没有说明普通文件的原子性,它只提到了 512 字节的管道原子性。

我已经用 tmpfs 和 ext4 试过了,结果是一样的。使用O_SYNC, ext4 写入变得原子化,我理解它,因为写入在到达磁盘之前不会返回,但对 tmpfs ( )O_SYNC没有帮助。/dev/shm

4

2 回答 2

4

POSIX 不提供任何最低限度的原子操作保证,read除了write管道上的写入PIPE_BUF(保证高达(≥ 512)字节的写入是原子的,但读取没有原子性保证)。read和的操作write以字节值来描述;除了管道之外,write与围绕单字节write操作的循环相比,操作没有提供额外的保证。

我不知道 Linux 会提供任何额外的保证,无论是 16 还是 512。在实践中,我希望它取决于内核版本、文件系统,还可能取决于其他因素,例如底层块设备、 CPU 数量、CPU 架构等。

和保证(O_SYNC同步I/O 数据完整性完成O_RSYNC,在 POSIX的可选SIO功能中给出)不是您需要的。它们保证在or系统调用之前将写入提交到持久存储,但不对在操作进行时启动的a 提出任何声明。O_DSYNCreadwritereadwritewriteread

在您的场景中,读取和写入文件看起来不像正确的工具集。

  • 如果您只需要传输少量数据,请使用管道。不要太担心复制:在大多数处理或上下文切换的规模上,复制内存中的数据非常快。另外,Linux 非常擅长优化副本。
  • 如果您需要传输大量数据,您可能应该使用某种形式的内存映射:如果不需要磁盘支持,则可以使用共享内存段,或者mmap如果需要。这并不能神奇地解决原子性问题,但可能会提高适当同步机制的性能。要执行同步,有两种基本方法:
    • 生产者将数据写入共享内存,然后向消费者发送一个通知,指出哪些数据可用。消费者仅根据请求处理数据。通知可以使用相同的通道(例如mmap+ msync)或不同的通道(例如管道)。
    • 生产者将数据写入共享内存,然后刷新写入(例如msync)。然后生产者将一个众所周知的值写入一个机器字(asig_atomic_t通常会起作用,尽管它的原子性仅对信号有形式上的保证——或者实际上是 a uintptr_t)。消费者读取一个机器字,并且仅当该字具有可接受的值时才处理相应的数据。
于 2016-02-24T00:54:05.670 回答
0

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

虽然有些人 公开认为 常规文件原子性仅适用于共享进程的线程,但 POSIX 没有先例将“两个线程”写成“同一个进程的两个线程”。此外,关于“无论何时成功关闭文件描述符,无论是什么原因导致(例如 [...] 进程终止)都应适用”的部分在隔离到一个进程的线程的要求中将是多余的。

于 2022-01-27T08:06:31.483 回答