linux 进程之间的 write(2)/read(2) 原子性
write(2)/read(2) atomicity between processes in linux
我有一个案例,有两个进程作用于同一个文件——一个作为写入器,一个作为 reader。该文件是一个单行文本文件,编写者循环重写该行。 reader 读取该行。伪代码如下所示:
编写器进程
char buf[][18] = {
"xxxxxxxxxxxxxxxx",
"yyyyyyyyyyyyyyyy"
};
i = 0;
while (1) {
pwrite(fd, buf[i], 18, 0);
i = (i + 1) % 2;
}
Reader 进程
while(1) {
pread(fd, readbuf, 18, 0);
//check if readbuf is either buf[0] or buf[1]
}
经过 运行 两个过程一段时间后,我可以看到 readbuf
是 xxxxxxxxxxxxxxxxyy
或 yyyyyyyyyyyyyyyyxx
。
我的理解是写入对于最大 512 字节的大小是原子的。但从我的实验来看,原子性似乎只适用于 16 个字节。
手册页没有说明普通文件的原子性,它只提到了 512 字节的管道原子性。
我已经用 tmpfs 和 ext4 试过了,结果是一样的。使用 O_SYNC
,ext4 写入成为原子,我理解它,因为写入不会 return 直到它到达磁盘,但 O_SYNC
对 tmpfs 没有帮助(/dev/shm
) .
POSIX 不为 read
and write
提供任何原子操作的最低保证,除了在管道上写入(其中最多 PIPE_BUF
(≥ 512) 字节的写入是保证是原子的,但读取没有原子性保证)。 read
和write
的操作是用字节值来描述的;除了管道之外,与围绕单字节 write
操作的循环相比,write
操作不提供额外的保证。
我不知道 Linux 会提供任何额外的保证,无论是 16 还是 512。实际上我希望它取决于内核版本、文件系统,可能还取决于其他底层块设备、CPU数量、CPU架构等因素
O_SYNC
、O_RSYNC
和 O_DSYNC
保证(POSIX 的 synchronized I/O data integrity completion, given for read
and write
in the optional SIO 特性)不是您所需要的。它们保证在 read
或 write
系统调用之前将写入提交到持久存储,但不对在 read
操作期间启动的 write
做出任何声明进行中。
在您的场景中,读取和写入文件看起来不是正确的工具集。
- 如果您只需要传输少量数据,请使用管道。不要太担心复制:在大多数处理或上下文切换的规模上,复制内存中的数据非常快。再加上Linux在优化副本方面相当不错。
- 如果您需要传输大量数据,您可能应该使用某种形式的内存映射:如果不需要磁盘支持,则使用共享内存段,或者
mmap
如果需要。这并不能神奇地解决原子性问题,但可能会提高适当同步机制的性能。要执行同步,有两种基本方法:
- 生产者将数据写入共享内存,然后向消费者发送一个通知,指明哪些数据可用。消费者仅根据请求处理数据。通知可以使用相同的通道(例如
mmap
+ msync
)或不同的通道(例如管道)。
- 生产者将数据写入共享内存,然后刷新写入(例如
msync
)。然后,生产者将一个众所周知的值写入一个机器字(sig_atomic_t
通常会起作用,即使它的原子性在形式上只对信号有保证——或者,在实践中,uintptr_t
)。消费者读取那个机器字,只有当这个字有可接受的值时才处理相应的数据。
PIPE_BUF
原子性要求适用于管道和 FIFO。 POSIX 给出
常规文件有不同的原子性要求,但 Linux 内核没有
符合。 regular-file 原子性要求出现在 2.9.7 Thread
与常规文件的交互
操作。
每当 write() 实现 return 某个正值 N 时,整个 N-byte
写应该是原子的。 (符合标准的 write() 实现可以选择
总是 return 一个小于或等于 1 的值,每次只接受一个字节
时间,在这种情况下原子性没有实际好处。)
虽然有些人
publicly argued
regular-file 原子性仅适用于共享进程的线程,有
POSIX 写“two threads”没有先例,意思是“two threads of the
相同的过程”。此外,关于“的部分也适用于文件
描述符已成功关闭,但是导致(例如[...]进程
终止)”在与一个线程隔离的要求中是多余的
过程。
我有一个案例,有两个进程作用于同一个文件——一个作为写入器,一个作为 reader。该文件是一个单行文本文件,编写者循环重写该行。 reader 读取该行。伪代码如下所示:
编写器进程
char buf[][18] = {
"xxxxxxxxxxxxxxxx",
"yyyyyyyyyyyyyyyy"
};
i = 0;
while (1) {
pwrite(fd, buf[i], 18, 0);
i = (i + 1) % 2;
}
Reader 进程
while(1) {
pread(fd, readbuf, 18, 0);
//check if readbuf is either buf[0] or buf[1]
}
经过 运行 两个过程一段时间后,我可以看到 readbuf
是 xxxxxxxxxxxxxxxxyy
或 yyyyyyyyyyyyyyyyxx
。
我的理解是写入对于最大 512 字节的大小是原子的。但从我的实验来看,原子性似乎只适用于 16 个字节。
手册页没有说明普通文件的原子性,它只提到了 512 字节的管道原子性。
我已经用 tmpfs 和 ext4 试过了,结果是一样的。使用 O_SYNC
,ext4 写入成为原子,我理解它,因为写入不会 return 直到它到达磁盘,但 O_SYNC
对 tmpfs 没有帮助(/dev/shm
) .
POSIX 不为 read
and write
提供任何原子操作的最低保证,除了在管道上写入(其中最多 PIPE_BUF
(≥ 512) 字节的写入是保证是原子的,但读取没有原子性保证)。 read
和write
的操作是用字节值来描述的;除了管道之外,与围绕单字节 write
操作的循环相比,write
操作不提供额外的保证。
我不知道 Linux 会提供任何额外的保证,无论是 16 还是 512。实际上我希望它取决于内核版本、文件系统,可能还取决于其他底层块设备、CPU数量、CPU架构等因素
O_SYNC
、O_RSYNC
和 O_DSYNC
保证(POSIX 的 synchronized I/O data integrity completion, given for read
and write
in the optional SIO 特性)不是您所需要的。它们保证在 read
或 write
系统调用之前将写入提交到持久存储,但不对在 read
操作期间启动的 write
做出任何声明进行中。
在您的场景中,读取和写入文件看起来不是正确的工具集。
- 如果您只需要传输少量数据,请使用管道。不要太担心复制:在大多数处理或上下文切换的规模上,复制内存中的数据非常快。再加上Linux在优化副本方面相当不错。
- 如果您需要传输大量数据,您可能应该使用某种形式的内存映射:如果不需要磁盘支持,则使用共享内存段,或者
mmap
如果需要。这并不能神奇地解决原子性问题,但可能会提高适当同步机制的性能。要执行同步,有两种基本方法:- 生产者将数据写入共享内存,然后向消费者发送一个通知,指明哪些数据可用。消费者仅根据请求处理数据。通知可以使用相同的通道(例如
mmap
+msync
)或不同的通道(例如管道)。 - 生产者将数据写入共享内存,然后刷新写入(例如
msync
)。然后,生产者将一个众所周知的值写入一个机器字(sig_atomic_t
通常会起作用,即使它的原子性在形式上只对信号有保证——或者,在实践中,uintptr_t
)。消费者读取那个机器字,只有当这个字有可接受的值时才处理相应的数据。
- 生产者将数据写入共享内存,然后向消费者发送一个通知,指明哪些数据可用。消费者仅根据请求处理数据。通知可以使用相同的通道(例如
PIPE_BUF
原子性要求适用于管道和 FIFO。 POSIX 给出
常规文件有不同的原子性要求,但 Linux 内核没有
符合。 regular-file 原子性要求出现在 2.9.7 Thread
与常规文件的交互
操作。
每当 write() 实现 return 某个正值 N 时,整个 N-byte
写应该是原子的。 (符合标准的 write() 实现可以选择
总是 return 一个小于或等于 1 的值,每次只接受一个字节
时间,在这种情况下原子性没有实际好处。)
虽然有些人 publicly argued regular-file 原子性仅适用于共享进程的线程,有 POSIX 写“two threads”没有先例,意思是“two threads of the 相同的过程”。此外,关于“的部分也适用于文件 描述符已成功关闭,但是导致(例如[...]进程 终止)”在与一个线程隔离的要求中是多余的 过程。