[APUE]父和子在fork之后共享相同的文件偏移量吗?

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)将类似地消除保证。


Omn*_*ous 4

好吧,我错了。

所以,他们共享一个偏移量,但还发生了其他奇怪的事情。如果他们不共享偏移量,您将得到如下所示的输出:

this is a long long output from chredt
Run Code Online (Sandbox Code Playgroud)

因为每个字符都会从自己的偏移量 0 开始写入,并一次前进一个字符。他们不会开始争论要写入文件的内容,直到到达句子的最后一个单词,该单词最终会交错。

因此,他们正在共享偏移量。

但奇怪的是,偏移量似乎并没有得到原子更新,因为两个进程的输出都没有完整显示。这就像一个的某些部分覆盖另一个的某些部分,即使它们也提前了偏移量,这样就不会总是发生这种情况。

如果未共享偏移量,则文件中的字节数最终将与两个字符串中最长的字节数完全相同。

如果偏移量以原子方式共享和更新,则文件中的字节数最终与两个字符串加在一起的字节数完全相同。

但最终文件中会有一些介于两者之间的字节,这意味着偏移量是共享的,而不是原子更新的,这很奇怪。但这显然就是发生的事情。多么奇怪啊。

  1. 进程A将offset读入A.offset
  2. 进程B将offset读入B.offset
  3. 进程A在A.offset处写入字节
  4. 进程A设置offset = A.offset + 1
  5. 进程 B 在 B.offset 处写入字节
  6. 进程A将offset读入A.offset
  7. 进程 B 设置 offset = B.offset + 1
  8. 进程A在A.offset处写入字节
  9. 进程A设置offset = A.offset + 1
  10. 进程B将offset读入B.offset
  11. 进程 B 在 B.offset 处写入字节
  12. 进程 B 设置 offset = B.offset + 1

这大概就是事件发生的顺序。多么奇怪啊。

存在 pread 和 pwrite 系统调用,因此两个进程可以在特定位置更新文件,而无需争夺全局偏移量的值胜出。