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

时间:2009-10-28 09:18:56

标签: 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);
 }
}

5 个答案:

答案 0 :(得分:4)

父和子共享内核中的相同文件表条目,其中包括偏移量。因此,父进程和子进程不可能在没有一个或两个进程关闭并重新打开文件的情况下具有不同的偏移量。因此,父级的任何写入都使用此偏移量并修改(递增)偏移量。然后,孩子的任何写入都使用新的偏移量,并对其进行修改。一次写一个字符会加剧这种情况。

从我的write(2)手册页:“调整文件偏移量和写入操作是作为原子步骤执行的。”

因此,从中可以保证,一个(父或子)的写入不会写在另一个的顶部。你还可以注意到,如果你要一次写(2)你的整个句子(在一次写(2)),保证句子将一起写在一起。

实际上,许多系统都是这样写日志文件的。许多相关进程(同一父节点的子节点)将具有由父节点打开的文件描述符。只要每个人一次写一整行(一次调用write(2)),日志文件就会按照你想要的方式读取。一次写一个角色不会有同样的保证。使用输出缓冲(例如stdio)同样会删除保证。

答案 1 :(得分:3)

嗯,我错了。

所以,他们正在分享一个偏移,但还有一些奇怪的事情正在发生。如果他们没有共享偏移量,您将得到如下所示的输出:

this is a long long output from chredt

因为每个人都会开始写自己的偏移0并一次推进一个角色。在得到句子的最后一个单词之前,他们不会开始对写入文件的内容发生冲突,这最终会交错。

所以,他们正在分享一个偏移。

但奇怪的是,它看起来似乎没有原子地更新偏移,因为两个进程输出都没有完整显示。就像一个人的某些部分正在覆盖另一部分,即使他们也推进了偏移,所以总是不会发生。

如果没有共享偏移量,你最终会得到文件中与两个字符串中最长的一个字节一样多的字节。

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

但是你最终会在文件中有一些字节位于其间的某个位置,这意味着offesets是共享的而不是原子地更新的,这简直就是奇怪的。但这显然是会发生的事情。多么奇怪。

  1. 进程A将偏移量读入A.offset
  2. 进程B将偏移读入B.offset
  3. 进程A在A.offset
  4. 处写入字节
  5. 进程A设置offset = A.offset + 1
  6. 进程B在B.offset
  7. 处写入字节
  8. 进程A将偏移量读入A.offset
  9. 进程B设置offset = B.offset + 1
  10. 进程A在A.offset
  11. 处写入字节
  12. 进程A设置offset = A.offset + 1
  13. 进程B将偏移读入B.offset
  14. 进程B在B.offset
  15. 处写入字节
  16. 进程B设置offset = B.offset + 1
  17. 这大约是事件顺序必须是什么。真奇怪。

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

答案 2 :(得分:1)

如果我从我的OS类中正确回想起来,分叉确实给了孩子它自己的偏移量(虽然它从父母开始的位置相同),它只是保持相同的打开文件表。虽然,我正在阅读的大部分内容似乎都表示不同。

答案 3 :(得分:1)

好吧,我调整了代码以在vanilla GCC / glibc上编译,这是一个示例输出:

thhis isias a l long oulout futput frd
 parent

而且我认为这支持文件位置 共享且 受竞赛影响的想法,这就是为什么它如此奇怪。请注意,我显示的数据有47个字符。这不仅仅是单个消息的38或39个字符,而且两个消息的77个字符都在一起 - 我能看到发生这种情况的唯一方法是,如果进程有时竞争更新文件位置 - 它们每个都写一个角色,他们每个都试图增加位置,但由于比赛只发生一个增量,一些字符被覆盖。

支持证据:我的系统man 2 lseek清楚地说明了

  

请注意,dup(2)或fork(2)创建的文件描述符共享当前文件位置指针,因此搜索此类文件可能会受到竞争条件的影响。

答案 4 :(得分:1)

使用pwrite,因为写入某些时候最终会遇到竞争条件,因为同一个资源(write())被多个进程共享,因为写入不会在完成后留下文件pos = 0,例如你最终在文件中间所以文件指针(fd)指向这个位置,如果其他进程想要做某事,那么它产生或工作的不是它想要做的事情,因为文件描述符将在分叉时共享!!

尝试给我反馈