为什么 SIGKILL 不终止已停止的程序(是)?

s1m*_*m0n 8 shell job-control

我正在使用Ubuntu 14.04,但我遇到了我似乎无法理解的行为:

  1. 运行yes命令(在默认 shell 中:Bash
  2. 键入CtrlZ停止yes
  3. 运行jobs。输出:
    [1]+ Stopped yes
  4. 跑到kill -9 %1yes。输出:
    [1]+ Stopped yes
  5. 运行jobs。输出:
    [1]+ Stopped yes

这是在3.16.0-30-generic并行虚拟机中运行的Ubuntu上。

为什么我的kill -9命令没有终止yes命令?我认为SIGKILL不能被捕获或忽略?以及如何终止yes命令?

lcd*_*047 10

信号被挂起的进程阻塞。在终端中:

$ yes
...
y
y
^Zy

[1]+  Stopped                 yes
Run Code Online (Sandbox Code Playgroud)

在第二个终端中:

$ killall yes
Run Code Online (Sandbox Code Playgroud)

在第一个终端中:

$ jobs
[1]+  Stopped                 yes

$ fg
yes
Terminated
Run Code Online (Sandbox Code Playgroud)

但是SIGKILL不能被屏蔽。killall -9 yes从第二个终端做同样的事情会立即在yes终端中给出:

[1]+  Killed                  yes
Run Code Online (Sandbox Code Playgroud)

因此,如果kill -9 %1不立即终止进程,那么要么在进程运行bash之前实际上不会发送信号fg,要么在内核中发现了错误。

  • 一些背景细节:在终端中发出 Ctrl+Z 时,bash 会向活动进程发送一个 `SIGTSTP`(它是 `SIGSTOP` 的可阻止版本)。这使进程处于内核不会调度它的冻结状态。这也会抑制信号处理(除了解冻进程的“SIGCONT”信号),从而防止进程立即被杀死。 (5认同)

Jde*_*eBP 8

不要惊慌。

没有什么时髦的事情发生。这里没有内核错误。这是 Bourne Again shell 和多任务操作系统的完全正常行为。

要记住的是,一个进程会杀死自己,即使是响应SIGKILL. 这里发生的事情是,Bourne Again shell它刚刚告诉杀死自己的进程开始杀死自己之前就开始处理事情。

考虑一下从yes停止点开始发生的事情,SIGTSTP并且您刚刚kill使用 Bourne Again shell执行了命令:

  1. 外壳发送SIGKILLyes进程。
  2. 同时
    1. yes进程计划运行并立即自行终止。
    2. Bourne Again shell 继续运行,发出另一个提示。

您看到一件事而其他人看到另一件事的原因是两个准备运行的进程之间的简单竞赛,获胜者完全取决于机器与机器之间以及随着时间的推移而变化的事物。系统负载会产生影响,就像您的 CPU 是虚拟的一样。

在有趣的情况下,步骤#2 的细节是这样的:

  1. Bourne Again shell 继续。
  2. 作为内置kill命令内部的一部分,它将作业表中的条目标记为需要在下一个可用点打印通知消息
  3. 它完成kill命令,并在打印提示之前再次检查它是否应该打印有关任何作业的通知消息。
  4. yes进程还没有机会杀死自己,因此就 shell 而言,该作业仍处于停止状态。因此外壳会为该作业打印“已停止”作业状态行,并重置其通知挂起标志。
  5. yes过程被安排并自行终止。
  6. 内核通知正在忙于运行其命令行编辑器的 shell,该进程已自行终止。Shell 会注意到状态的变化并将作业标记为再次挂起的通知。
  7. 只需按下enter以再次循环提示打印,外壳就有机会打印新的作业状态。

要点是:

  • 进程会杀死自己。 SIGKILL并不神奇。当从内核模式返回到应用程序模式时,进程会检查挂起的信号,这发生在页面错误、(非嵌套)中断和系统调用结束时。唯一特别的是,内核不允许响应的动作SIGKILL是立即和无条件的自杀之外的任何动作,并且不会返回到应用程序模式。重要的是,进程既需要进行内核到应用程序模式的转换,需要被调度运行以响应信号。
  • 虚拟 CPU 只是主机操作系统上的一个线程。无法保证主机已安排虚拟 CPU 运行。主机操作系统也不是神奇的。
  • 当作业状态发生更改时,不会打印通知消息(除非您使用set -o notify)。当 shell 下一次到达其执行周期中的某个点时,将打印它们,以查看是否有任何通知未决。
  • 通知挂起标志被设定两次,一次通过kill,一旦由SIGCHLD信号处理程序。这意味着如果 shell在重新调度以杀死自身的进程之前运行,则可以看到两条消息yes;一个是“Stopped”消息,一个是“Killed”消息。
  • 显然,该/bin/kill程序无法访问 shell 的内部作业表;所以你不会看到这样的行为/bin/kill。通知挂起标志仅由SIGCHLD处理程序设置一次。
  • 出于同样的原因,你不会看到这种行为,如果你killyes过程从另一个外壳。

  • 这是一个有趣的理论,但是 OP 可以输入“jobs”,而 shell 仍然认为该进程是活动的。那将是一种异常长的调度竞争条件。:) (3认同)
  • 首先,感谢您的详尽解答!我当然有道理并清除了很多东西..但是如上所述,我可以在 `kill` 之后运行多个 `jobs` 命令,这些命令仍然表明进程刚刚停止。然而,你激励我继续尝试,我发现了这一点:只要我运行另一个外部命令(不是像 `echo` 或 `jobs` 这样的内置 shell),就会打印消息 `[1]+ Terminated yes`。所以我可以随心所欲地运行`jobs`,它会不断打印`[1]+ Stopped yes`。但是,例如,只要我运行 `ls`,Bash 就会打印 `[1]+ Terminated yes` (3认同)

Dan*_*scu 2

你的系统上可能会发生一些奇怪的事情,在我的系统上,无论有没有:你的食谱都可以很好地工作-9

> yes
...
^Z
[1]+  Stopped                 yes
> jobs
[1]+  Stopped                 yes
> kill %1
[1]+  Killed                  yes
> jobs
> 
Run Code Online (Sandbox Code Playgroud)

获取 pid 并jobs -p尝试将其杀死root