実行中にバイナリを変更する


10

開発中にバイナリファイルを実行している状況に遭遇することがよくあります。たとえばa.out、バックグラウンドで実行すると、時間がかかるためです。それを行っている間に、私は生成a.outしてa.out再度コンパイルするCコードに変更を加えます。これまでのところ、問題はありません。実行中のプロセスはa.out通常どおり続行され、クラッシュすることはなく、常に最初に開始した元のコードが実行されます。

ただし、a.outRAMのサイズに匹敵する巨大なファイルであると言います。この場合はどうなりますか?そして、それが共有オブジェクトファイルにリンクされていると言います。実行中libblas.soに変更libblas.soした場合はどうなりますか?どうなりますか?

私の主な質問ですが-私が実行したときというOSを保証しa.outごとに、その後、元のコードが常に正常に動作しますが、元のバイナリまたはのサイズに関係なく、バイナリ.soこれらの場合でも、それがリンクしたファイル.o.soファイルが中にmodfiedされていますランタイム?

私は同様の問題に対処するこれらの質問があることを知っています:https : //stackoverflow.com/questions/8506865/when-a-binary-file-runs-does-it-copy-its-entire-binary-data-into-memory -at-once 実行中にスクリプトを編集するとどうなりますか? プログラムの実行中にライブ更新を行うにはどうすればよいですか?

これは私がこれについてもう少し理解するのに役立ちましたが、彼らが私が何を望んでいるかを正確に尋ねているとは思いません、これは実行中にバイナリ変更した結果の一般的なルールです


私にとって、あなたがリンクした質問(特にスタックオーバーフローの質問)は、これらの結果(またはその欠如)を理解する上ですでにかなりの助けとなっています。カーネルはプログラムをメモリのテキスト領域/セグメントにロードするため、ファイルサブシステムを介して行われた変更の影響を受けません。
ジョンWHスミス

@JohnWHSmith Stackoverflowでは、トップの答えはif they are read-only copies of something already on disc (like an executable, or a shared object file), they just get de-allocated and are reloaded from their sourceであるので、バイナリが巨大である場合、バイナリの一部がRAMから不足し、再度必要になると「ソースから再ロード」されるという印象を受けました。.(s)oファイルには、実行時に反映されます。しかし、もちろん私は誤解しているかもしれません。それが、このより具体的な質問をしている理由です
texasflood

@JohnWHSmithまた、2番目の答えはNo, it only loads the necessary pages into memory. This is demand paging.、私が実際に要求したことは保証できないという印象を受けていたためです。
texasflood 2015

回答:


11

スタックオーバーフローの質問は最初は十分であるように見えましたが、あなたのコメントから、なぜこれについてまだ疑問があるのか​​理解できます。私にとって、これはまさに、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ノードを介してそれを実行するため、リンクに加えた変更(それらを新しいファイルに関連付けるなど)を無視します。


素晴らしい、詳細な答え。それが私の混乱を説明しています。iノードがまだ利用可能であるため、元のバイナリファイルからのデータはまだディスク上にあり、ディスク上dfの空きバイト数を算出するために使用することは、iノードを取得しないため間違っていると私は考えています。削除されたすべてのファイルシステムリンクが考慮されていますか?だから私は使うべきdf -iですか?(これは単なる技術的な好奇心です。正確なディスク使用量を知る必要はありません!)
texasflood

1
将来の読者のために明確にするために-私の混乱は、実行を考えたときにバイナリ全体がRAMに読み込まれるため、RAMが小さい場合、バイナリの一部がRAMを離れ、ディスクからリロードする必要があるということでした。ファイルを変更すると問題が発生します。しかし、その答えは、それもあなたの場合、バイナリが実際にディスクから削除されないことを明らかにしたrmか、mv元のファイルへのiノードがすべてのプロセスがそのiノードへのリンクを削除するまで削除されないように。
texasflood 2015年

@texasfloodそのとおりです。すべてのパスが削除されると、新しいプロセス(df含まれている)はiノードに関する情報を取得できなくなります。見つかった新しい情報はすべて、新しいファイルと新しいiノードに関連しています。ここでの主なポイントは、プロセスサブシステムはこの問題に関心がないため、メモリ管理の概念(デマンドページング、プロセススワッピング、ページフォールトなど)はまったく無関係です。これはファイルサブシステムの問題であり、ファイルサブシステムによって処理されます。プロセスサブシステムはそれを気にしません、それはそれがここにあるものではありません。
ジョンWHスミス

@texasflood注意df -i:このツールはおそらくfsのスーパーブロックまたはそのキャッシュから情報を取得するため、古いバイナリ(すべてのリンクが削除されている)のiノードが含まれている可能性があります。ただし、新しいプロセスが古いデータを自由に使用できるという意味ではありません。
ジョンWHスミス

2

私の理解では、実行中のプロセスのメモリマッピングにより、カーネルはマップされたファイルの予約済み部分を更新できません。プロセスが実行中の場合は、そのファイルのすべてが予約されているため、ソースの新しいバージョンをコンパイルすると、実際に新しいiノードのセットが作成されるため、更新されます。要するに、実行可能ファイルの古いバージョンは、ページフォールトイベントを通じてディスク上でアクセス可能なままです。あなたは巨大なファイルを更新してもそう、それはすべきであるアクセス可能なままとカーネルがなければならない、まだ限り、プロセスが実行されているようのため手付かずのバージョンを参照してください。元のファイルのiノード、プロセスが実行されている間は再利用しないください

もちろん、これは確認する必要があります。


2

.jarファイルを置き換える場合、これは常に当てはまるわけではありません。JARリソースと一部のランタイムリフレクションクラスローダーは、プログラムが明示的に情報を要求するまでディスクから読み込まれません。

jarはメモリにマップされる単一の実行可能ファイルではなく単にアーカイブであるため、これは問題にすぎません。これは少し話題から外れていますが、それでもあなたの質問と私が自分の足で撃った何かの派生物です。

実行ファイルの場合:はい。jarファイルの場合:多分(実装によって異なります)。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.