Bash 管道信号传播 - 它是如何工作的?

Att*_*tie 5 bash

在回答这个问题时,我无法完全解释信号如何通过管道传播。

考虑以下示例。

使用timeout作为管道的第一个元素

这会导致gpg救助已捕获的SIGTERM交付给cat,由timeout,留下损坏的文件。

$ timeout 1 cat /dev/urandom | gpg -er attie@attie.co.uk > ./myfile.gpg

gpg: Terminated caught ... exiting
Terminated
$ gpg -d < ./myfile.gpg > /dev/null

You need a passphrase to unlock the secret key for
user: "Attie Grande <attie@attie.co.uk>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)

gpg: encrypted with 4096-bit RSA key, ID C9AEA6AE, created 2016-12-13
      "Attie Grande <attie@attie.co.uk>"
gpg: block_filter 0x145e790: read error (size=14775,a->size=14775)
gpg: block_filter 0x145f110: read error (size=10710,a->size=10710)
gpg: WARNING: encrypted message has been manipulated!
gpg: block_filter: pending bytes!
gpg: block_filter: pending bytes!
Run Code Online (Sandbox Code Playgroud)

timeout在管道中间使用

这按预期工作 -gpg干净地退出。

$ cat /dev/urandom | timeout 1 cat | gpg -er attie@attie.co.uk > ./myfile.gpg
$ gpg -qd < ./myfile.gpg > /dev/null

You need a passphrase to unlock the secret key for
user: "Attie Grande <attie@attie.co.uk>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)
Run Code Online (Sandbox Code Playgroud)

使用SIGUSR1代替SIGTERM

同样,这按预期工作 -gpg干净地退出。我期望因为cat退出SIGUSR1,而gpg忽略它。

$ timeout -sUSR1 1 cat /dev/urandom | gpg -er attie@attie.co.uk > ./myfile.gpg
$ gpg -qd < ./myfile.gpg > /dev/null

You need a passphrase to unlock the secret key for
user: "Attie Grande <attie@attie.co.uk>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)
Run Code Online (Sandbox Code Playgroud)

使用进程替换

再次,这有效 - 尽管我没想到它会如此。

$ gpg -er attie@attie.co.uk > ./myfile.gpg < <( timeout 1 cat /dev/urandom )
$ gpg -qd < ./myfile.gpg > /dev/null

You need a passphrase to unlock the secret key for
user: "Attie Grande <attie@attie.co.uk>"
4096-bit RSA key, ID C9AEA6AE, created 2016-12-13 (main key ID 7826F053)
Run Code Online (Sandbox Code Playgroud)

我只能假设管道中第一个元素的信号传播到管道中的其余元素(甚至将它们与timeout cat | cat | gpg失败分开)。

我查看了文档,并尝试了使用set -eset -o pipefail但它们并没有像我预期的那样行事。

  • 究竟发生了什么?
  • 什么是语义?
  • 我们对此有任何控制吗?
  • 有没有比从管道前端移动信号生成过程更好的方法?

Kam*_*ski 9

我只能假设管道中第一个元素的信号传播到管道中的其余元素。

据我所知,没有这种传播。我主要回答你的第一个问题:

究竟发生了什么?

简答

(这可能有点简化。)

  1. 运行管道时,interactivebash将每个进程放在一个进程组中,PGID(进程组 ID)等于PID第一个命令的(进程 ID)。
  2. timeout将自己更改为自己PGIDPID。如果timeout是管道中的第一个命令,则不会改变任何内容。
  3. timeout不仅将信号发送到底层命令,而且还发送到其整个进程组。如果timeout是管道中的第一个命令,则其进程组仍将包含gpg,因此gpg将获得信号。

下面对该现象进行研究和阐述。


细化

1.bash行为

当运行一个管,交互式bash每一个处理组与过程的地方PGID等于所述PID第一命令的。您可以进行自己的测试(请参阅是否可以从中获取进程组 ID/proc?)。我还没有研究过更复杂的可能性(例如,如果第一个“命令”是一个子外壳呢?),在你的情况下,它们并不重要。重要的是gpg在这些命令中

timeout 1 cat /dev/urandom | gpg -er attie@attie.co.uk > ./myfile.gpg
cat /dev/urandom | timeout 1 cat | gpg -er attie@attie.co.uk > ./myfile.gpg
timeout -sUSR1 1 cat /dev/urandom | gpg -er attie@attie.co.uk > ./myfile.gpg
gpg -er attie@attie.co.uk > ./myfile.gpg < <( timeout 1 cat /dev/urandom )
Run Code Online (Sandbox Code Playgroud)

获得PGID等于PID

  • timeout
  • (首先) cat
  • timeout
  • gpg (即本身)

分别。

2.timeout改变自己PGID(或不改变)

运行strace timeout 1 cat,您将看到以下内容:

setpgid(0, 0)
Run Code Online (Sandbox Code Playgroud)

摘录自man 2 setpgid

int setpgid(pid_t pid, pid_t pgid);
Run Code Online (Sandbox Code Playgroud)

setpgid()PGID指定的进程的设置pidpgid。如果pid为零,则使用调用进程的进程 ID。如果pgid为零,PGID则由 指定的 pid 进程的进程 ID 与其进程 ID 相同。

这意味着timeout设置它PGID等于它的PID。有两种可能:

  • 如果timeout是第一个命令,其PGID是之前和之后相同setpgid,所以gpg仍然具有相同的PGID作为timeout;
  • 如果timeout不是第一个命令,它会PGID被更改,即使gpg最初相同PGID,现在timeout两个PGIDs 也不同。

3.timeout发出比你预期更多的信号

同样strace timeout 1 cat揭示了以下几行:

kill(19401, SIGTERM)
…
kill(0, SIGTERM)
Run Code Online (Sandbox Code Playgroud)

在这个例子中19401PIDcat。如果你使用,-s USR1那么将会有SIGUSR1而不是SIGTERM等。这第二个kill负责你认为是通过管道的信号传播。见man 2 kill(摘录):

int kill(pid_t pid, int sig);
Run Code Online (Sandbox Code Playgroud)

如果pid等于0,则sig发送到调用进程的进程组中的每个进程。

调用过程是timeout。它向整个进程组发送信号。我承认我不知道这背后的目的是什么,但它仍然如此。

因此,如果timeout是管道中的第一个命令,则所选信号将被发送到它的每个部分(好吧,几乎;考虑timeout同一管道中的另一个)。这包括gpg. 然后取决于gpg它对信号的反应。


其他问题

我们对此有任何控制吗?有没有比从管道前端移动信号生成过程更好的方法?

我的快速搜索没有找到设置/更改的通用工具PGID。我认为您可以编写自己的程序来调用setpgid(2)左右;但是现在,当我们知道发生了什么时,timeout从管道前端移动似乎是一种非常明智的方法。

另请注意,这是因为timeout行为方式。其他信号生成过程可能不需要这种变通方法。