6

APUEセクション8.3fork functionで、親プロセスと子プロセス間のファイル共有について、次の
ように述べています。It is important that the parent and the child share the same file offset.

また、セクション8.9Race 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);
 }
}
4

5 に答える 5

6

親と子は、オフセットを含むカーネル内の同じファイルテーブルエントリを共有します。したがって、ファイルを閉じて再度開くプロセスの一方または両方がなければ、親と子が異なるオフセットを持つことは不可能です。したがって、親による書き込みはすべてこのオフセットを使用し、オフセットを変更(インクリメント)します。次に、子による書き込みはすべて新しいオフセットを使用し、それを変更します。一度に1文字ずつ書くと、この状況はさらに悪化します。

私のwrite(2)のマニュアルページから:「ファイルオフセットの調整と書き込み操作はアトミックステップとして実行されます。」

したがって、それから、一方(親または子)からの書き込みが他方(親または子)から上書きされないことが保証されます。また、文全体を一度に書く(2)場合(write(2)の1回の呼び出しで)、文が1つの部分にまとめて書かれることが保証されていることにも注意してください。

実際には、多くのシステムがこの方法でログファイルを書き込みます。多くの関連するプロセス(同じ親の子)には、親によって開かれたファイル記述子があります。それぞれが一度に1行全体を書き込む限り(write(2)を1回呼び出すだけで)、ログファイルは必要に応じて読み取られます。一度に文字を書くことは同じ保証を持っていません。出力バッファリング(たとえば、stdioを使用)を使用すると、同様に保証が削除されます。

于 2009-10-28T15:23:05.673 に答える
4

まあ、私は間違っていました。

したがって、彼らはオフセットを共有していますが、何か別の奇妙なことが起こっています。オフセットを共有していない場合は、次のような出力が得られます。

this is a long long output from chredt

それぞれが独自のオフセット0で書き込みを開始し、一度に1文字ずつ進むためです。文の最後の単語に到達するまで、ファイルに何を書き込むかについて競合し始めませんでした。最後の単語はインターリーブされてしまいます。

したがって、それらはオフセットを共有しています。

しかし、奇妙なことに、どちらのプロセスの出力も完全に表示されないため、オフセットがアトミックに更新されているようには見えません。オフセットを進めて常に発生しないようにしているにもかかわらず、一方の一部が他方の一部を上書きしているようなものです。

オフセットが共有されていなかった場合、ファイルには2つの文字列のうち最長のものとまったく同じ数のバイトが含まれることになります。

オフセットが共有され、アトミックに更新される場合、ファイルには、両方の文字列を合わせたのとまったく同じ数のバイトが含まれることになります。

しかし、ファイル内のバイト数が中間になり、オフェセットが共有され、アトミックに更新されないことを意味します。これはまったく奇妙なことです。しかし、それは明らかに何が起こるかです。なんて奇妙なことでしょう。

  1. プロセスAはオフセットをA.offsetに読み込みます
  2. プロセスBはオフセットをB.offsetに読み込みます
  3. プロセスAはA.offsetにバイトを書き込みます
  4. プロセスAはオフセット=A.offset+1を設定します
  5. プロセスBはB.offsetにバイトを書き込みます
  6. プロセスAはオフセットをA.offsetに読み込みます
  7. プロセスBはオフセット=B.offset+1を設定します
  8. プロセスAはA.offsetにバイトを書き込みます
  9. プロセスAはオフセット=A.offset+1を設定します
  10. プロセスBはオフセットをB.offsetに読み込みます
  11. プロセスBはB.offsetにバイトを書き込みます
  12. プロセスBはオフセット=B.offset+1を設定します

これが、イベントのシーケンスとほぼ同じです。なんて奇妙なことでしょう。

preadおよびpwriteシステムコールが存在するため、2つのプロセスが、グローバルオフセットの勝者の価値を競うことなく、特定の位置でファイルを更新できます。

于 2009-10-28T09:49:01.707 に答える
2

さて、私はバニラGCC / glibcでコンパイルするようにコードを調整しました、そしてここに出力例があります:

thhis isias a l long oulout futput frd
 parent

そして、それはファイルの位置が共有され、それ人種の影響を受けるという考えを支持していると思います。それがとても奇妙な理由です。私が示したデータは47文字であることに注意してください。これは、いずれかの単一メッセージの38文字または39文字を超え、両方のメッセージを合わせた77文字未満です。プロセスがファイルの位置を更新するために競合する場合があることを確認できる唯一の方法は、それぞれが文字、それぞれが位置をインクリメントしようとしますが、レースのために1つのインクリメントのみが発生し、一部の文字が上書きされます。

裏付けとなる証拠:man 2 lseek私のシステムでは明確に述べています

dup(2)またはfork(2)によって作成されたファイル記述子は、現在のファイル位置ポインターを共有するため、そのようなファイルの検索は競合状態の影響を受ける可能性があることに注意してください。

于 2009-10-28T10:14:24.490 に答える
1

OSクラスから正しく思い出せば、フォークは子に独自のオフセットを与えます(ただし、親と同じ位置から開始します)が、開いているファイルテーブルは同じままです。しかし、私が読んでいるもののほとんどはそうではないと述べているようです。

于 2009-10-28T09:30:21.660 に答える
1

pwriteを使用するこの場所を指して、他のプロセスが何かを実行したい場合は、ファイル記述子がフォーク間で共有されるため、実行したとおりに生成または動作しません。

フィードバックしてみてください

于 2010-10-27T14:01:50.810 に答える