我在用着乌班图14.04我正在经历这种我似乎无法理解的行为:
- 运行
yes
命令(在默认 shell 中:重击) - 键入CtrlZ以停止
yes
- 跑步
jobs
。输出:
[1]+ Stopped yes
- 跑到
kill -9 %1
停下来yes
。输出:
[1]+ Stopped yes
- 跑步
jobs
。输出:
[1]+ Stopped yes
这是3.16.0-30-generic
在并行虚拟机中运行的 Ubuntu 上。
kill -9
为什么我的命令没有终止是的命令?我想信号杀死不能被抓住或忽视?我怎样才能终止是的命令?
答案1
暂停进程的信号被阻止。在终端中:
$ 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
,要么您发现了内核中的错误。
答案2
不要恐慌。
没有什么奇怪的事情发生。这里没有内核错误。这是 Bourne Again shell 和多任务操作系统的完全正常行为。
要记住的是进程自杀,甚至回应SIGKILL
。这里发生的事情是 Bourne Again shell 正在处理一些事情前它刚刚告诉自己杀死自己的过程实际上也杀死了自己。
yes
考虑一下从停止点开始发生的情况SIGTSTP
,并且您刚刚kill
使用 Bourne Again shell 执行了该命令:
- shell 发送
SIGKILL
给yes
进程。 - 在平行下:
- 该
yes
进程计划运行并立即终止。 - Bourne Again shell 继续运行,发出另一个提示。
- 该
您看到一件事而其他人看到另一件事的原因是两个准备运行的进程之间的简单竞赛,其中的获胜者完全取决于机器之间以及随着时间的推移而变化的事物。系统负载会产生影响,CPU 是虚拟的这一事实也是如此。
在有趣的情况下,步骤 #2 的细节是这样的:
- Bourne Again shell 仍在继续。
- 作为内置
kill
命令内部的一部分,它将作业表中的条目标记为需要打印通知消息在下一个可用点。 - 它完成
kill
命令,并在打印提示之前再次检查是否应该打印有关任何作业的通知消息。 - 该
yes
进程还没有机会杀死自己,因此就 shell 而言,该作业仍处于停止状态。因此,shell 会打印该作业的“已停止”作业状态行,并重置其通知挂起标志。 - 该
yes
进程被安排并自行终止。 - 内核通知正在忙于运行其命令行编辑器的 shell,该进程已自行终止。 shell 记录状态变化并将作业再次标记为通知挂起。
- 只需按下enter再次循环提示打印即可使 shell 有机会打印新的作业状态。
重点是:
- 进程会自我终止。
SIGKILL
这并不神奇。进程在从内核模式返回到应用程序模式时会检查待处理信号,这种情况发生在页面错误、(非嵌套)中断和系统调用结束时。唯一特别的是,内核不允许对 的响应操作是SIGKILL
立即和无条件的自杀以外的任何操作,并且不会返回到应用程序模式。重要的是,进程需要进行从内核到应用程序模式的转换和被安排运行以响应信号。 - 虚拟CPU只是主机操作系统上的一个线程。无法保证主机已安排虚拟 CPU 运行。主机操作系统也不神奇。
- 当作业状态发生更改时,不会打印通知消息(除非您使用
set -o notify
)。当 shell 到达其执行周期中的下一个点时,将打印它们,检查是否有任何通知待处理。 - 通知挂起标志被设置两次,
kill
一次由SIGCHLD
信号处理程序设置。这意味着人们可以看到二如果 shell 在yes
进程被重新调度以终止自身之前运行,则会出现消息;一条是“已停止”消息,一条是“已终止”消息。 - 显然,该
/bin/kill
程序无法访问 shell 的内部作业表;所以你不会看到这样的行为/bin/kill
。通知挂起标志仅由处理程序设置一次SIGCHLD
。 - 出于同样的原因,如果您从另一个 shell 进行进程,
kill
则不会看到此行为。yes
答案3
您观察到的是此版本的 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
系统调用或其兄弟之一。
交互式 shell 应该检查死亡的子进程并在打印提示之前捕获它们(除非另有配置)。此版本的 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 在命令后打印提示符后立即报告“Killed” kill
,但这并不能保证,因为存在竞争条件。信号是异步传递的:kill
一旦内核确定要向哪个进程传递信号,系统调用就会立即返回,而无需等待信号实际传递。有可能,而且在实践中确实发生了,bash 有时间检查其子进程的状态,发现它仍然没有死亡(wait4
不报告任何子进程死亡),并打印该进程仍然停止。错误的是,在下一个提示之前,信号已被传递(ps
报告进程已死亡),但 bash 仍然没有调用wait4
(我们可以看到,这不仅是因为它仍然报告作业为“已停止”,而且因为僵尸仍然存在于进程表中)。事实上,bash 仅在下次需要调用wait4
、运行其他外部命令时才会获取僵尸。
该错误是间歇性的,在跟踪 bash 时我无法重现它(大概是因为这是 bash 需要快速反应的竞争条件)。如果信号在 bash 检查之前传递,则一切都会按预期发生。
答案4
你的系统上可能会发生一些奇怪的事情,在我的系统上,无论有没有-9
:你的食谱都可以很好地工作:
> yes
...
^Z
[1]+ Stopped yes
> jobs
[1]+ Stopped yes
> kill %1
[1]+ Killed yes
> jobs
>
获取 pidjobs -p
并尝试将其杀死root
。