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


8

我正在使用Ubuntu 14.04,但遇到这种现象,我似乎无法理解:

  1. 运行yes命令(在默认外壳中: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命令?


1
那很有意思。SIGKILL应该可以运行,并且可以在我的Linux Mint 17上运行。对于其他任何信号,通常需要在以后再将其发送给SIGCONT,以确保该信号已被停止的目标接收。
PSkocik

bash是否真的为已暂停的进程打印“已停止” ?
edmz 2015年

请提供内核版本(uname -a
roaima 2015年

Linux ubuntu 3.16.0-30-generic #40~14.04.1-Ubuntu SMP Thu Jan 15 17:43:14 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux。我在Parallels Desktop中运行Ubuntu。
s1m0n

1
@black大多数贝壳说“已停止”。tcsh说“已暂停”,而zsh说“已暂停”。外观上的差异。更重要的是,bash为STOP和TSTP输出相同的消息,所有其他shell都用STOP消息标记了STOP消息,(signal)以便您可以分辨出区别。

Answers:


10

信号被阻塞以暂停进程。在终端中:

$ yes
...
y
y
^Zy

[1]+  Stopped                 yes

在第二个终端中:

$ killall yes

在第一个终端中:

$ jobs
[1]+  Stopped                 yes

$ fg
yes
Terminated

但是SIGKILL不能被阻止。killall -9 yes从第二个终端执行相同的操作会立即在yes终端中给出:

[1]+  Killed                  yes

因此,如果kill -9 %1没有立即终止进程,则可能bash是直到您执行fg该进程才真正发送信号,或者您没有发现内核中的错误。


4
一些背景细节:在终端bash中发出Ctrl + Z时,bash会向活动进程发送一个SIGTSTP(是的可阻止版本SIGSTOP)。这会使进程处于冻结状态,在该状态下内核不会调度它。这也抑制了信号处理(SIGCONT使过程解冻的信号除外),因此可以防止立即终止该过程。
mreithub

1
与其他信号不同,SIGKILL不会因挂起进程而被阻止。将KILL信号发送到挂起的进程会以异步方式将其杀死,但实际上实际上是立即终止的。
吉尔斯(Gillles)“所以-别再邪恶了”

1
@Gilles这就是我上面试图说明的:SIGTERM被阻止,但SIGKILL不是。无论如何,根据OP的评论,问题似乎是jobs没有检测到该进程已死,而不是未被该进程杀死kill -9 %1
lcd047

1
但是我可以在系统上重现s1m0n的行为(Debian,amd64,bash 4.3.30)。
吉尔(Gilles)'所以

1
虽然SIGKILL无法阻止,但不能保证它将在任何有意义的时间内交付。例如,如果某个进程被挂起等待阻塞的I / O,SIGKILL则直到该进程唤醒后才会到达。如果没有I / O发生,则可能永远不会发生。
sapi 2015年

7

不要惊慌

没有什么时髦的事情。这里没有内核错误。这是来自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外壳继续。
  2. 作为内置kill命令内部的一部分,它将作业表中的条目标记为需要在下一个可用点打印通知消息
  3. 它完成kill命令,并且在打印提示之前,再次检查以查看是否应打印有关任何作业的通知消息。
  4. yes进程还没有杀死自己的机会,因此就外壳程序而言,该作业仍处于停止状态。因此,外壳程序为该作业打印“已停止”作业状态行,并重置其通知挂起标志。
  5. yes过程被安排并杀死了自己。
  6. 内核会通知正在忙于运行其命令行编辑器的Shell进程已终止自身。Shell会记录状态更改,并将该作业标记为再次等待通知。
  7. 只需按enter一下以再次循环进行提示打印,外壳便可以打印新的作业状态。

要点是:

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

3
这是一个很有意思的理论,但是OP可以输入jobs并且Shell仍然认为该过程仍然有效。那将是一个异常长的调度竞赛条件。:)
lcd047

3
首先,感谢您的详尽回答!我当然是有道理的,并清除了很多事情。.但是如上所述,我可以在运行乘法jobs命令之后,kill这些命令仍然表明进程刚刚停止。但是,您激发了我继续尝试的灵感,我发现了这一点:在[1]+ Terminated yes我运行另一个外部命令(不是内置的shell echojobs)时,就会立即打印该消息。因此,我可以jobs根据自己的喜好运行,并且它会不断打印[1]+ Stopped yes。但是ls例如我跑步时,Bash就会打印[1]+ Terminated yes
s1m0n

lcd047没有阅读您对问题的评论;这很重要,应该适当地将其编辑到问题的开头。重载主机操作系统很容易,以至于来宾似乎在内部进行非常奇怪的调度。就像这样,还有更多。(我曾经设法通过失控的Bing Desktop占用了大量主机CPU时间来造成非常奇怪的调度。)
JdeBP 2015年

1
@Gilles问题似乎是jobs没有注意到进程实际上已经死了……虽然不确定如何通过运行另一个命令来更新状态。
lcd047

1
甚至Gilles也没有看到评论。这就是为什么您应该在问题中添加此类重要内容,而不是将其隐藏在注释中的原因。吉尔斯,答案显然在谈到延迟交付的信号,在不延误发送它。你把它们弄混了。另外,请阅读提问者的评论(实际上是此处给出的要点),并查看您所做的非常重要的错误基本假设。虚拟处理器不一定必须同步运行,也不能始终保持全速运行。
JdeBP 2015年

2

在您的系统上可能会发生一些时髦的事情,在我的情况下,无论是否使用-9

> yes
...
^Z
[1]+  Stopped                 yes
> jobs
[1]+  Stopped                 yes
> kill %1
[1]+  Killed                  yes
> jobs
> 

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


请问您使用的是哪个发行版/内核/ bash版本?也许您的bash的内部kill命令会花费更多的精力并检查作业是否被冻结(您可能希望尝试找出作业的PID并使用杀死它env kill <pid>。那样,您将使用实际的kill命令,而不是内置的bash。
mreithub

在opensuse 13.2上使用bash-4.2-75.3.1.x86_64 杀死cmd并非内部命令:which kill /usr/bin/kill
Dan Cornilescu

1
which不是bash-builtin,因此which <anything>将始终为您提供实际命令的路径。但尝试比较kill --help对比/usr/bin/kill --help
mreithub

嗯对 确实,这是内置的kill
Dan Cornilescu 2015年

2

您正在观察的是此版本bash中的错误。

kill -9 %1会立即终止工作。您可以通过观察ps。您可以跟踪bash进程以查看何时kill调用系统调用,也可以跟踪子进程以查看何时接收和处理信号。更有趣的是,您可以查看该过程正在发生什么。

bash-4.3$ sleep 9999
^Z
[1]+  Stopped                 sleep 9999
bash-4.3$ kill -9 %1

[1]+  Stopped                 sleep 9999
bash-4.3$ jobs
[1]+  Stopped                 sleep 9999
bash-4.3$ jobs -l
[1]+  3083 Stopped                 sleep 9999
bash-4.3$ 

在另一个终端:

% ps 3083
  PID TTY      STAT   TIME COMMAND
 3083 pts/4    Z      0:00 [sleep] <defunct>

子进程是僵尸。它已经死了:它剩下的只是进程表中的一个条目(但没有内存,代码,打开的文件等)。该条目将保留下来,直到其父级注意到并通过调用wait系统调用或其同级之一来检索其退出状态。

交互式外壳程序应检查是否有死掉的孩子并在打印提示之前对其进行收获(除非另有配置)。在某些情况下,此版本的bash无法做到:

bash-4.3$ jobs -l
[1]+  3083 Stopped                 sleep 9999
bash-4.3$ true
bash-4.3$ /bin/true
[1]+  Killed                  sleep 9999

您可能希望bash在命令后打印提示后立即报告“杀死” kill,但这不能保证,因为存在竞争条件。信号是异步传递的:kill内核确定要向哪个进程传递信号后,系统调用将立即返回,而无需等待信号实际传递。在实践中,bash有可能并且确实发生了,bash有时间检查其子进程的状态,发现它仍然没有死(wait4没有报告任何孩子死亡),并打印该进程仍在停止。出问题的是,在下一个提示之前,信号已传递(ps报告该进程已死),但bash仍未调用wait4(我们可以看到,不仅因为它仍将作业报告为“已停止”,而且还因为进程表中仍然存在僵尸)。实际上,当bash wait4运行其他外部命令时,它仅在下次需要调用时才收获僵尸。

该错误是间歇性的,在跟踪bash时我无法重现该错误(大概是因为这是bash需要快速反应的竞争条件)。如果信号在bash检查之前发出,则一切都会按预期进行。

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.