私は数十年間コンピュータを、10年間はLinuxを専門に扱ってきましたが、実際にはOSの機能のほとんどを魔法とは異なりブラックボックスとして扱っていることに戸惑います。
今日、私はkill
コマンドについて考えました。1日に複数回(「通常」と-9
の両方の方法で)使用していますが、どのように動作するのかまったくわかりません。シーン。
私の観点からは、実行中のプロセスが「ハング」した場合、そのPIDでkill
を呼び出すと、突然実行されなくなります。マジック!
そこで実際に何が起こりますか?マンページは「シグナル」について話しますが、確かにそれは単なる抽象概念です。 kill -9
をプロセスに送信する場合、プロセスの協力(シグナルの処理など)は必要ありません。プロセスを停止するだけです。
本当に知りたい!
プロセスにkill -9を送信しても、プロセスの協力(シグナルの処理など)は必要ありません。プロセスを強制終了するだけです。
一部のシグナルは捕捉および無視できるため、それらはすべて協力を伴うと想定しています。しかし、_man 2 signal
_に従って、「signals SIGKILLおよびSIGSTOPをキャッチまたは無視することはできません」。 SIGTERMをキャッチできるため、プレーンkill
が常に有効であるとは限りません。これは通常、プロセスのハンドラーの何かがうまくいかなかったことを意味します。1
プロセスが特定のシグナルのハンドラーを定義しない(またはできない)場合、カーネルはデフォルトのアクションを実行します。 SIGTERMおよびSIGKILLの場合、これはプロセスを終了することです(ただし、そのPIDは1であり、カーネルは終了しませんinit
)2 つまり、そのファイルハンドルが閉じられ、そのメモリがシステムプールに返され、その親がSIGCHILDを受け取り、その孤立した子がexit
を呼び出したかのように、initによって継承されます(_man 2 exit
_を参照)。 )。プロセスはもはや存在しません–ゾンビとして終わる場合を除いて、その場合でも、いくつかの情報とともにカーネルのプロセステーブルにリストされます。これは、親がwait
を使用せず、この情報を適切に処理しない場合に発生します。ただし、ゾンビプロセスにはメモリが割り当てられていないため、実行を継続できません。
Linuxがプロセスによって使用されるすべてのリソースへの参照を保持し、プロセスを「強制終了」すると、Linuxがそのテーブルを通過してリソースを1つずつ解放する、メモリ内のグローバルテーブルのようなものはありますか?
それで十分だと思います。物理メモリはページごとに追跡され(1ページは通常4 KBのチャンクに相当)、これらのページはグローバルプールから取得され、グローバルプールに返されます。含まれているデータ(つまり、既存のファイルから読み取られたデータ)が再び必要になった場合に解放されたページがキャッシュされるという点で、少し複雑です。
マンページは「シグナル」について話しますが、確かにそれは単なる抽象概念です。
確かに、すべての信号は抽象化です。それらは「プロセス」と同様に概念的なものです。私はセマンティクスを少し遊んでいますが、SIGKILLがSIGTERMと質的に異なるということであれば、はい、いいえです。捕まえられないという意味ではありますが、両方ともシグナルであるという意味ではありません。類推すると、Appleはオレンジではありませんが、リンゴとオレンジは、先入観の定義によれば、両方とも果物です。SIGKILLはmoreキャッチできないので抽象ですが、それでもシグナルです。SIGTERM処理の例を以下に示します。
_#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
void sighandler (int signum, siginfo_t *info, void *context) {
fprintf (
stderr,
"Received %d from pid %u, uid %u.\n",
info->si_signo,
info->si_pid,
info->si_uid
);
}
int main (void) {
struct sigaction sa;
memset(&sa, 0, sizeof(sa));
sa.sa_sigaction = sighandler;
sa.sa_flags = SA_SIGINFO;
sigaction(SIGTERM, &sa, NULL);
while (1) sleep(10);
return 0;
}
_
このプロセスは永遠にスリープします。ターミナルで実行し、kill
を使用してSIGTERMに送信できます。それは次のようなものを吐き出します:
_Received 15 from pid 25331, uid 1066.
_
1066は私のUIDです。 PIDはkill
が実行されるシェルのPID、またはフォークした場合はkillのPID(_kill 25309 & echo $?
_)になります。
繰り返しになりますが、SIGKILLが機能しないため、ハンドラーをSIGKILLに設定しても意味がありません。3 私が_kill -9 25309
_の場合、プロセスは終了します。しかし、それはまだ合図です。カーネルは誰がシグナルを送信したか、どのようなシグナルに関する情報を持っています)等々.
1.可能な信号のリストをまだ確認していない場合は、_kill -l
_を参照してください。
2. Tim Postが以下に述べるように、別の例外は、無停電スリープのプロセスに適用されます。これらは、根本的な問題が解決されるまで起こされないため、すべてのシグナル(SIGKILLを含む)がその間遅延されます。ただし、プロセスはその状況をわざと作成することはできません。
3.これは、実際に_kill -9
_を使用するほうがよいことを意味するものではありません。私のサンプルハンドラーは、exit()
につながらないという意味で悪いものです。 SIGTERMハンドラーの真の目的は、一時ファイルのクリーンアップなどの処理を実行する機会をプロセスに与え、その後、自発的に終了することです。 _kill -9
_を使用する場合、このチャンスは得られないため、「自発的に終了する」部分が失敗したように見える場合にのみ、そのようにします。
各プロセスはスケジュールされた時間実行され、ハードウェアタイマーによって中断されて、他のタスクにそのCPUコアを提供します。これが、CPUコアよりもはるかに多くのプロセスを持つことができる、または単一のコアCPUで多くのプロセスを使用してすべてのオペレーティングシステムを実行することが可能な理由です。
プロセスが中断された後、制御はカーネルコードに戻ります。そのコードは、プロセス側からの協力なしに、中断されたプロセスの実行を再開しないことを決定できます。 kill -9は、プログラムのどの行でも実行される可能性があります。
ここでは、プロセスを強制終了する方法の理想的な説明を示します。実際には、どのUnixバリアントにも多くの複雑さと最適化があります。
カーネルは、プロセスごとにデータ構造を持ち、マッピングしているメモリ、スレッドの種類、スケジュールされている日時、開いているファイルなどの情報を格納します。カーネルがプロセスを強制終了する場合は、次のように書き留めます。プロセスが強制終了されるプロセスのデータ構造(およびおそらく各スレッドのデータ構造内)。
プロセスのスレッドの1つが現在別のCPUでスケジュールされている場合、カーネルはその別のCPUで割り込みをトリガーして、そのスレッドがより迅速に実行を停止するようにします。
スケジューラは、スレッドを強制終了する必要があるプロセスにあることに気づくと、それ以上スケジュールしません。
プロセスのスレッドがスケジュールされなくなった場合、カーネルはプロセスのリソース(メモリ、ファイル記述子など)の解放を開始します。カーネルがリソースを解放するたびに、カーネルはその所有者がまだライブリソースを持っているかどうかをチェックします。プロセスにライブリソース(メモリマッピング、オープンファイル記述子など)がなくなると、プロセス自体のデータ構造を解放し、対応するエントリをプロセステーブルから削除できます。
一部のリソースはすぐに解放できます(I/O操作で使用されていないメモリの割り当て解除など)。他のリソースは待機する必要があります。たとえば、I/O操作の進行中は [〜#〜] dma [〜#の間、I/O操作を説明するデータを解放できません。 〜] が進行中、それがアクセスしているメモリが使用中であり、DMAをキャンセルすると、周辺機器への接続が必要になります)。そのようなリソースのドライバに通知されます、キャンセルを急いでしようとする場合があります。操作が進行中でなくなると、ドライバーはそのリソースの解放を完了します。
(プロセステーブルのエントリは、実際には親プロセスに属しているリソースであり、プロセスが終了 したときに解放され、親がイベントを確認します 。 )