다음 스크립트가 있습니다.
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_(유닉스), 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 신호를 수신했음을 의미합니다. 이제 두 프로세스(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 매뉴얼 설명하다:
주어진 신호가 이전에 무시하도록 설정된 경우 이 코드는 해당 설정을 변경하지 않습니다. 이는 비작업 제어 쉘이 하위 프로세스를 시작할 때 특정 신호를 무시하는 경우가 많기 때문에 하위 프로세스가 이를 존중하는 것이 중요합니다.
하지만 우리가 스스로 그것을 죽이지 않는다면 왜 sleep을 죽이지 않고 차단만 해야 하는 SIGTSTP를 보낸 후에 sleep이 죽는 걸까요? 고아 프로세스 그룹에 속하는 모든 중지된 프로세스커널에서 SIGHUP 가져오기:
프로세스 종료로 인해 프로세스 그룹이 고아 프로세스 그룹이 되고 새로 고아 프로세스 그룹의 구성원이 중지되면 SIGHUP 신호와 SIGCONT 신호를 새로 고아 프로세스 그룹의 각 프로세스로 보내야 합니다. .
사용자 정의 핸들러가 설치되지 않은 경우 SIGHUP은 프로세스를 종료합니다(signal(7)):
Signal Standard Action Comment
SIGHUP P1990 Term Hangup detected on controlling terminal
or death of controlling process
(strace에서 sleep을 실행하면 상황이 더 복잡해집니다...).
좋습니다. 원래 질문으로 돌아가는 것은 어떨까요?
내가 어떻게 할 수있는:
일부 정리 코드를 실행하려면 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 신호는 포착, 차단 또는 무시될 수 없습니다.
그래서, 당신은 할 수 없습니다.