既存のファイルの途中に64kBのデータのようなものをアトミックに書き込む必要があります。それがすべてであるか、何も書かれるべきではありません。Linux / Cでそれを達成する方法は?
4 に答える
私はそれが可能だとは思わないか、少なくとも、書き込みがアトミックであることを契約の一部として保証するインターフェースはありません。つまり、現時点でアトミックな方法がある場合、それは実装の詳細であり、そのままにしておくのは安全ではありません。おそらく、問題に対する別の解決策を見つける必要があります。
ただし、書き込みプロセスが 1 つしかなく、他のプロセスが完全な書き込みを確認するか、まったく書き込みを行わないようにすることが目標である場合は、ファイルの一時コピーに変更を加えてから、それrename
をアトミックに置き換えることができます。古いファイルに対して開いているファイル記述子をすでに持っているリーダーは、古い内容を見るでしょう。名前で新しく開くと、新しいコンテンツが表示されます。部分的な更新は、どの読者にも表示されません。
ファイルの内容を「原子的に」変更する方法はいくつかあります。技術的には、変更自体が真にアトミックになることはありませんが、他のすべてのプロセスに対してアトミックに見えるようにする方法があります。
Linux での私のお気に入りの方法は、 を使用して書き込みリース
fcntl(fd, F_SETLEASE, F_WRLCK)
を取得することです。fd
ファイルへの唯一の開いている記述子である場合にのみ成功します。つまり、他の誰も (このプロセスでさえも) ファイルを開いていません。CAP_LEASE
また、カーネルがリースを許可するには、プロセスを実行しているユーザーがファイルを所有しているか、プロセスが root として実行されているか、プロセスに機能が必要です。成功すると、リース所有者プロセスは、
SIGIO
別のプロセスがファイルを開いたり切り詰めたりするたびにシグナル (デフォルト) を受け取ります。/proc/sys/fs/lease-break-time
オープナーは、最大で2 秒間 (デフォルトでは 45 秒)、またはリースの所有者がリースを解放またはダウングレードするか、ファイルを閉じるまでのいずれか短い方まで、カーネルによってブロックされます。したがって、リース所有者は、他のプロセスがファイルの内容を見ることができずに、「アトミック」操作を完了するのに数十秒かかります。注意が必要なシワがいくつかあります。1 つは、カーネルがリースを許可するために必要な特権または所有権です。もう 1 つは、相手がファイルを開いたり切り詰めたりするのが遅れるだけであるという事実です。リース所有者は、ファイルを置き換える (ハードリンクまたは名前変更する) ことはできません。(可能ですが、オープナーは常に元のファイルを開きます。) また、ファイルの名前変更、ハードリンク、およびリンク解除/削除は、ファイルの内容には影響しないため、ファイル リースの影響はまったくありません。
生成されたシグナルを処理する必要があることも覚えておいてください。
fcntl(fd, F_SETSIG, signum)
信号を変更するために使用できます。私は個人的に単純なシグナル ハンドラ (本体が空のもの) を使用してシグナルをキャッチしますが、他の方法もあります。半原子性を達成する移植可能な方法は、 を使用してメモリ マップを使用すること
mmap()
です。アイデアは、memmove()
または類似のものを使用してできるだけ早くコンテンツを置き換え、次に を使用msync()
して変更を実際のストレージ メディアにフラッシュすることです。ファイル内のメモリ マップ オフセットがページ サイズの倍数である場合、マップされたページはページ キャッシュを反映します。つまり、何らかの方法でファイルを読み取る他のプロセス
mmap()
(またはread()
その派生プロセス) は、memmove()
. はmsync()
、システム クラッシュの場合に、変更がディスクにも確実に保存されるようにするためにのみ必要です。基本的には と同等fsync()
です。プリエンプション (現在のタイムスライスがアップしているためにカーネルがアクションを中断する) とページ フォールトを回避するために、まずマップされたデータを読み取ってページがメモリ内にあることを確認し
sched_yield()
、memmove()
. マップされたデータを読み取ると、ページがページ キャッシュにフォールトし、残りのタイムスライスが解放されるため、カーネルによって中断されないsched_yield()
可能性が非常に高くなります。memmove()
(ページが既にフォールトされていることを確認していない場合、カーネルはmemmove()
ページごとに個別に中断する可能性があります。プロセスではそれを確認できませんが、他のプロセスでは変更がページ サイズのチャンクで発生することがわかります。)これは正確にはアトミックではありませんが、実用的です。保証はなく、レース ウィンドウが非常に短くなるだけです。したがって、私はこれをセミアトミックと呼んでいます。
この方法は、ファイル リースと互換性があることに注意してください。ファイルに対して書き込みリースを取得しようとすることもできますが、1 秒または 2 秒などの許容可能な時間内にリースが許可されない場合は、リースレス メモリ マッピングにフォールバックします。
timer_create()
およびを使用timer_settime()
してタイムアウト タイマーを作成し、同じ空のボディ シグナル ハンドラーを使用してSIGALRM
シグナルをキャッチします。そのようにして、タイムアウトが発生したときにfcntl()
中断されます (で返さ-1
れますerrno == EINTR
) -- タイマー間隔が小さな値 (たとえば 25000000 ナノ秒、または 0.025 秒) に設定されているため、その後非常に頻繁に繰り返され、最初の割り込みが失敗した場合にシステムコールが中断されます。何らかの理由。ほとんどのユーザー空間アプリケーションは、元のファイルのコピーを作成し、コピーの内容を変更してから、元のファイルをコピーで置き換えます。
ファイルを開く各プロセスでは、完全な変更のみが表示され、古い内容と新しい内容が混在することはありません。ただし、ファイルを開いたままにしておくと、元の内容しか表示されず、変更に気付かない (自分で確認しない限り)。ほとんどのテキスト エディタはチェックしますが、デーモンやその他のプロセスは気にしません。
Linux では、ファイル名とその内容は 2 つの別個のものであることに注意してください。ファイルを開いたり、リンクを解除/削除したり、ファイルを開いている限り、コンテンツの読み取りと変更を続けることができます。
他のアプローチもあります。最適なアプローチは状況に大きく依存するため、特定のアプローチを提案したくありません。他のプロセスはファイルを開いたままにしますか、それともコンテンツを読み取る前に常に (再) オープンしますか? アトミック性は優先されますか、それとも絶対に必要ですか? データは、XML のように構造化されたプレーン テキストですか、それともバイナリですか?
追加するために編集:
ファイルがアトミックに正常に変更されることを事前に保証する方法はないことに注意してください。理論上でも、実際上でもありません。
たとえば、ディスクがいっぱいで書き込みエラーが発生する場合があります。または、ドライブが間違った瞬間に中断する可能性があります。ここでは、典型的なユース ケースでアトミックに見えるようにするための実用的な方法を3 つだけ挙げます。
書き込みリースが私のお気に入りの理由はfcntl(fd,F_GETLEASE,&ptr)
、リースがまだ有効かどうかをいつでも確認できるからです。そうでない場合、書き込みはアトミックではありませんでした。
同じデータが直前に読み取られている場合 (そのデータがページ キャッシュにある可能性が高いため)、高いシステム負荷によって 64k 書き込みのリースが中断されることはほとんどありません。プロセスにスーパーユーザー権限がある場合はsetpriority(PRIO_PROCESS,getpid(),-20)
、ファイル リースを取得してファイルを変更している間、一時的にプロセスの優先度を最大に上げるために使用できます。上書きされるデータが読み取られたばかりの場合、スワップに移動される可能性はほとんどありません。したがって、スワッピングも発生しないはずです。
つまり、リース メソッドが失敗する可能性は十分にありますが、実際には、この補遺で言及されている追加のトリックがなくても、ほぼ常に成功します。
個人的には、/fcntl()
の前に変更後の呼び出しを使用して、変更がアトミックでないかどうかを確認するだけです(停電が発生した場合にデータがディスクにヒットすることを確認します)。これにより、変更がアトミックかどうかを確認するための絶対に信頼できる簡単な方法が得られます。msync()
fsync()
構成ファイルやその他の機密データについては、名前を変更する方法もお勧めします。(実際には、NFS セーフ ファイル ロックに使用されるハードリンク アプローチを好みます。これは同じことですが、名前の競合を検出するために一時的な名前を使用します。) ただし、ファイルを開いたままにしておくプロセスはすべてチェックする必要があるという問題があります。自発的にファイルを再度開いて、変更された内容を確認します。
ディスク書き込みは、抽象化のレイヤーがなければアトミックにはなりません。ジャーナルを保持し、書き込みが中断された場合は元に戻す必要があります。
私の知る限り、PIPE_BUF のサイズ未満の書き込みはアトミックです。しかし、私はこれに頼ることはありません。ファイルにアクセスするプログラムを作成した場合は、flock()を使用して排他アクセスを実現できます。このシステム コールは、ファイルにロックを設定し、ロックを認識している他のプロセスがアクセスできるかどうかを許可します。