ページキャッシュページが変更されると、ダーティとマークされ、書き戻しが必要になることを知っていますが、次の場合に何が起こりますか。
シナリオ:実行可能ファイルであるファイル/ apps/EXEは、ページキャッシュに完全にページングされ(すべてのページがキャッシュ/メモリにある)、プロセスPによって実行されます
継続的なリリースにより、/ apps/EXEが新しい実行可能ファイルに置き換えられます。
仮定1:プロセスP(および古い実行可能ファイルを参照するファイル記述子を持つ他のユーザー)は、メモリ内の古い/ apps/EXEを問題なく使用し続け、そのパスを実行しようとすると、新しい実行可能ファイルが取得されます。
仮定2:ファイルのすべてのページがメモリにマップされていない場合、置き換えられたファイルのページを必要とするページフォールト、およびおそらくセグメンテーションフォールトが発生するまで、問題がないと想定しています発生しますか?
質問1: vmtouchのようなものでファイルのすべてのページをmlockすると、シナリオはまったく変わりますか?
質問2:/apps/EXEがリモートNFS上にある場合、何か違いがありますか? (そうではないと思います)
2つの仮定を修正または検証して、2つの質問に答えてください。
これが、ある種の3.10.0-957.el7カーネルを備えたCentOS 7.6ボックスであるとしましょう
更新:さらに考えてみると、このシナリオは他のダーティページのシナリオと何の違いもないのではないかと思います。
新しいバイナリを書き込むプロセスは、すべてページインされているため、読み取りを実行してすべてのキャッシュページを取得し、それらすべてのページがダーティとマークされます。それらがロックされている場合、参照カウントがゼロになった後、それらはコアメモリを占有する無駄なページになります。
現在実行中のプログラムが終了すると、他のすべてが新しいバイナリを使用するのではないかと思います。それがすべて正しいと仮定すると、ファイルの一部だけがページインされている場合にのみ興味深いと思います。
継続的なリリースにより、/ apps/EXEが新しい実行可能ファイルに置き換えられます。
これは重要な部分です。
新しいファイルがリリースされる方法は、新しいファイル(例_/apps/EXE.tmp.20190907080000
_)を作成し、コンテンツを書き込み、権限と所有権を設定し、最終的に古いファイルを置き換えて、最終的な名前_/apps/EXE
_に名前を変更(2)します。
その結果、新しいファイルには新しいiノード番号が付けられます(つまり、実際には別のファイルです)。
そして、古いファイルには独自のiノード番号があり、ファイル名がそれを指していなくても(またはファイルがない場合でも、実際にはまだです)そのiノードを指す名前はもうありません。)
したがって、ここで重要なのは、Linuxで「ファイル」について言及する場合、ファイルが開かれるとiノードがファイルへの参照となるため、ほとんどの場合、実際には「inodes」について言及しているということです。
仮定1:プロセスP(および古い実行可能ファイルを参照するファイル記述子を持つ他のユーザー)は、メモリ内の古い/ apps/EXEを問題なく使用し続け、そのパスを実行しようとすると、新しい実行可能ファイルが取得されます。
正しい。
仮定2:ファイルのすべてのページがメモリにマップされていない場合、置き換えられたファイルからのページを必要とするページフォールト、およびおそらくセグメンテーションフォールトが発生するまで、問題がないと思います発生しますか?
不正解です。古いiノードはまだ残っているので、古いバイナリを使用するプロセスからのページフォールトは、ディスク上でそれらのページを見つけることができます。
古いバイナリを実行しているプロセスの_/proc/${pid}/exe
_シンボリックリンク(またはlsof
出力)を見ると、これのいくつかの影響を確認できます。これにより、/app/EXE (deleted)
が表示されます。名前はもうありませんが、iノードはまだ残っています。
また、バイナリが使用するディスク領域は、プロセスが終了した後にのみ解放されることも確認できます(そのiノードが開いている唯一のプロセスであると想定しています)。プロセスを終了する前後にdf
の出力を確認してください。もう存在しないと思っていた古いバイナリのサイズが下がるのを確認してください。
ところで、これはバイナリだけでなく、開いているファイルにも当てはまります。プロセスでファイルを開いてファイルを削除すると、そのプロセスがファイルを閉じる(または終了する)まで、ファイルはディスク上に保持されます。ハードリンクがディスク内のiノードを指す名前の数のカウンターを保持するのと同様に、ファイルシステムドライバー(Linuxカーネル内)は、そのiノードへの参照がいくつ存在するかのカウンターをメモリに保持し、ディスクからiノードを解放するのは一度だけです。実行中のシステムからの参照もリリースされました。
質問1:ファイルのすべてのページをvmtouchのようなものでmlockすると、シナリオが変わります
この質問は、ページをロックしないとセグメンテーション違反が発生するという誤った仮定2に基づいています。それはしません。
質問2:/ apps/EXEがリモートNFS上にある場合、何か違いがありますか? (そうではないと思います)
meantと同じように動作し、ほとんどの場合それが機能しますが、NFSにはいくつかの「落とし穴」があります。
NFSでまだ開いているファイルを削除した場合のアーティファクトが表示されることがあります(そのディレクトリでは非表示のファイルとして表示されます)。
また、NFSエクスポートにデバイス番号を割り当てて、NFSサーバーの再起動時にそれらが「再シャッフル」されないようにする方法もあります。
しかし、主な考え方は同じです。 NFSクライアントドライバーは引き続きiノードを使用し、iノードが参照されている間(サーバー上で)ファイルを保持しようとします。
仮定2:ファイルのすべてのページがメモリにマップされていない場合、置き換えられたファイルからのページを必要とするページフォールトが発生し、おそらくsegfaultが発生するまでは問題ないと思いますか?
いいえ、それは起こりません。なぜなら、カーネルは、現在実行されているファイル内の何かを置き換えて書き込むことを許可しないからです。そのようなアクションはETXTBSY
[1]で失敗します:
_cp /bin/sleep sleep; ./sleep 3600 & echo none > ./sleep
[9] 5332
bash: ./sleep: Text file busy
_
Dpkgなどがバイナリを更新する場合、バイナリは上書きされませんが、ディレクトリエントリが完全に異なるファイルを指すrename(2)
と、古いファイルへのマッピングまたはオープンハンドルが残っているプロセスを使用します。問題なく使用し続けます。
[1]ETXBUSY
保護は、「テキスト」(=ライブコード/実行可能ファイル)と見なすこともできる他のファイルに拡張されません:共有ライブラリ、Javaクラスなど;別のプロセスによってマップされている間にこのようなファイルを変更するとwillプロセスがクラッシュします。Linuxでは、ダイナミックリンカーは_MAP_DENYWRITE
_フラグをmmap(2)
に設定しますが、間違えないでください-何の影響もありません。
_$ cc -xc - <<<'void lib(){}' -shared -o lib.so
$ cc -Wl,-rpath=. lib.so -include unistd.h -xc - <<<'
extern void lib();
int main(){ truncate("lib.so", 0); lib(); }
'
./a.out
Bus error
_
filbrandenの答えは、継続的なリリースプロセスがrename
を介してファイルの適切なアトミック置換を行うことを前提としています。そうではないが、ファイルをインプレースで変更する場合、状況は異なります。しかし、あなたのメンタルモデルはまだ間違っています。
ページキャッシュは正規バージョンですと変更されたものであるため、ディスク上で変更されてページキャッシュと矛盾する可能性はありません。ファイルへの書き込みは、ページキャッシュを介して行われます。既にそこにある場合は、既存のページが変更されます。まだ存在しない場合、部分的なページを変更しようとすると、ページ全体がキャッシュされ、その後、すでにキャッシュされているかのように変更されます。ページ全体にまたがる書き込みは、それらをページングする読み取りステップを最適化することができます(ほぼ間違いなくそうします)。いずれにせよ、存在するファイルの正規の変更可能なバージョンは1つだけ(*)存在し、ページキャッシュに存在します。 。
(*)少し嘘をつきました。 NFSやその他のリモートファイルシステムの場合、複数存在する可能性があり、それらは通常(使用されるマウントオプションとサーバー側オプションに応じて)、書き込みの原子性と順序付けのセマンティクスを正しく実装しません。だからこそ、私たちの多くはそれらが根本的に壊れていると考え、書き込みと並行して書き込みが行われる状況でそれらを使用することを拒否します。