OnT*_*Way 5 c unix file-io fork race-condition
在APUE第8.3节中fork function,关于父进程和子进程之间的文件共享,
它说:It is important that the parent and the child share the same file offset.
在第8.9节中Race Conditions,有一个例子:父和子都写入
一个在调用fork函数之前打开的文件.该程序包含竞争条件,
因为输出取决于内核运行进程的顺序以及每个进程运行的时间.
但在我的测试代码中,输出是重叠的.
[Langzi @ Freedom apue] $ cat race.out
这是一个漫长的输出,这是父母的长输出
看起来父和子具有单独的文件偏移而不是共享相同的偏移量.
我的代码中有错误吗?或者我是否误解了共享偏移的含义?
任何建议和帮助将不胜感激.
以下是我的代码:
#include "apue.h"
#include <fcntl.h>
void charatatime(int fd, char *);
int main()
{
pid_t pid;
int fd;
if ((fd = open("race.out", (O_WRONLY | O_CREAT | O_TRUNC),
S_IRUSR | S_IWUSR)) < 0)
err_sys("open error");
if ((pid = fork()) < 0)
err_sys("fork error");
else if (pid == 0)
charatatime(fd, "this is a long long output from child\n");
else
charatatime(fd, "this is a long long output from parent\n");
exit(0);
}
void charatatime(int fd, char *str)
{
// try to make the two processes switch as often as possible
// to demonstrate the race condition.
// set synchronous flag for fd
set_fl(fd, O_SYNC);
while (*str) {
write(fd, str++, 1);
// make sure the data is write to disk
fdatasync(fd);
}
}
Run Code Online (Sandbox Code Playgroud)
小智 5
父子进程在内核中共享相同的文件表条目,其中包括偏移量。因此,如果没有一个或两个进程关闭和重新打开文件,父进程和子进程不可能有不同的偏移量。因此,父项的任何写入都使用此偏移量并修改(增加)偏移量。然后孩子的任何写入使用新的偏移量,并修改它。一次写入一个字符会加剧这种情况。
来自我的 write(2) 手册页:“文件偏移量的调整和写入操作是作为原子步骤执行的。”
因此,由此可以保证,任何一方(父母或子女)的写入都不会覆盖另一方的写入。您还可以注意到,如果您要一次写 (2) 整个句子(在一次 write(2) 调用中),则可以保证将句子一起写成一个整体。
实际上,许多系统以这种方式写入日志文件。许多相关进程(同一个父进程的子进程)都有一个由父进程打开的文件描述符。只要他们每个人一次写一整行(调用一次 write(2)),日志文件就会按照您的意愿读取。一次写入一个字符不会有相同的保证。使用输出缓冲(例如,stdio)将类似地消除保证。
好吧,我错了。
所以,他们共享一个偏移量,但还发生了其他奇怪的事情。如果他们不共享偏移量,您将得到如下所示的输出:
this is a long long output from chredt
Run Code Online (Sandbox Code Playgroud)
因为每个字符都会从自己的偏移量 0 开始写入,并一次前进一个字符。他们不会开始争论要写入文件的内容,直到到达句子的最后一个单词,该单词最终会交错。
因此,他们正在共享偏移量。
但奇怪的是,偏移量似乎并没有得到原子更新,因为两个进程的输出都没有完整显示。这就像一个的某些部分覆盖另一个的某些部分,即使它们也提前了偏移量,这样就不会总是发生这种情况。
如果未共享偏移量,则文件中的字节数最终将与两个字符串中最长的字节数完全相同。
如果偏移量以原子方式共享和更新,则文件中的字节数最终与两个字符串加在一起的字节数完全相同。
但最终文件中会有一些介于两者之间的字节,这意味着偏移量是共享的,而不是原子更新的,这很奇怪。但这显然就是发生的事情。多么奇怪啊。
这大概就是事件发生的顺序。多么奇怪啊。
存在 pread 和 pwrite 系统调用,因此两个进程可以在特定位置更新文件,而无需争夺全局偏移量的值胜出。