次のスクリプトがあります。
suspense_cleanup () {
echo "Suspense clean up..."
}
int_cleanup () {
echo "Int clean up..."
exit 0
}
trap 'suspense_cleanup' SIGTSTP
trap 'int_cleanup' SIGINT
sleep 600
実行してを押すと表示され、Ctrl-C
終了Int clean up...
します。
Ctrl-Z
ただし、を押すと、^Z
文字が画面に表示されて停止します。
私がすることができます:
- そこにクリーンアップコードを実行し
Ctrl-Z
、多分何かをエコーすることもできます。 - その後も引き続き一時停止しますか?
glibcのドキュメントをランダムに読み込んで、次のことを見つけました。
SUSP文字の一般的な解釈を無効にするアプリケーションは、ユーザーに操作を中止するための別のメカニズムを提供する必要があります。ユーザーがこのメカニズムを呼び出すと、プログラムはプロセス自体だけでなくプロセスのプロセスグループにSIGTSTPシグナルを送信する必要があります。
しかし、これがここに当てはまるかどうかはわかりませんが、とにかく機能しないようです。
コンテキスト:Vim / Neovimでサポートされているすべてのサスペンス関連機能をサポートするインタラクティブシェルスクリプトを作成しようとしています。今すぐ:
- プログラムで中断する機能(
:suspend
ユーザーがCtrl-zを押すのではなくCtrl-zを使用) - 一時停止する前に操作を実行する機能(Vimに自動保存)
- 再開する前にタスクを実行する機能(NeoVimのVimResume)
編集する:sleep 600
に変更してもfor x in {1..100}; do sleep 6; done
機能しません。
編集2:sleep 600
と交換すると機能しますsleep 600 & wait
。私はそれがなぜ、それがどのように機能するのか、またはそのようなものの制限が何であるかを全く確信していません。
答え1
Linuxや他のUNIX系システムでの信号処理は、カーネル端末ドライバ、上位→下位関係、プロセスグループ、端末制御、タスク制御のためのシェル信号処理の有効/無効、各プロセスの信号ハンドラなど、多くのプレイヤーが関わる非常に複雑なテーマです。 、等。
まず、Control- C、
Control-keyバインディングはZシェルではなくカーネルによって処理されます。次のコマンドを使用してデフォルト定義を表示できますstty -a
。
$ stty -a
speed 38400 baud; rows 64; columns 212; line = 0;
intr = ^C; quit = ^\; erase = ^H; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc
ここで私たちはintr = ^C
科を見ますsusp = ^Z
。 sttyはTCGETS ioctlを使用してカーネルからこの情報を取得します。
システムコール:
$ strace stty -a |& grep TCGETS
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
主キーバインディングはLinuxカーネルコードで定義されています。
#define INIT_C_CC { \
[VINTR] = 'C'-0x40, \
[VQUIT] = '\\'-0x40, \
[VERASE] = '\177', \
[VKILL] = 'U'-0x40, \
[VEOF] = 'D'-0x40, \
[VSTART] = 'Q'-0x40, \
[VSTOP] = 'S'-0x40, \
[VSUSP] = 'Z'-0x40, \
[VREPRINT] = 'R'-0x40, \
[VDISCARD] = 'O'-0x40, \
[VWERASE] = 'W'-0x40, \
[VLNEXT] = 'V'-0x40, \
INIT_C_CC_VDSUSP_EXTRA \
[VMIN] = 1 }
基本操作も定義されます。
static void n_tty_receive_char_special(struct tty_struct *tty, unsigned char c,
bool lookahead_done)
{
struct n_tty_data *ldata = tty->disc_data;
if (I_IXON(tty) && n_tty_receive_char_flow_ctrl(tty, c, lookahead_done))
return;
if (L_ISIG(tty)) {
if (c == INTR_CHAR(tty)) {
n_tty_receive_signal_char(tty, SIGINT, c);
return;
} else if (c == QUIT_CHAR(tty)) {
n_tty_receive_signal_char(tty, SIGQUIT, c);
return;
} else if (c == SUSP_CHAR(tty)) {
n_tty_receive_signal_char(tty, SIGTSTP, c);
return;
}
シグナルはついに次の__kill_pgrp_info()に達します。
/*
* __kill_pgrp_info() sends a signal to a process group: this is what the tty
* control characters do (^C, ^Z etc)
* - the caller must hold at least a readlock on tasklist_lock
*/
Controlこれは私たちの物語にとって重要です。C-and-を使用してControl生成された信号が Z前景に送信されます。プロセスグループ新しく実行されるスクリプトは、リーダーである親対話型シェルによって生成されます。スクリプトとその子供たちグループに属しています。
したがって、ユーザーの意見で正しく指摘したように、カミール・マコロフスキー、送信時Control-Zスクリプトを起動した後、スクリプトはSIGTSTP信号を受信します。そして sleep
信号がグループに送信されると、グループ内のすべてのプロセスが信号を受信するためです。コードからトラップを削除すると、次のように表示されるかどうかを簡単に確認できます(ただし、常に
https://en.wikipedia.org/wiki/shebang_(Unix)、Shebangがなければどうなるかという定義はありません.))
#!/usr/bin/env bash
# suspense_cleanup () {
# echo "Suspense clean up..."
# }
# int_cleanup () {
# echo "Int clean up..."
# exit 0
# }
# trap 'suspense_cleanup' SIGTSTP
# trap 'int_cleanup' SIGINT
sleep 600
実行し(名前をsigtstp.shとして指定)、停止します。
$ ./sigtstp.sh
^Z
[1]+ Stopped ./sigtstp.sh
$ ps aux | grep -e '[s]leep 600' -e '[s]igtstp.sh'
ja 27062 0.0 0.0 6908 3144 pts/25 T 23:50 0:00 sh ./sigstop.sh
ja 27063 0.0 0.0 2960 1664 pts/25 T 23:50 0:00 sleep 600
ja
私のユーザー名は異なり、ユーザー名も異なり、PIDも異なる場合があります。ただし、重要なのは、文字「T」で示されているように、両方のプロセスが停止していることです。からman ps
:
PROCESS STATE CODES
(...)
T stopped by job control signal
これは、両方のプロセスがSIGTSTP信号を受信したことを意味します。 2つのプロセス(sigstop.shを含む)がシグナルを受信したときにシグナルハンドラが
suspense_cleanup()
実行されないのはなぜですか? Bashは終了するまでこれを実行しませんsleep 600
。その要求は以下によって行われます。
POSIX:
シェルがユーティリティのフォアグラウンドコマンドの実行が完了するのを待っている間にトラップセット信号が受信されると、その信号に関連するトラップはフォアグラウンドコマンドが完了するまで実行されません。
(オープンソースの世界では、IT一般標準は単なるヒントの集まりに過ぎず、誰にも従うように強制する法的要件はありません。)睡眠が少ない(3秒など)、それも役に立ちません。sleep
どうせプロセスが停止して完了しないからです。すぐに呼び出すには、suspense_cleanup()
バックグラウンドで実行し、上記のwait
POSIXリンクの指示に従う必要があります。
#!/usr/bin/env bash
suspense_cleanup () {
echo "Suspense clean up..."
}
int_cleanup () {
echo "Int clean up..."
exit 0
}
trap 'suspense_cleanup' SIGTSTP
trap 'int_cleanup' SIGINT
sleep 600 &
wait
実行して停止します。
$ ./sigstop.sh
^ZSuspense clean up...
sleep 600
今消えsigtstp.sh
た。
$ ps aux | grep -e '[s]leep 600' -e '[s]igtstp.sh'
$
sigtstp.shが消えた理由は明らかです。wait
シグナルによって中断され、スクリプトの最後の行だったので終了しました。さらに驚くべきことは、SIGINTを送信するとsigtstp.shが終了した後もスリープモードが実行され続けることです。
$ ./sigtstp.sh
^CInt clean up...
$ ps aux | grep -e '[s]leep 600' -e '[s]igtstp.sh'
ja 32354 0.0 0.0 2960 1632 pts/25 S 00:12 0:00 sleep 600
しかし、親が死んだので、initによって採用されます。
$ grep PPid /proc/32354/status
PPid: 1
その理由は、シェルがバックグラウンドで子プロセスを実行すると、デフォルトのSIGINTハンドラを無効にしてその中のプロセスを終了するためです(signal(7))](https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html):
シェルが非同期リストを実行しているときにジョブ制御が無効になっている場合(set -mの説明を参照)、リストのコマンドはシェルのSIGINTおよびSIGQUITシグナルの無視(SIG_IGN)シグナル操作を継承します。他のすべての場合、シェルによって実行されるコマンドは、トラップ特殊組み込み関数によってシグナル操作が変更されない限り、親からシェルによって継承されたのと同じシグナル操作を継承する必要があります(トラップを参照)。
いくつかのSOリファレンス: https://stackoverflow.com/questions/46061694/bash-why-cant-i-set-a-trap-for-sigint-in-a-Background-shell/46061734#46061734、 https://stackoverflow.com/questions/45106725/why-do-shells-ignore-sigint-and-sigquit-in-Backgrounded-processes。 SIGINTを受信した後にすべての子プロセスを終了するには、トラップハンドラでこれを手動で実行する必要があります。ただし、SIGINT はまだすべての子孫に渡されますが、無視されます。 sleepを使用せずに、代わりにコマンド用の独自のSIGINTハンドラをインストールした場合(例:tcpdumpを試してください)!glibc マニュアル 説明する:
与えられた信号が以前に無視されるように設定されている場合、このコードはその設定を変更しません。これは、非タスク制御シェルが子プロセスを開始するときに特定の信号を無視することが多いため、子プロセスがそれを尊重することが重要です。
しかし、私たちが自分でそれを殺さないならば、なぜスリープを殺さずにブロックする必要があるSIGTSTPを送った後にスリープが死ぬのでしょうか?孤児プロセスグループに属するすべての停止プロセスカーネルからSIGHUPをインポートする:
プロセスの終了によってプロセスグループが孤立プロセスグループになり、新しく孤立したプロセスグループのメンバーが停止した場合は、SIGHUP信号とSIGCONT信号を新しく孤立したプロセスグループの各プロセスに送信する必要があります。 。
カスタムハンドラがインストールされていない場合、SIGHUPはプロセスを終了します(signal(7)):
Signal Standard Action Comment
SIGHUP P1990 Term Hangup detected on controlling terminal
or death of controlling process
(straceでスリープを実行すると、状況がより複雑になります...)。
いいですね。元の質問に戻るのはどうですか?
私がすることができます:
いくつかのクリーンアップコードを実行するには、Ctrl-Zを押して何かをエコーしてから一時停止を再開しますか?
私がする方法は次のとおりです。
#!/usr/bin/env bash
suspense_cleanup () {
echo "Suspense clean up..."
trap - SIGTSTP
kill -TSTP $$
trap 'suspense_cleanup' SIGTSTP
}
int_cleanup () {
echo "Int clean up..."
exit 0
}
trap 'suspense_cleanup' SIGTSTP
trap 'int_cleanup' SIGINT
sleep 600 &
while true
do
if wait
then
echo child died, exiting
exit 0
fi
done
これでsuspense_cleanup()
、プロセスを停止する前に呼び出されます。
$ ./sigtstp.sh
^ZSuspense clean up...
[1]+ Stopped ./sigtstp.sh
$ ps aux | grep -e '[s]leep 600' -e '[s]igtstp.sh'
ja 4129 0.0 0.0 6920 3196 pts/25 T 00:29 0:00 bash ./sigtstp.sh
ja 4130 0.0 0.0 2960 1660 pts/25 T 00:29 0:00 sleep 600
$ fg
./sigtstp.sh
^ZSuspense clean up...
[1]+ Stopped ./sigtstp.sh
$ fg
./sigtstp.sh
^CInt clean up...
$ ps aux | grep -e '[s]leep 600' -e '[s]igtstp.sh'
ja 4130 0.0 0.0 2960 1660 pts/25 S 00:29 0:00 sleep 600
$ grep PPid /proc/4130/status
PPid: 1
10秒ほど眠り、睡眠が完了したらスクリプトが終了することを確認できます。
#!/usr/bin/env bash
suspense_cleanup () {
echo "Suspense clean up..."
trap - SIGTSTP
kill -TSTP $$
trap 'suspense_cleanup' SIGTSTP
}
int_cleanup () {
echo "Int clean up..."
exit 0
}
trap 'suspense_cleanup' SIGTSTP
trap 'int_cleanup' SIGINT
# sleep 600 &
sleep 10 &
while true
do
if wait
then
echo child died, exiting
exit 0
fi
done
走る:
$ time ./sigtstp.sh
child died, exiting
real 0m10.007s
user 0m0.003s
sys 0m0.004s
答え2
Bash ジョブ制御が通常の tty 特殊文字を妨げるようです。 SIGTSTPはBashに転送できますが、実行中のプロセスには転送できません。
~からhttps://www.gnu.org/software/bash/manual/bash.html#Job-Control-Basics:
Bashが実行されているオペレーティングシステムがジョブ制御をサポートしている場合、Bashにはそれを使用するツールが含まれています。プロセスの実行中に一時停止文字(通常は「^ Z」、Control-Z)を入力すると、プロセスは停止し、コントロールはBashに返されます。
続行(Ctrl-Q、start
から呼び出すstty
)はBashジョブ制御では機能しません。停止をキャンセルするにはプロセスがfg
必要です。bg
答え3
man 7 signal
、またはを読むと、https://www.man7.org/linux/man-pages/man7/signal.7.html
次のようになります。
SIGKILLおよびSIGSTOP信号は捕捉、ブロック、または無視できません。
だから、あなたはできません。