termA はフリーズしません。入力ボックスにコールバックを表示しているだけです。 Enter
を押すだけです キーを押して入力を続行してください。
問題を説明する前に、read
について少し説明します。 コマンドが動作します。 stdin
から入力データを読み込みます EOF
まで 遭遇します。 read
への呼び出しと言っても安全です コマンドは、ディスク上のファイルからの読み取りに関してはノンブロッキングです。しかし stdin
の場合 端末に接続されている場合、ユーザーが何かを入力するまでコマンドはブロックされます。
シグナルハンドラはどのように機能しますか?
シグナル処理がどのように機能するかについての簡単な説明。以下の C
のスニペットを参照してください SIGINT
で動作するだけです (別名 CTRL+C
)
#include <stdio.h>
#include <signal.h>
/* signal handler definition */
void signal_handler(int signum){
printf("Hello World!\n");
}
int main(){
//Handle SIGINT with a signal handler
signal(SIGINT, signal_handler);
//loop forever!
while(1);
}
シグナルハンドラを登録し、無限ループに入ります。 Ctrl-C
を叩いたとき 、シグナルハンドラ signal_handler()
が 実行する必要があり、"Hello World!"
画面に出力されますが、プログラムは無限ループに陥っていました。 "Hello World!"
を出力するには シグナルハンドラを実行するためにループを壊したのは間違いないですよね?したがって、プログラムだけでなくループも終了する必要があります。見てみましょう:
gcc -Wall -o sighdl.o signal.c
./sighdl.o
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
^CHello World!
出力が示すように、 Ctrl-C
を発行するたびに 、 "Hello World!"
と出力されますが、プログラムは無限ループに戻ります。 SIGQUIT
を発行した後でのみです Ctrl-\
の信号 プログラムは実際に終了しましたか。 ulimit
に応じて コアをダンプするか、受信したシグナル番号を出力します。
ループが終了するという解釈は妥当ですが、シグナル処理の主な理由、つまり非同期イベント処理は考慮されていません。つまり、シグナル ハンドラーは、プログラムの制御の標準フローから外れて動作します。実際、プログラム全体がコンテキスト内に保存され、シグナル ハンドラーが実行されるためだけに新しいコンテキストが作成されます。シグナル ハンドラーがアクションを完了すると、コンテキストが元に戻り、通常の実行フローが開始されます (つまり、 while(1)
).
ご質問にお答えするには、
<ブロック引用>結論は、bash が外部コマンドをフォアグラウンドで実行している場合、フォアグラウンド プロセスが終了するまで受信したシグナルを処理しないことを確認しました
ここで注意すべき重要なことは、外部 コマンド部分。最初のケースでは、sleep
は外部プロセスですが、2 番目のケースでは read
シェル自体からの組み込みです。したがって、これら 2 つの信号の伝播は、これらの両方のケースで異なります
type read
read is a shell builtin
type sleep
sleep is /usr/bin/sleep
<ブロック引用> 1. termA という名前のターミナルを開き、作成されたファイル callback.sh を /bin/bash callback.sh で初めて実行すると、情報が即座にポップアップします。
はい、この動作は予期されたものです。この時点では関数のみが定義されており、トラップ ハンドラは関数 myCallback
に登録されているためです。 信号はスクリプトでまだ受信されていません。実行シーケンスが進むにつれて、read
からのメッセージ プロンプトが初めてスローされます。
2.termB という名前の新しいターミナルを開き、pkill -USR1 -f callback.sh
を実行します。 初めて、termA で情報が即座にポップアップします
はい、read
コマンドは文字列の後に Enter が続くのを待っています EOF
を知らせるキープレス 、シグナル SIGUSR1
を受信します 他の端末からは、現在の実行コンテキストが保存され、現在の日付を含む文字列を出力するシグナル ハンドラーにコントロールが切り替えられます。
ハンドラーの実行が終了するとすぐに、コンテキストは while
に戻ります read
のループ コマンドはまだ入力文字列を待っています。 read
まで コマンドが成功した場合、後続のすべてのシグナル トラップは、シグナル ハンドラー内の文字列を出力するだけです。
termB に進み、pkill -USR1 -f callback.sh
を実行します
前に説明したのと同じ read
コマンドは、while ループで一度は完了しません。文字列の読み取りに成功した場合にのみ、ループの次の繰り返しが開始され、新しいプロンプト メッセージがスローされます。
画像ソース:The Linux Programming Interface by Michael KerrisK
<ブロック引用>
結論として、bash がフォアグラウンドで外部コマンドを実行している場合、フォアグラウンド プロセスが終了するまで受信したシグナルを処理しないことが検証されました。
bash
する C
でシグナルを処理する / 実装レベルですが、trap
で設定されたハンドラは実行されません フォアグラウンド プロセスが終了するまで。これは標準で要求されています:
When a signal for which a trap has been set is received while the shell is waiting for the completion of a utility executing a foreground command, the trap associated with that signal shall not be executed until after the foreground command has completed.
標準では、組み込みコマンドと外部コマンドに違いがないことに注意してください。 read
のような「ユーティリティ」の場合 、別のプロセスで実行されないため、シグナルがその「コンテキスト」で発生するのか、メインシェルの「コンテキスト」で発生するのか、およびハンドラーが trap
で設定されているのかどうかは明らかではありません read
の前後に実行する必要があります
問題 1:callback.sh には無限の while ループが含まれています。フォアグラウンド プロセスが終了するまで受信した信号を処理しないことを説明する方法。この場合、フォアグラウンド プロセスは決して終了しません。
それは間違っている。 bash
while
を実行していません 別のプロセスでループします。フォアグラウンド プロセス bash
がないため trap
で設定された任意のハンドラを実行できます すぐに。 read
の場合 while
ではなく、外部コマンドでした。 フォアグラウンド プロセスになります。
issue2:いいえ please input something for foo: shown
タームA;
termB に移動し、pkill -USR1 -f callback.sh
を実行します 三度目。
次の情報が termA に再び表示されます:callback function called at Mon Nov 19 09:07:24 HKT 2018
まだ please input something for foo:
はありません termA に示されています。情報 please input something for foo:
の理由 フリーズする?
フリーズしません。 Enter を押すだけです
それだけです
a) bash
read
を再起動します ビルトインインプレイス シグナルによって中断された場合 -- while
を介して戻りません。 ループ
b) -p
で設定されたプロンプトを再表示しません。
bash
に注意してください SIGINT
の場合、プロンプトを再び表示することさえありません ユーザーからこれまでに読み取った文字列を破棄する場合でも、キーボードから処理されました:
$ cat goo
trap 'echo INT' INT
echo $$
read -p 'enter something: ' var
echo "you entered '$var'"
$ bash goo
24035
enter something: foo<Ctrl-C>^CINT
<Ctrl-C>^CINT
<Ctrl-C>^CINT
bar<Enter>
you entered 'bar'
それは you entered 'foobar'
を出力します 信号が kill -INT <pid>
の別のウィンドウから送信された場合 Ctrl-C の代わりに ターミナルから。
この最後の部分のすべてのもの (how read
中断されるなど)は非常にbash
です 明確な。 ksh
のような他のシェルでは または dash
、そして bash
でも POSIX モードで実行した場合 (bash --posix
)、処理されたシグナルは実際に read
を中断します 組み込み。上記の例では、シェルは you entered ''
を出力します。 最初の ^C の後に終了し、 read
の場合 がループから呼び出されると、ループが再開されます。