この質問に答えるには、シグナルがプロセスに送信される方法と、プロセスがカーネル内に存在する方法を理解する必要があります。
各プロセスは task_struct
として表されます カーネル内 (定義は sched.h
にあります) ヘッダー ファイルであり、ここから始まります)。その構造体は、プロセスに関する情報を保持します。たとえば、pid。重要な情報は、関連する信号が格納されている行 1566 にあります。これは、プロセスにシグナルが送信された場合にのみ設定されます。
デッド プロセスまたはゾンビ プロセスにはまだ task_struct
があります . 親プロセス (自然または採用による) が wait()
を呼び出すまで、構造体は残ります。 SIGCHLD
を受け取った後 その子プロセスを取得します。シグナルが送信されると、signal_struct
が設定されています。この場合、シグナルがキャッチ可能かどうかは問題ではありません。
プロセスが実行されるたびにシグナルが評価されます。 正確には以前 プロセスはする 走る。プロセスは TASK_RUNNING
にあります 州。カーネルは schedule()
を実行します スケジューリングアルゴリズムに従って次に実行されるプロセスを決定するルーチン。このプロセスが次に実行中のプロセスであると仮定すると、signal_struct
の値は 処理する待機シグナルがあるかどうかに関係なく評価されます。シグナルハンドラが手動で定義されている場合 (signal()
を介して) または sigaction()
)、シグナルのデフォルト アクションでない場合は、登録された関数が実行されます 実行されます。デフォルトのアクションは、送信されるシグナルによって異なります。
たとえば、SIGSTOP
シグナルのデフォルト ハンドラーは、現在のプロセスの状態を TASK_STOPPED
に変更します。 そして schedule()
を実行します 実行する新しいプロセスを選択します。注意、SIGSTOP
キャッチできません (SIGKILL
など) )、したがって、手動シグナル ハンドラを登録する可能性はありません。キャッチできないシグナルの場合、デフォルトのアクションが常に実行されます。
あなたの質問へ:
機能していないプロセスまたは停止しているプロセスは、スケジューラによって TASK_RUNNING
にあると判断されることはありません。 再度述べます。したがって、カーネルは、対応するシグナルのシグナルハンドラー (デフォルトまたは定義済み) を実行することはありません。したがって、exit_signal
再び設定されることはありません。 signal_struct
を設定することにより、シグナルがプロセスに「配信」されます。 task_struct
で プロセスの再実行はありませんが、プロセスは二度と実行されないため、他には何も起こりません。実行するコードはありません。プロセスに残っているのはそのプロセス構造体だけです。
ただし、親プロセスが wait()
までに子プロセスを取得した場合 、受け取る終了コードは、プロセスが「最初に」終了したときのものです。処理待ちのシグナルがあっても問題ありません。
ゾンビ プロセスは、基本的に既に死んでいます。唯一のことは、誰もその死をまだ認めていないため、プロセス テーブルのエントリと制御ブロック (活動中のすべてのスレッドに対して Linux カーネルが維持する構造) を占有し続けていることです。ファイルの強制ロック、共有メモリ セグメント、セマフォなどの他のリソースは回収されます。
誰もこの信号に基づいて行動できないため、それらに信号を送ることはできません。プロセスはすでに実行を終了しているため、KILL のような致命的なシグナルでさえ役に立ちません。自分で試すことができます:
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main(void)
{
pid_t pid = fork();
if (pid == -1)
exit(-1);
if (pid > 0) {
//parent
printf("[parent]: I'm the parent, the pid of my child is %i\n"
"I'll start waiting for it in 10 seconds.\n", pid);
sleep(10);
int status;
wait(&status);
if (WIFSIGNALED(status)) {
printf("[parent]: My child has died from a signal: %i\n", WTERMSIG(status));
} else if (WIFEXITED(status)) {
printf("[parent]: My child has died from natural death\n");
} else {
printf("[parent]: I don't know what happened to my child\n");
}
} else {
//child
printf("[child]: I'm dying soon, try to kill me.\n");
sleep(5);
printf("[child]: Dying now!\n");
}
return 0;
}
ここでは、子プロセスを待機する前に fork してスリープするプロセスを開始します。子供は少し寝るだけです。子が眠っているとき、または子が終了した直後に殺して違いを確認できます:
$ make zombie
cc zombie.c -o zombie
$ ./zombie
[parent]: I'm the parent, the pid of my child is 16693
I'll start waiting for it in 10 seconds.
[child]: I'm dying soon, try to kill me.
# Here, I did "kill -15 16693" in another console
[parent]: My child has died from a signal: 15
$ ./zombie
[parent]: I'm the parent, the pid of my child is 16717
I'll start waiting for it in 10 seconds.
[child]: I'm dying soon, try to kill me.
[child]: Dying now!
# Here, I did "kill -15 16717" in another console
[parent]: My child has died from natural death