シェルプロセス(例sh
)とそのサブプロセス(例cat
)が与えられた場合、シェルのプロセスIDを使用して+アクションをシミュレートする方法はCtrl?C
私が試したことは次のとおりです。
sh
次に実行しますcat
。
[user@host ~]$ sh
sh-4.3$ cat
test
test
SIGINT
cat
他の端末から送信:
[user@host ~]$ kill -SIGINT $PID_OF_CAT
cat
信号が受信され終了します(予想どおり)。
親プロセスにシグナルを送信するのはうまくいかないようです。シグナルがcat
親プロセスに送信されたときに伝播されないのはなぜですかsh
?
これはうまくいきません:
[user@host ~]$ kill -SIGINT $PID_OF_SH
答え1
CTRL+C仕組み
最初に理解する必要があるのは、CTRL+がどのようにC機能するかです。
CTRL+を押すと、C端末エミュレータはETX文字(End of Text / 0x03)を送信します。
TTYは、この文字を受信するとSIGINTを端末のフォアグラウンドプロセスグループに送信するように構成されています。この設定はを実行して見ると見ることがstty -a
できますintr = ^C;
。これPOSIX仕様INTR を受信すると、SIGINT を端末のフォアグラウンド・プロセス・グループに送信する必要があることを示します。
フォアグラウンドプロセスグループとは何ですか?
今問題は、フォアグラウンドプロセスグループが何であるかを判断する方法です。フォアグラウンドプロセスグループは、単にキーボードから生成されたすべての信号(SIGTSTP、SIGINTなど)を受信するプロセスグループです。
プロセスグループIDを確認する最も簡単な方法は、次を使用することですps
。
ps ax -O tpgid
2番目の列はプロセスグループIDです。
プロセスグループにシグナルを送信するには?
これでプロセスグループIDが何であるかがわかったので、グループ全体にシグナルを送信するPOSIXの動作をシミュレートする必要があります。
kill
グループIDを前に追加するだけです。たとえば、プロセスグループIDが1234の場合は、次のようにします。-
kill -INT -1234
端末番号を使用してCTRL+をシミュレートします。C
CTRLしたがって、上記は+をC手動プロセスとしてシミュレートする方法を示しています。しかし、TTY番号を知っていて、その端末の+をエミュレートしたい場合はCTRLどうすればいいですか?C
とても簡単です。
$tty
ターゲットにしたい端末だとしましょう(tty | sed 's#^/dev/##'
端末で実行して取得できます)。
kill -INT -$(ps h -t $tty -o tpgid | uniq)
これにより、フォアグラウンドプロセスグループにSIGINTが送信されます$tty
。
答え2
〜のようにvinc17 言う、これが起こる理由はありません。信号生成キーシーケンス(例:Ctrl+)を入力すると、C信号は端末に接続されている(関連する)すべてのプロセスに送信されます。生成された信号にはそのようなメカニズムはありませんkill
。
しかし、このようなコマンドは
kill -SIGINT -12345
すべてのプロセスに信号を送信します。プロセスグループ12345;見てください殺す(1) そして殺す(2)。シェルの子プロセスは通常シェルのプロセスグループにあるため(少なくとも非同期でない場合)、負のシェルPIDにシグナルを送信すると、必要に応じて機能できます。
こんな
〜のようにvinc17が指摘しました。、これは対話型シェルでは機能しません。これは代替です可能働く:
kill -SIGINT -$(echo $(ps -p)シェルPIDtpgid=))
ps -pPID_of_shell
シェルのプロセス情報を取得します。ヘッダーなしで端末プロセスグループIDのみを出力する必要があることo tpgid=
を示します。ps
10000未満の場合、ps
先行スペースが表示されます。これは$(echo …)
先行(および末尾)スペースを削除する簡単な方法です。
私はDebianマシンでおおよそのテストをしました。
答え3
この質問には独自の回答が含まれています。 Send SIGINT
to cat
process はkill
+ を押すと何が起こるかを完全にシミュレートします。CtrlC
より正確には、割り込み文字は^C
(デフォルトでは)SIGINT
端末の前景プロセスグループ内のすべてのプロセスに送信されます。cat
複数のプロセスを含むより複雑なコマンドを実行する場合^C
。
バックグラウンド演算子なしで外部コマンドを実行すると、&
シェルはコマンドの新しいプロセスグループを作成し、プロセスグループがフォアグラウンドにあることを端末に通知します。シェルは独自のプロセスグループに残り、フォアグラウンドにはもうありません。その後、シェルはコマンドが終了するのを待ちます。
ここで一般的な誤解、つまりシェルが子プロセスとターミナル間の相互作用を促進するために何かをしているという考えの犠牲になるようです。それは本当ではありません。設定(プロセス生成、ターミナルモード設定、パイプ生成、その他のファイル記述子リダイレクト、ターゲットプログラムの実行)が完了したら、シェルただ待っています。。cat
通常の入力であるか信号を生成する特殊文字(たとえば)であれ、入力した内容はシェルを介して渡されません^C
。プロセスは独自のファイル記述子を介して端末に直接アクセスでき、フォアグラウンドプロセスグループであるため、cat
端末はプロセスに直接信号を送信できます。cat
皮が剥がれた。
プロセスが終了すると、cat
そのプロセスの親プロセスであるシェルに通知が送信されますcat
。これにより、シェルがアクティブになり、前景として再び表示されます。
理解度を高める練習です。
新しい端末のシェルプロンプトで、次のコマンドを実行します。
exec cat
このキーワードを使用すると、子プロセスを作成せずにexec
シェルが実行されます。cat
シェルはで置き換えられますcat
。以前シェルに属していたPIDは、現在のPIDですcat
。ps
他の端末でこれを確認してください。任意の行を入力してcat
繰り返し表示されることを確認すると、親プロセスであるシェルプロセスがなくてもまだ正常に機能することがわかります。今+を押すとCtrlどうなりますかC?
答え:
SIGINT は終了する cat プロセスに渡されます。端末の唯一のプロセスなので、シェルプロンプトで「exit」と言ったようにセッションが終了します。実際に猫以前はしばらくの間あなたの殻。
答え4
setpgid
POSIX C プロセスグループの最小例
基本 API の実行可能な最小の例を理解する方が簡単です。
これは、子プロセスがプロセスグループを変更していない場合に子プロセスにシグナルを送信する方法を示していますsetpgid
。
メインプログラム
#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
プログラムが中断されます。
ご覧のとおり、2つのプロセスのpgidは横にあるので同じですfork
。
次に、クリックするたびに次の操作を行います。
Ctrl + C
再出力されます:
sigint parent
sigint child
これは次の方法を示しています。
- プロセスグループ全体にシグナルを送信する
kill(-pgid, SIGINT)
- ターミナルでは、Ctrl + Cはデフォルトでプロセスグループ全体にkillコマンドを送信します。
SIGQUITなど、2つのプロセスに異なる信号を送信してプログラムを終了しますCtrl + \
。
次に実行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でテストされました。