shell 脚本中的并发信号处理

问题描述 投票:0回答:1

如果 shell 在忙于前一个信号的同时接收到新信号,那么 shell 可能会同时执行信号处理程序(陷阱操作)。这是一个示例脚本

handler() {
     echo -n '<'
     for i in a b c d e; do echo -n $i; sleep 1; done
     echo -n '>'
}
trap handler USR1
...

产生以下输出

<ab<abcd<abcde>e>cde>
^  ^    ^
|  |    |
signal received

此行为会破坏复杂的处理程序。

我通过实验发现,可以通过在子shell中执行来实现原子性:

trap '(handler)' USR1

<abcde><abcde>
^  ^  ^
|  |  |
signal received

并且

foo=$(handler)
也是原子的(从某种意义上说,它不会被信号中断),使处理程序能够更改 shell 的状态。

但是,整个问题似乎是 Linux 特定的:Mac OS 上的 bash 根本不会中断处理程序。这使得我的解决方案依赖于未记录的行为来克服未记录的特定于平台的行为。这听起来不太可靠。

我找不到有关该主题的任何知识。有没有标准/更好的方法来处理它?

shell unix concurrency signals sh
1个回答
0
投票

对于 shell 程序来说,这确实是一个棘手的问题! “Shell 命令语言”的 POSIX-2008 标准第 2.12 节“Shell 执行环境”指定了以下内容(强调我的):

应将子 shell 环境创建为 shell 环境的副本,除非未忽略的信号陷阱应设置为默认操作。对子 shell 环境所做的更改不应影响 shell 环境。命令替换、用括号分组的命令以及异步列表应在子 shell 环境中执行。

换句话说,除了进程间通信(例如通过命名管道)之外的所有内容在 shell 执行环境之间都是分开的。这包括陷阱处理程序本身,它们在进入子 shell 时自动重置为默认状态(除非陷阱处理程序被设置为忽略状态)。

在您的问题提供的示例输出中,请注意,当您发送三个信号(根据您的图表)时,陷阱处理程序仅被调用两次。这是预期的行为,因为其中一个信号是在第一个陷阱处理程序仍在运行时发送的,因此该信号被传递到安装在第一个陷阱处理程序运行的子 shell 环境中的陷阱处理程序。除非陷阱处理程序本身进行更改,这应该设置为默认状态,其中默认状态可能是您在示例中使用的特定信号的忽略状态。

您的两个解决方案(使用括号和命令替换对命令进行分组)都会创建一个子 shell 环境,该环境会在运行任何命令之前重置陷阱处理程序,以确保在发送另一个信号时不会再次调用它。您注意到的区别在于,使用命令替换允许陷阱处理程序影响父 shell 执行环境的状态,而简单地对命令进行分组并不能提供实现此目的的便捷方法。

但是,整个问题似乎是 Linux 特定的:Mac OS 上的 bash 根本不会中断处理程序。这使得我的解决方案依赖于未记录的行为来克服未记录的特定于平台的行为。这听起来不太可靠。

正如所建立的,您提出的解决方案已由 POSIX 完全指定。可能未指定的是当陷阱处理程序已经运行时是否调用陷阱处理程序,并且根据您的测试 Mac OS 和 Linux 有所不同。我发现的标准中没有任何内容表明不应像 C 中那样再次调用陷阱处理程序,这表明 Linux 的行为是可以预料的。另一方面,标准中似乎没有任何内容可以阻止 shell 在陷阱处理程序运行时阻止信号,如果可以依赖的话,这可能是一个有用的约定。

另一种方法是简单地使用内置的

trap
将陷阱处理程序重置为陷阱处理程序本身的默认状态,如下所示:

handler() {
    trap - USR1
    # run handler
    trap handler USR1
}

trap handler USR1
# run program

这可能更方便,因为陷阱处理程序所需的所有状态更改不必通过单个变量传递到父 shell 执行环境。相反,完整的父 shell 执行环境可能会按照正常情况进行更改。

© www.soinside.com 2019 - 2024. All rights reserved.