スタックオーバーフローの質問は最初は十分であるように見えましたが、あなたのコメントから、なぜこれについてまだ疑問があるのか理解できます。私にとって、これはまさに、2つのUNIXサブシステム(プロセスとファイル)が通信するときに発生する重要な状況です。
ご存知かもしれませんが、UNIXシステムは通常、ファイルサブシステムとプロセスサブシステムの2つのサブシステムに分かれています。さて、システムコールを通じて他の方法で指示されない限り、カーネルはこれらの2つのサブシステムが相互に対話するべきではありません。ただし、例外が1つあります。プロセスのテキスト領域への実行可能ファイルの読み込みです。もちろん、この操作はシステムコール(execve
)によってもトリガーされると主張する人もいますが、これは通常、プロセスサブシステムがファイルサブシステムに暗黙的な要求を行う1つのケースとして知られています。
プロセスサブシステムには当然ファイルを処理する方法がないため(そうでない場合、全体を2つに分割しても意味がありません)、ファイルサブシステムがファイルにアクセスするために提供するものをすべて使用する必要があります。これはまた、プロセスサブシステムが、ファイルの編集/削除に関してファイルサブシステムがとる措置に提出されることを意味します。この点で、このU&L質問に対するGillesの回答を読むことをお勧めします。私の残りの答えは、Gillesからのこのより一般的なものに基づいています。
注意すべき最初のことは、内部的には、ファイルはiノードを通じてのみアクセス可能であることです。カーネルにパスが指定されている場合、最初のステップは、カーネルをiノードに変換して、他のすべての操作に使用することです。プロセスは、実行可能ファイルをメモリにロードするときに、パスの変換後にファイルサブシステムによって提供されるiノードを介して実行します。iノードは複数のパス(リンク)に関連付けられている場合があり、プログラムはリンクのみを削除できます。ファイルとそのiノードを削除するには、ユーザーランドはそのiノードへの既存のリンクをすべて削除し、それが完全に未使用であることを確認する必要があります。これらの条件が満たされると、カーネルは自動的にファイルをディスクから削除します。
Gillesの答えの一部である実行可能ファイルの置き換えを確認すると、ファイルの編集/削除方法に応じて、カーネルは常にファイルサブシステム内に実装されたメカニズムを通じて異なる反応/適応をすることがわかります。
- 戦略1(open / truncate to zero / writeまたはopen / write / truncate to new size)を試してみると、カーネルがリクエストを処理する必要がないことがわかります。エラー26:テキストファイルがビジー(
ETXTBSY
)になります。結果はまったくありません。
- 戦略2を試す場合、最初のステップは実行可能ファイルを削除することです。ただし、プロセスによって使用されているため、ファイルサブシステムが起動し、ファイル(およびそのiノード)がディスクから本当に削除されなくなります。この時点から、古いファイルのコンテンツにアクセスする唯一の方法は、そのiノードを介してアクセスすることです。これは、プロセスサブシステムが新しいデータをテキストセクションにロードする必要がある場合に常に実行されます(内部的には、パスを使用しても意味がありません)それらをiノードに変換するとき)。あなたはしたにも関わらず、リンク解除しますファイル(すべてのパスが削除された)の場合、プロセスは何もしなかったかのようにファイルを使用できます。古いパスで新しいファイルを作成しても、何も変更されません。新しいファイルには、実行中のプロセスが認識していない完全に新しいiノードが付与されます。
戦略2と3は実行可能ファイルに対しても安全です。実行中の実行可能ファイル(および動的に読み込まれるライブラリ)は、ファイル記述子を持つという意味では開いているファイルではありませんが、動作は非常に似ています。一部のプログラムがコードを実行している限り、ディレクトリエントリがなくてもファイルはディスクに残ります。
mv
操作はアトミックであるため、戦略3は非常に似ています。これにはおそらくrename
システムコールを使用する必要があります。また、カーネルモードではプロセスを中断できないため、処理が完了するまで(成功したかどうかにかかわらず)この操作を妨げるものはありません。繰り返しになりますが、古いファイルのiノードは変更されません。新しいファイルが作成され、古いiノードのリンクの1つに関連付けられていても、すでに実行中のプロセスはそのファイルを認識しません。
戦略3では、新しいファイルを既存の名前に移動するステップで、古いコンテンツにつながるディレクトリエントリが削除され、新しいコンテンツにつながるディレクトリエントリが作成されます。これは1つのアトミック操作で行われるため、この戦略には大きな利点があります。プロセスがいつでもファイルを開いた場合、古いコンテンツまたは新しいコンテンツのいずれかが表示されます。コンテンツが混在したり、ファイルが失われたりするリスクはありません。既存。
ファイルgcc
の再コンパイル:を使用している場合(および動作は他の多くのコンパイラでもおそらく同じです)、戦略2を使用していstrace
ます。コンパイラのプロセスを実行すると、次のことがわかります。
stat("a.out", {st_mode=S_IFREG|0750, st_size=8511, ...}) = 0
unlink("a.out") = 0
open("a.out", O_RDWR|O_CREAT|O_TRUNC, 0666) = 3
chmod("a.out", 0750) = 0
- コンパイラは、
stat
およびlstat
システムコールによって、ファイルがすでに存在することを検出します。
- ファイルはリンク解除されています。ここでは、名前
a.out
でアクセスできなくなりましたが、すでに実行中のプロセスで使用されている限り、そのiノードとコンテンツはディスクに残ります。
- 新しいファイルが作成され、という名前で実行可能になります
a.out
。これはまったく新しいiノードであり、すでに実行中のプロセスが気にしないまったく新しいコンテンツです。
共有ライブラリに関しては、同じ動作が適用されます。ライブラリオブジェクトがプロセスによって使用されている限り、リンクをどのように変更しても、オブジェクトはディスクから削除されません。何かをメモリにロードする必要があるときはいつでも、カーネルはファイルのiノードを介してそれを実行するため、リンクに加えた変更(それらを新しいファイルに関連付けるなど)を無視します。