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


5

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

请考虑以下示例。

使用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!

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)

使用SIGUSR1而不是SIGTERM

再次,这可以按预期工作 - gpg干净利落地退出。我希望因为cat退出SIGUSR1gpg忽略它。

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

使用流程替换

再次,这是有效的 - 虽然我没想到它。

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

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

我已经看过文档,并且玩过set -eset -o pipefail但是他们并没有像我期待的那样行事。

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

Answers:


8

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

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

究竟发生了什么?

简短的回答

(这可能会有所简化。)

  1. 运行管道时,交互式bash将进程组中的每个进程置于PGID(进程组ID)等于PID第一个命令的(进程ID)。
  2. timeout改变自己PGID的自己PID。如果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 )

获得PGID等于PID

  • timeout
  • (首先) cat
  • timeout
  • gpg (即自己)

分别。

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

strace timeout 1 cat,你会看到其他的东西:

setpgid(0, 0)

摘录自man 2 setpgid

int setpgid(pid_t pid, pid_t pgid);

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

这意味着timeoutPGID等于它PID。有两种可能性:

  • 如果timeout是第一个命令,它PGID的前后是相同的setpgid,所以gpg仍然具有相同PGIDtimeout;
  • 如果timeout不是第一个命令,它的PGID改变,并且即使gpg最初曾同PGIDtimeout两个PGID小号,现在是不同的。

3. timeout发送超出预期的信号

同样strace timeout 1 cat显示如下行:

kill(19401, SIGTERM)

kill(0, SIGTERM)

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

int kill(pid_t pid, int sig);

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

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

因此,如果timeout是管道中的第一个命令,那么所选择的信号将被发送到它的每个部分(好吧,差不多; timeout在同一个管道中考虑另一个)。这包括gpg。然后由它gpg如何对信号作出反应。


其他问题

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

我的快速搜索没有提供设置/更改的常用工具PGID。我想你可以自己写一个会打电话的程序setpgid(2); 但现在,当我们知道发生了什么时,timeout从管道前面移动似乎是一种非常理智的方法。

还要注意这是因为timeout行为方式。其他信号生成过程可能不需要这样的解决方法。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.