web-dev-qa-db-ja.com

LinuxでゾンビプロセスにSIGKILLを送信するとどうなりますか?

Linuxでは、子プロセスが終了し、その親がまだその上で待機していない場合、ゾンビプロセスになります。子の終了コードはpid記述子に格納されます。

SIGKILLが子に送信された場合、影響はありません。

これは、終了コードがSIGKILLによって変更されないことを意味しますか?または、終了コードが変更されて、子がSIGKILLを受け取ったために終了したことを示しますか?

10
user137481

その質問に答えるには、シグナルがプロセスに送信される方法と、プロセスがカーネルにどのように存在するかを理解する必要があります。

各プロセスは、カーネル内で_task_struct_として表されます(定義は_sched.h_ヘッダーファイルにあり、 here で始まります)。その構造体はプロセスに関する情報を保持しています。たとえば、pid。重要な情報は 行1566 にあり、関連する信号が格納されています。これは、シグナルがプロセスに送信された場合にのみ設定されます。

デッドプロセスまたはゾンビプロセスには、_task_struct_。がまだ残っています。親プロセス(自然または養子縁組による)が呼び出されるまで、構造体は残りますSIGCHLDを受け取った後のwait()は、子プロセスを取得します。シグナルが送信されると、_signal_struct_が設定されます。この場合、信号がキャッチ可能な信号であるかどうかは関係ありません。

シグナルはプロセスが実行されるたびに評価されます。または、正確には、beforeプロセスwouldrun。その後、プロセスは_TASK_RUNNING_状態になります。カーネルはschedule()ルーチンを実行し、そのスケジューリングアルゴリズムに従って次の実行プロセスを決定します。このプロセスが次に実行されるプロセスであると仮定すると、処理される待機シグナルがあるかどうかに関係なく、_signal_struct_の値が評価されます。シグナルハンドラーが手動で定義されている場合( signal() または sigaction() を使用)、登録されている関数が実行されます。 信号のデフォルトアクション が実行されます。デフォルトのアクションは、送信される信号によって異なります。

たとえば、SIGSTOPシグナルのデフォルトハンドラーは、現在のプロセスの状態を_TASK_STOPPED_に変更してから、schedule()を実行して、実行する新しいプロセスを選択します。 SIGSTOPは(SIGKILLのように)キャッチできないため、手動シグナルハンドラーを登録することはできません。キャッチできないシグナルの場合、デフォルトのアクションが常に実行されます。


あなたの質問へ:

機能しないプロセスまたはデッドプロセスは、スケジューラによって再び_TASK_RUNNING_状態にあると判断されることはありません。したがって、カーネルは、対応するシグナルのシグナルハンドラー(デフォルトまたは定義済み)を実行しません。したがって、 _exit_signal_ が再び設定されることはありません。シグナルは、プロセスの_signal_struct_に_task_struct_を設定することによってプロセスに「配信」されますが、プロセスが再度実行されることはないため、他に何も起こりません。実行するコードはなく、プロセスに残っているのはそのプロセス構造体だけです。

ただし、親プロセスがその子をwait()で取得する場合、受信する終了コードは、プロセスが「最初に」停止したときのものです。処理されるのを待っているシグナルがあってもかまいません。

14
chaos

ゾンビプロセスは基本的にすでに死んでいます。唯一のことは、誰もその死を認めていないため、プロセステーブルのエントリと制御ブロック(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;
}

ここでは、子供を待つ前に、フォークしてスリープするプロセスを開始します。子供は少し寝るだけです。眠っているとき、または終了した直後に子供を殺して、違いを確認できます。

$ 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
9
lgeorget