web-dev-qa-db-ja.com

Ctrl-Cはどのように子プロセスを終了しますか?

私はどのように理解しようとしている CTRL+C 親プロセスではなく、子プロセスを終了します。 bashのようないくつかのスクリプトシェルでこの動作を確認します。ここでは、長時間実行されるプロセスを開始してから、 CTRL-C そして、コントロールはシェルに戻ります。

どのように機能するのか、特に親(シェル)プロセスが終了しないのはなぜですか?

シェルは、特別な処理を行う必要がありますか CTRL+C イベントとはいの場合、それは正確に何をしますか?

61
vitaut

デフォルトでは、シグナルはカーネルによって処理されます。古いUnixシステムには15のシグナルがありました。今はもっとあります。確認してもいい </usr/include/signal.h>(またはkill -l)。 CTRL+C SIGINTという名前の信号です。

各シグナルを処理するためのデフォルトのアクションもカーネルで定義されており、通常はシグナルを受信したプロセスを終了します。

すべての信号(ただしSIGKILL)はプログラムで処理できます。

そして、これがシェルの機能です。

  • シェルが対話モードで実行されている場合、このモード用の特別な信号処理があります。
  • findなどのプログラムを実行すると、シェル:
    • forks自体
    • そして、子のためにデフォルトの信号処理を設定します
    • 子を特定のコマンドに置き換えます(例:find)
    • を押すと CTRL+C、親シェルはこの信号を処理しますが、子はそれを受け取ります-デフォルトのアクションで-終了します。 (子もシグナル処理を実装できます)

シェルスクリプトでtrap信号もできます...

また、インタラクティブシェルの信号処理も設定できます。先頭にこれを入力してください~/.profile。 (すでにログインしていることを確認し、anotherターミナルでテストします-自分でロックアウトできます)

trap 'echo "Dont do this"' 2

今、押すたびに CTRL+C シェルでは、メッセージが出力されます。行を削除することを忘れないでください!

興味があれば、プレーン/bin/shソースコードでのシグナル処理 here

上記では、コメントにいくつかの誤った情報がありました(現在は削除されています)ので、ここに興味のある人が非常にいいリンクである場合- 信号処理の仕組み

62
jm666

まず、 POSIX端末インターフェイスに関するWikipediaの記事 ずっと読んでください。

SIGINT信号は、端末回線制御によって生成され、端末のフォアグラウンドプロセスグループ内のすべてのプロセスにブロードキャストされます。シェルは、実行したコマンド(またはコマンドパイプライン)の新しいプロセスグループを既に作成しており、そのプロセスグループがその(ターミナルの)フォアグラウンドプロセスグループであることを端末に伝えています。すべての同時コマンドパイプラインには独自のプロセスグループがあり、foregroundコマンドパイプラインは、シェルがターミナルとしてプログラムされたプロセスグループを持つものです端末のフォアグラウンドプロセスグループ。フォアグラウンドとバックグラウンドの間の「ジョブ」の切り替えは(詳細は別として)、どのプロセスグループがフォアグラウンドのプロセスであるかを端末に伝えることです。

シェルプロセス自体は、さらに別のプロセスグループに属しているため、これらのプロセスグループのいずれかがフォアグラウンドにある場合、シグナルを受信しません。とても簡単です。

21
JdeBP

端末は、現在端末に接続されているプロセスにINT(割り込み)信号を送信します。プログラムはそれを受け取り、それを無視するか、終了するかを選択できます。

必ずしも強制的に閉じられるプロセスはありません(デフォルトでは、sigintを処理しない場合、動作はabort()を呼び出すことですが、それを調べる必要があります)。

もちろん、実行中のプロセスは、それを起動したシェルから分離されています。

wanted移動する親シェルの場合、execを使用してプログラムを起動します。

exec ./myprogram

そのようにして、親シェルは子プロセスに置き換えられます

7
sehe

setpgid POSIX Cプロセスグループの最小例

基礎となるAPIの最小限の実行可能な例を使用すると、理解しやすくなる場合があります。

