dic*_*tto 6 shell bash signals trap background-process
请解释一下:
#!/bin/bash
# This is scripta.sh
./scriptb.sh &
pid=$!
echo $pid started
sleep 3
while true
do
kill -SIGINT $pid
echo scripta.sh $$
sleep 3
done
Run Code Online (Sandbox Code Playgroud)
——
#!/bin/bash
# This is scriptb.sh
trap "echo Ouch;" SIGINT
while true
do
echo scriptb.sh $$
sleep 1
done
Run Code Online (Sandbox Code Playgroud)
当我运行./scripta.sh陷阱不打印。如果我从 SIGINT 切换到任何其他信号(我尝试了 SIGTERM、SIGUSR1),陷阱会按预期打印“哎哟”。这怎么会发生?
G-M*_*ca' 11
如果我从 SIGINT 切换到任何其他信号(我尝试了 SIGTERM、SIGUSR1),陷阱会按预期打印“Ouch”。
显然你没有尝试 SIGQUIT;您可能会发现它的行为与 SIGINT 相同。
问题是作业控制。
在 Unix 的早期,每当 shell 将进程或管道置于后台时,它会将这些进程设置为忽略 SIGINT 和 SIGQUIT,因此如果用户随后键入Ctrl+ C(中断)或Ctrl+ \(退出),它们不会被终止一个前台任务。当作业控制出现时,它带来了进程组,所以现在shell需要做的就是把后台作业放到一个新的进程组中;只要它不是当前的终端进程组,进程就不会看到来自键盘的信号(Ctrl+ C、Ctrl+\ 和Ctrl+ Z(SIGTSTP))。shell 可以使用默认信号处理离开后台进程。事实上,它可能必须,因为进程可以被杀死Ctrl+C当它们被带到前台时。
但是非交互式 shell 不使用作业控制。出于历史原因,非交互式 shell 会退回到忽略后台进程的 SIGINT 和 SIGQUIT 的旧行为是有道理的——即使向他们发送了键盘类型的信号,也允许后台进程继续运行. 并且 shell 脚本在非交互式 shell 中运行。
而且,如果您查看bash(1) 中trap命令下的最后一段,您会看到
进入外壳时被忽略的信号不能被捕获或重置。
因此,如果您./scriptb.sh?&
从交互式shell 的命令提示符运行,它的信号处置将被保留(即使它被置于后台),并且该trap命令按预期工作。但是,如果您运行./scripta.sh(使用或不使用&),它会在非交互式 shell 中运行脚本。当该非交互式 shell 运行时./scriptb.sh?&,它会将scriptb进程设置为忽略中断并退出。因此,trap命令 inscriptb.sh静默失败。
通过一些追踪:
strace -o aaa ./scripta
Run Code Online (Sandbox Code Playgroud)
我们可以观察到默认情况下
read(255, "#!/bin/bash\n# this is scripta.sh"..., 153) = 153
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT CHLD], [], 8) = 0
lseek(255, -108, SEEK_CUR) = 45
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7f1ee9b7ca10) = 2483
Run Code Online (Sandbox Code Playgroud)
scripta阻塞INT和CHLD信号也是如此;scriptb通过fork(此处称为clone)继承这些设置。以及正在scriptb做什么?如果我们从 via 运行它scripta:
strace -o bbb ./scriptb &
Run Code Online (Sandbox Code Playgroud)
然后寻找信号相关的东西,我们发现:
rt_sigprocmask(SIG_BLOCK, NULL, [], 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7fb1b2e75250}, {SIG_IGN, [], 0}, 8) = 0
rt_sigaction(SIGINT, {SIG_IGN, [], SA_RESTORER, 0x7fb1b2e75250}, {SIG_DFL, [], SA_RESTORER, 0x7fb1b2e75250}, 8) = 0
Run Code Online (Sandbox Code Playgroud)
这表明没有任何东西被阻止,然后该INT信号首先被给予默认处理,然后被忽略。scriptb直接从shell下运行strace对比显示:
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT], [], 8) = 0
rt_sigaction(SIGINT, {0x45fbf0, [], SA_RESTORER, 0x7f2c3f6d4250}, {SIG_DFL, [], SA_RESTORER, 0x7f2c3f6d4250}, 8) = 0
rt_sigprocmask(SIG_BLOCK, [INT CHLD], [], 8) = 0
rt_sigprocmask(SIG_BLOCK, ~[RTMIN RT_1], [INT CHLD], 8) = 0
...
Run Code Online (Sandbox Code Playgroud)
或者在进入然后重复的睡眠呼叫处理之前从未被忽略。好的。呃。让我们在scripta和之间放置一个垫片scriptb,重置SIGINT为默认状态......
#include <getopt.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int ch;
while ((ch = getopt(argc, argv, "h?")) != -1) {
switch (ch) {
case 'h':
case '?':
default:
abort();
}
}
argc -= optind;
argv += optind;
if (argc < 1) abort();
signal(SIGINT, SIG_DFL);
execvp(*argv, argv);
abort();
return 1;
}
Run Code Online (Sandbox Code Playgroud)
使用方法如下:
$ make defaultsig
cc defaultsig.c -o defaultsig
$ grep defaultsig scripta.sh
./defaultsig ./scriptb.sh &
$ ./scripta.sh
12510 started
scriptb.sh 12510
scriptb.sh 12510
scriptb.sh 12510
scripta.sh 12509
Ouch
scriptb.sh 12510
scriptb.sh 12510
scriptb.sh 12510
scripta.sh 12509
Ouch
...
Run Code Online (Sandbox Code Playgroud)
是的,现在效果很好。但是,我不知道为什么bash会这样,也许向他们提交错误?中的代码sig.c看起来非常复杂,并且其他地方有更多的信号处理......
| 归档时间: |
|
| 查看次数: |
3672 次 |
| 最近记录: |