什么时候处理信号以及为什么某些信息会冻结?

When is a signal handled and why does some info freeze up?

  1. 打开一个名为 "termA" 的终端,然后 运行 创建的文件 callback.sh/bin/bash callback.sh

    cat  callback.sh
    #!/bin/bash
    myCallback() {
        echo "callback function called at $(date)"
    }
    trap myCallback SIGUSR1
    sleep 20 
    
  2. 打开一个名为 "termB" 和 运行:

    的新终端
    pkill -USR1  -f  callback.sh
    

termA 20 秒后显示如下内容;它从未立即显示在 termA 中:

callback function called at Mon Nov 19 08:21:52 HKT 2018

结论验证了当Bash在前台执行外部命令时,它不会处理接收到的任何信号,直到前台进程终止(参见signal trap)。

callback.sh做一点改动:

cat  callback.sh
#!/bin/bash
myCallback() {
    echo "callback function called at $(date)"
}
trap myCallback SIGUSR1
while true; do
    read -p  "please input something for foo: " foo
done

添加无限 while 循环并删除 sleep 20

  1. 打开一个名为 "termA" 的终端并 运行 使用 /bin/bash callback.sh 创建的文件 callback.sh;第一次,信息立即弹出。

    please input something for foo: 
    
  2. 打开一个名为 "termB" 和 运行 pkill -USR1 -f callback.sh 的新终端;第一次,信息会在 termA 中立即弹出。

    callback function called at Mon Nov 19 09:07:14 HKT 2018
    

问题 1: callback.sh 包含一个无限循环。这如何解释以下内容?

it does not handle any signals received until the foreground process terminates

在这种情况下,前台进程永远不会终止。

B期继续,运行pkill -USR1 -f callback.sh第二次

callback function called at Mon Nov 19 09:07:14 HKT 2018

上面的信息再次在 termA 中立即弹出。

问题 2:termA 中没有显示 please input something for foo:, 第三次去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: 冻结了?

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!"打印,但程序returns进入无限循环。只有在用 Ctrl-\ 发出 SIGQUIT 信号后,程序才真正退出。根据您的 ulimit 设置,它会转储核心或打印出收到的信号编号。

虽然循环会退出的解释是合理的,但它没有考虑信号处理的主要原因,即异步事件处理。这意味着信号处理程序的行为超出了程序控制的标准流程;事实上,整个程序都保存在一个上下文中,并且会创建一个新的上下文来仅供信号处理程序在其中执行。一旦信号处理程序完成其操作,上下文就会切换回来并开始正常的执行流程(即while(1)).

回答您的问题,

The conclusion verified that When bash is executing an external command in the foreground, it does not handle any signals received until the foreground process terminates

这里要注意的关键是external命令部分。在第一种情况下,sleep 是一个外部进程,但在第二种情况下,read 是 shell 本身的内置进程。因此,在这两种情况下,信号向这两者的传播都不同

type read
read is a shell builtin
type sleep
sleep is /usr/bin/sleep

1.Open a terminal,named termA ,and run the created file callback.sh with /bin/bash callback.sh for first time,the info pop up instantly.

是的,这种行为是预期的。因为此时只定义了函数并将陷阱处理程序注册到函数myCallback,脚本中还没有接收到信号。随着执行顺序的进行,来自 read 提示的消息第一次被抛出。

2.Open a new terminal ,named termB and run pkill -USR1 -f callback.sh first time,the info pop up instantly in termA

是的,当 read 命令等待字符串后跟 Enter 键时,它会收到一个信号 EOF 38=] 从另一个终端,当前执行上下文被保存并且控制切换到信号处理程序,该信号处理程序打印带有当前日期的字符串。

一旦处理程序完成执行,上下文就会恢复到 while 循环,其中 read 命令仍在等待输入字符串。在 read 命令成功之前,所有后续信号陷阱只会在信号处理程序中打印字符串。

Go on in termB,run pkill -USR1 -f callback.sh the second time.

与前面解释的一样,read命令在你的while循环中没有完成一次,只有当它成功读取一个字符串时,循环的下一次迭代才会开始,并且会出现新的提示信息被抛出。


图片来源:Michael KerrisK 的 Linux 编程接口

The conclusion verified that When bash is executing an external command in the foreground, it does not handle any signals received until the foreground process terminates.

bash C / 实现级别处理信号,但不会 运行 使用 [=13= 设置的处理程序] 直到前台进程终止。这是 standard:

要求的

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 之类的 "utility",它不是在单独的进程中执行的,信号是在其 "context" 中还是在主 shell 中出现并不明显],以及使用 trap 设置的处理程序是否应该在 read return 之前或之后 运行。

issue 1:callback.sh contains a infinite while loop,how to explain it does not handle any signals received until the foreground process terminates.,in this case the foreground process never terminates.

错了。 bash 没有在单独的进程中执行 while 循环。由于没有前台进程 bash 正在等待,它可以 运行 立即使用 trap 设置的任何处理程序。如果 read 是一个外部命令,那么 while 将是前台进程。

issue2: No please input something for foo: shown in termA;

go to termB, run pkill -USR1 -f callback.sh the third time.

The following info shown in termA again: callback function called at Mon Nov 19 09:07:24 HKT 2018

Still no please input something for foo: shown in termA. Why the info please input something for foo: freeze up?

它不会冻结。只需按 Enter 它就会再次出现。

就是这样

a) bash 将在被信号中断时重新启动 read 内置 就地 -- 它不会 return 和再次通过 while 循环

b) 在这种情况下,它不会重新显示用 -p 设置的提示。

请注意,在处理了来自键盘的 SIGINT 的情况下,bash 甚至不会再次显示提示,即使它确实丢弃了到目前为止从用户那里读取的字符串:

$ 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'

如果信号是从另一个window用kill -INT <pid>而不是用Ctrl-C从终端发送的,那将打印you entered 'foobar' .

最后一部分的所有内容(read 如何被打断等)都非常 bash 具体。在其他 shell 中,如 kshdash,甚至在 bash 中 运行 处于 POSIX 模式(bash --posix)时,任何处理的信号实际上都会中断 read 内置函数。在上面的示例中,shell 将打印 you entered '' 并在第一个 ^C 后退出,如果从循环中调用 read,循环将重新启动。