これは、子がsetpgidでプロセスグループを変更しなかった場合に、どのように信号が子に送信されるかを示しています。

main.c

_#define _XOPEN_SOURCE 700
#include <assert.h>
#include <signal.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

volatile sig_atomic_t is_child = 0;

void signal_handler(int sig) {
    char parent_str[] = "sigint parent\n";
    char child_str[] = "sigint child\n";
    signal(sig, signal_handler);
    if (sig == SIGINT) {
        if (is_child) {
            write(STDOUT_FILENO, child_str, sizeof(child_str) - 1);
        } else {
            write(STDOUT_FILENO, parent_str, sizeof(parent_str) - 1);
        }
    }
}

int main(int argc, char **argv) {
    pid_t pid, pgid;

    (void)argv;
    signal(SIGINT, signal_handler);
    signal(SIGUSR1, signal_handler);
    pid = fork();
    assert(pid != -1);
    if (pid == 0) {
        is_child = 1;
        if (argc > 1) {
            /* Change the pgid.
             * The new one is guaranteed to be different than the previous, which was equal to the parent's,
             * because `man setpgid` says:
             * > the child has its own unique process ID, and this PID does not match
             * > the ID of any existing process group (setpgid(2)) or session.
             */
            setpgid(0, 0);
        }
        printf("child pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)getpgid(0));
        assert(kill(getppid(), SIGUSR1) == 0);
        while (1);
        exit(EXIT_SUCCESS);
    }
    /* Wait until the child sends a SIGUSR1. */
    pause();
    pgid = getpgid(0);
    printf("parent pid, pgid = %ju, %ju\n", (uintmax_t)getpid(), (uintmax_t)pgid);
    /* man kill explains that negative first argument means to send a signal to a process group. */
    kill(-pgid, SIGINT);
    while (1);
}
_

GitHubアップストリーム

コンパイル:

_gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -Wpedantic -o setpgid setpgid.c
_

setpgidなしで実行

CLI引数がない場合、setpgidは実行されません。

_./setpgid
_

可能な結果:

_child pid, pgid = 28250, 28249
parent pid, pgid = 28249, 28249
sigint parent
sigint child
_

プログラムがハングします。

ご覧のとおり、両方のプロセスのpgidはforkを介して継承されるため、同じです。

その後、あなたがヒットするたびに:

_Ctrl + C
_

再び出力します:

_sigint parent
sigint child
_

これは次の方法を示しています。

  • kill(-pgid, SIGINT)でプロセスグループ全体にシグナルを送信する
  • 端末のCtrl + Cは、デフォルトでプロセスグループ全体にkillを送信します

両方のプロセスに異なるシグナルを送信して、プログラムを終了します。 _Ctrl + \_を使用したSIGQUIT。

setpgidで実行

引数を指定して実行する場合、例:

_./setpgid 1
_

次に、子はそのpgidを変更し、今度は親からのみ毎回1つのsigintのみが印刷されます:

_child pid, pgid = 16470, 16470
parent pid, pgid = 16469, 16469
sigint parent
_

そして今、あなたがヒットするたびに:

_Ctrl + C
_

親だけが信号も受信します:

_sigint parent
_

以前と同様に、SIGQUITを使用して親を殺すことができます。

_Ctrl + \
_

ただし、子は別のPGIDを持ち、そのシグナルを受信しません!これは以下から見ることができます:

_ps aux | grep setpgid
_

あなたはそれを明示的に殺す必要があります:

_kill -9 16470
_

これにより、シグナルグループが存在する理由が明確になります。それ以外の場合は、常に手動でクリーニングするために残っているプロセスの束を取得します。

Ubuntu 18.04でテスト済み。

CTRL+C killコマンドへのマップです。それらを押すと、killはSIGINTシグナルを送信します。これはプロセスを中断します。

キル: http://en.wikipedia.org/wiki/Kill_(command)

SIGINT: http://en.wikipedia.org/wiki/SIGINT_(POSIX)

1
Brice Favre