Thunderbird や Firefox などのキラー アプリケーションが、実行中にシステムのパッケージ マネージャーを介して更新できるのはなぜでしょうか。更新中に古いコードはどうなるのでしょうか。実行中に自動的に更新されるプログラム a.out を書きたい場合、どうすればよいでしょうか。
答え1
一般的なファイルの置き換え
まず、ファイルを置き換えるにはいくつかの方法があります。
開ける既存のファイルを書き込み用に開き、長さを 0 に切り詰めて、新しいコンテンツを書き込みます。(あまり一般的ではないバリエーションとして、既存のファイルを開き、古いコンテンツを新しいコンテンツで上書きし、新しい長さの方が短い場合はファイルを新しい長さに切り詰めるという方法があります。) シェルの用語では、次のようになります。
echo 'new content' >somefile
取り除く古いファイルを削除し、同じ名前で新しいファイルを作成します。シェル用語では、次のようになります。
rm somefile echo 'new content' >somefile
一時的な名前で新しいファイルに書き込み、動く新しいファイルを既存の名前に変更します。移動により古いファイルは削除されます。シェル用語では:
echo 'new content' >somefile.new mv somefile.new somefile
戦略間の違いをすべて列挙するつもりはありませんが、ここでは重要な点についてのみ言及します。戦略 1 では、いずれかのプロセスが現在ファイルを使用している場合、プロセスは更新時に新しいコンテンツを参照します。プロセスがファイル コンテンツが同じままであると想定している場合、これは混乱を招く可能性があります。これは、ファイルを開いているプロセス (または で表示されている) にのみ当てはまることに注意してください。lsof
ドキュメントを開いている対話型アプリケーション (エディターでファイルを開くなど) は通常、ファイルを開いたままにせず、「ドキュメントを開く」操作中にファイル コンテンツを読み込み、「ドキュメントを保存」操作中に (上記の戦略のいずれかを使用して) ファイルを置き換えます。/proc/PID/fd/
戦略2と3では、何らかのプロセスがファイルをsomefile
開いている場合、古いファイルはコンテンツアップグレード中も開いたままです。戦略2では、ファイルを削除する手順では、実際にはディレクトリ内のファイルのエントリのみが削除されます。ファイル自体は、そのファイルにつながるディレクトリエントリがない場合にのみ削除されます(一般的なUnixファイルシステムでは、同じファイルに対する複数のディレクトリエントリ)そしてsleep
プロセスはそれをオープンしていません。これを確認するには、プロセスが強制終了されたときにのみファイルが削除される (rm
ディレクトリ エントリのみが削除される)という方法があります。
echo 'old content' >somefile
sleep 9999999 <somefile &
df .
rm somefile
df .
cat /proc/$!/fd/0
kill $!
df .
戦略 3 では、新しいファイルを既存の名前に移動する手順で、古いコンテンツにつながるディレクトリ エントリが削除され、新しいコンテンツにつながるディレクトリ エントリが作成されます。これは 1 つのアトミック操作で実行されるため、この戦略には大きな利点があります。プロセスがいつでもファイルを開いた場合、古いコンテンツまたは新しいコンテンツのいずれかが表示されます。コンテンツが混在したり、ファイルが存在しないというリスクはありません。
実行ファイルの置き換え
Linux 上で実行中の実行可能ファイルで戦略 1 を試すと、エラーが発生します。
cp /bin/sleep .
./sleep 999999 &
echo oops >|sleep
bash: sleep: Text file busy
「テキストファイル」とは、実行可能なコードを含むファイルを意味します。歴史的な理由は不明Linux は、他の多くの UNIX 系と同様に、実行中のプログラムのコードを上書きすることを拒否します。一部の UNIX 系ではこれを許可しており、新しいコードが古いコードに非常によく考えられた変更を加えたものでない限り、クラッシュにつながります。
Linux では、動的にロードされたライブラリのコードを上書きできます。これにより、それを使用しているプログラムがクラッシュする可能性があります。( では、sleep
起動時に必要なすべてのライブラリ コードをロードするため、これを観察できない可能性があります。 のように、スリープ後に何か役に立つことを行う、より複雑なプログラムを試してみてくださいperl -e 'sleep 9; print lc $ARGV[0]'
。)
インタプリタがスクリプトを実行している場合、スクリプトファイルはインタプリタによって通常の方法で開かれるため、スクリプトの上書きに対する保護はありません。インタプリタの中には、最初の行を実行する前にスクリプト全体を読み込んで解析するものもあれば、必要に応じてスクリプトを読み込むものもあります。実行中にスクリプトを編集するとどうなりますか?そしてLinux はシェル スクリプトをどのように処理しますか?詳細については。
戦略 2 と 3 は実行可能ファイルに対しても安全です。実行中の実行可能ファイル (および動的にロードされたライブラリ) は、ファイル記述子を持つという意味ではオープン ファイルではありませんが、非常によく似た動作をします。何らかのプログラムがコードを実行している限り、ディレクトリ エントリがなくてもファイルはディスク上に残ります。
アプリケーションのアップグレード
ほとんどのパッケージ マネージャーは、上記で述べた大きな利点 (いつでもファイルを開くと有効なバージョンになる) があるため、戦略 3 を使用してファイルを置き換えます。
アプリケーションのアップグレードが失敗する可能性があるのは、1 つのファイルのアップグレードはアトミックですが、アプリケーションが複数のファイル (プログラム、ライブラリ、データなど) で構成されている場合、アプリケーション全体のアップグレードはアトミックではないためです。次の一連のイベントを検討してください。
- アプリケーションのインスタンスが開始されます。
- アプリケーションがアップグレードされました。
- 実行中のインスタンス アプリケーションは、データ ファイルの 1 つを開きます。
ステップ 3 では、古いバージョンのアプリケーションの実行中のインスタンスが、新しいバージョンのデータ ファイルを開いています。これが機能するかどうかは、アプリケーション、それがどのファイルであるか、そしてファイルがどの程度変更されたかによって異なります。
アップグレード後、古いプログラムがまだ実行されていることに気づくでしょう。新しいバージョンを実行するには、古いプログラムを終了して新しいバージョンを実行する必要があります。パッケージ マネージャーは通常、アップグレード時にデーモンを強制終了して再起動しますが、エンド ユーザー アプリケーションはそのまま残します。
いくつかのデーモンには、デーモンを終了して新しいインスタンスが再起動するのを待つことなく(サービスの中断を引き起こす)、アップグレードを処理するための特別な手順があります。これは、次の場合に必要です。初期化、これは強制終了できません。initシステムは、実行中のインスタンスに呼び出しを要求する方法を提供します。execve
新しいバージョンに置き換えられます。
答え2
アップグレードはプログラムの実行中に実行できますが、表示される実行中のプログラムは実際には古いバージョンです。古いバイナリは、プログラムを閉じるまでディスク上に残ります。
説明:Linux システムでは、ファイルは単なる inode であり、複数のリンクを持つことができます。たとえば、私のシステムでは、 は へのリンクに/bin/bash
すぎません。 のリンクに対してinode 3932163
を発行すると、どの inode が にリンクしているかがわかりますls --inode /path
。ファイル (inode) は、そのファイルを指すリンクが 0 個で、どのプログラムでも使用されていない場合にのみ削除されます。パッケージ マネージャーが をアップグレードする場合、たとえば/usr/bin/firefox
、最初にリンクを解除 (ハード リンク を削除) し、次に別の inode (新しいバージョンを含む inode) へのハードリンクである という/usr/bin/firefox
新しいファイルを作成します。古い inode は空きとしてマークされ、新しいデータを保存するために再利用できます。ただし、ディスク上には残ります (inode はファイルシステムの構築時にのみ作成され、削除されることはありません)。 の次回の起動時には、新しい inode が使用されます。/usr/bin/firefox
firefox
firefox
実行中に自分自身を「アップグレード」するプログラムを書きたい場合、考えられる唯一の解決策は、バイナリ ファイルのタイムスタンプを定期的にチェックし、それがプログラムの開始時刻よりも新しい場合は自分自身をリロードすることです。
答え3
Thunderbird や Firefox などのキラー アプリケーションを、実行中にシステムのパッケージ マネージャー経由で更新できるのはなぜでしょうか? まあ、これはあまりうまく機能しないと言えます...パッケージ更新の実行中に Firefox を開いたままにしておくと、Firefox がひどく壊れてしまいました。Firefox が壊れすぎて適切に閉じることさえできなかったため、強制終了して再起動しなければならないこともありました。
更新中に古いコードはどうなりますか? 通常、Linux ではプログラムはメモリにロードされるため、プログラムの実行中はディスク上の実行ファイルは不要または使用されません。実際、実行ファイルを削除してもプログラムは気にしません...ただし、一部のプログラムは実行ファイルを必要とする可能性があり、特定の OS (Windows など) は実行ファイルをロックして、プログラムの実行中に削除や名前の変更/移動を防止します。Firefox は実際には非常に複雑で、GUI (ユーザー インターフェイス) の構築方法を指示する一連のデータ ファイルを使用するため、動作が停止します。パッケージの更新中にこれらのファイルは上書き (更新) されるため、古い Firefox 実行ファイル (メモリ内) が新しい GUI ファイルを使用しようとすると、奇妙なことが起こる可能性があります...
実行中に自動的に更新されるプログラム a.out を記述したい場合はどうすればよいでしょうか? あなたの質問に対する答えはすでにたくさんあります。こちらをチェックしてください: https://stackoverflow.com/questions/232347/自動アップデーターの実装方法 ちなみに、プログラミングに関する質問は StackOverflow に投稿したほうがよいでしょう。