私はどのように理解しようとしている CTRL+C 親プロセスではなく、子プロセスを終了します。 bash
のようないくつかのスクリプトシェルでこの動作を確認します。ここでは、長時間実行されるプロセスを開始してから、 CTRL-C そして、コントロールはシェルに戻ります。
どのように機能するのか、特に親(シェル)プロセスが終了しないのはなぜですか?
シェルは、特別な処理を行う必要がありますか CTRL+C イベントとはいの場合、それは正確に何をしますか?
デフォルトでは、シグナルはカーネルによって処理されます。古いUnixシステムには15のシグナルがありました。今はもっとあります。確認してもいい </usr/include/signal.h>
(またはkill -l)。 CTRL+C SIGINT
という名前の信号です。
各シグナルを処理するためのデフォルトのアクションもカーネルで定義されており、通常はシグナルを受信したプロセスを終了します。
すべての信号(ただしSIGKILL
)はプログラムで処理できます。
そして、これがシェルの機能です。
find
などのプログラムを実行すると、シェル:fork
s自体シェルスクリプトでtrap
信号もできます...
また、インタラクティブシェルの信号処理も設定できます。先頭にこれを入力してください~/.profile
。 (すでにログインしていることを確認し、anotherターミナルでテストします-自分でロックアウトできます)
trap 'echo "Dont do this"' 2
今、押すたびに CTRL+C シェルでは、メッセージが出力されます。行を削除することを忘れないでください!
興味があれば、プレーン/bin/sh
ソースコードでのシグナル処理 here 。
上記では、コメントにいくつかの誤った情報がありました(現在は削除されています)ので、ここに興味のある人が非常にいいリンクである場合- 信号処理の仕組み 。
まず、 POSIX端末インターフェイスに関するWikipediaの記事 ずっと読んでください。
SIGINT
信号は、端末回線制御によって生成され、端末のフォアグラウンドプロセスグループ内のすべてのプロセスにブロードキャストされます。シェルは、実行したコマンド(またはコマンドパイプライン)の新しいプロセスグループを既に作成しており、そのプロセスグループがその(ターミナルの)フォアグラウンドプロセスグループであることを端末に伝えています。すべての同時コマンドパイプラインには独自のプロセスグループがあり、foregroundコマンドパイプラインは、シェルがターミナルとしてプログラムされたプロセスグループを持つものです端末のフォアグラウンドプロセスグループ。フォアグラウンドとバックグラウンドの間の「ジョブ」の切り替えは(詳細は別として)、どのプロセスグループがフォアグラウンドのプロセスであるかを端末に伝えることです。
シェルプロセス自体は、さらに別のプロセスグループに属しているため、これらのプロセスグループのいずれかがフォアグラウンドにある場合、シグナルを受信しません。とても簡単です。
端末は、現在端末に接続されているプロセスにINT(割り込み)信号を送信します。プログラムはそれを受け取り、それを無視するか、終了するかを選択できます。
必ずしも強制的に閉じられるプロセスはありません(デフォルトでは、sigintを処理しない場合、動作はabort()
を呼び出すことですが、それを調べる必要があります)。
もちろん、実行中のプロセスは、それを起動したシェルから分離されています。
wanted移動する親シェルの場合、exec
を使用してプログラムを起動します。
exec ./myprogram
そのようにして、親シェルは子プロセスに置き換えられます
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);
}
_
コンパイル:
_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 + \
_を使用した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シグナルを送信します。これはプロセスを中断します。