什么是不可中断的过程?

有时候,当我用 Linux 编写程序时,它会因为某种 bug 而崩溃,这个程序就会变成一个不可中断的进程,并且会一直运行下去,直到我重新启动计算机(即使我退出了)。我的问题是:

  • 是什么使一个过程变得不可中断?
  • 我要怎么阻止这一切发生?
  • 这可能是一个愚蠢的问题,但是有没有办法在不重新启动我的电脑的情况下打断它呢?
109292 次浏览

如果您讨论的是一个“僵尸”进程(在 ps 输出中被指定为“僵尸”) ,那么这是进程列表中的一个无害记录,等待某人收集其返回代码,因此可以安全地忽略它。

你能描述一下什么和“不可中断的过程”适合你吗?它是否在“杀死 -9”中幸存下来,并快乐地一路前行?如果是这样的话,那么它就会卡在某个系统调用上,而这个系统调用又会卡在某个驱动程序上,你就会一直处于这个过程中,直到重新启动(有时候最好尽快重新启动)或者卸载相关的驱动程序(这种情况不太可能发生)。您可以尝试使用“ strace”来找出您的流程被卡住的地方,并在将来避免它。

不可中断进程是一个正好位于系统调用(内核函数)中且不能被信号中断的进程。

要理解这意味着什么,您需要理解可中断系统调用的概念。经典的例子是 read()。这是一个系统调用,可能需要很长的时间(秒) ,因为它可能涉及旋转硬盘驱动器,或移动头部。在此期间的大部分时间里,进程将处于睡眠状态,阻塞硬件。

当进程在系统调用中休眠时,它可以接收一个 Unix 异步信号(比如 SIGTERM) ,然后发生以下情况:

  • 系统调用提前退出,并设置为将-EINTR 返回到用户空间。
  • 执行信号处理程序。
  • 如果进程仍在运行,它将从系统调用中获取返回值,并且可以再次进行相同的调用。

从系统调用提前返回使用户空间代码能够立即改变其响应信号的行为。例如,针对 SIGINT 或 SIGTERM 干净地终止。

另一方面,有些系统调用不允许以这种方式中断。如果系统由于某种原因停止调用,则进程可以无限期地保持这种不可终止状态。

LWN 在7月份运行了一个涉及这个话题的 文章不错

回答最初的问题:

  • 如何防止这种情况发生: 找出哪个驱动程序给您带来麻烦,要么停止使用,要么成为一名内核黑客并修复它。

  • 如何在不重新启动的情况下终止不可中断进程: 以某种方式终止系统调用。通常最有效的方式做到这一点,而不打电源开关是拉电源线。您还可以成为一名内核黑客,并让驱动程序使用 TASK _ KILLABLE,正如 LWN 文章中所解释的那样。

当进程处于用户模式时,它可以在任何时候被中断(切换到内核模式)。当内核返回到用户模式时,它检查是否有任何信号挂起(包括用于终止进程的信号,如 SIGTERMSIGKILL)。这意味着只能在返回用户模式时关闭进程。

进程不能在内核模式下被杀死的原因是,它可能会破坏同一机器上所有其他进程使用的内核结构(同样,杀死一个线程可能会破坏同一进程中其他线程使用的数据结构)。

当内核需要做一些可能需要很长时间的事情时(例如等待另一个进程写的管道或者等待硬件做一些事情) ,它通过将自己标记为睡眠并调用调度程序切换到另一个进程来睡眠(如果没有非睡眠进程,它切换到一个“虚拟”进程,该进程告诉 CPU 放慢一点速度并处于一个循环 & mash; 空闲循环中)。

如果一个信号被发送到一个休眠进程,它必须被唤醒之前,它将返回到用户空间,从而处理挂起的信号。这里我们有两种主要睡眠类型的区别:

  • TASK_INTERRUPTIBLE,可打断睡眠。如果一个任务被标记为这个标志,那么它正在睡眠,但是可以被信号唤醒。这意味着将任务标记为睡眠的代码正在期待一个可能的信号,在它醒来后将检查该信号并从系统调用返回。在处理完信号之后,系统调用可能会自动重新启动(我不会详细介绍它是如何工作的)。
  • TASK_UNINTERRUPTIBLE,不间断的睡眠。如果一个任务被标记为这个标志,那么它不会被其他任何东西唤醒,除了它正在等待的东西,要么是因为它不容易重新启动,要么是因为程序希望系统调用是原子的。这也可以用于已知非常短的睡眠。

TASK_KILLABLE(在 LWN 文章中被 ddaa 的答案链接到)是一个新的变体。

这回答了你的第一个问题。至于你的第二个问题: 你不能避免不间断睡眠,它们是一个正常的事情(例如,每次进程从/写入磁盘时都会发生) ; 然而,它们应该只持续几分之一秒。如果它们持续的时间更长,通常意味着硬件问题(或者设备驱动程序问题,在内核中看起来是一样的) ,设备驱动程序正在等待硬件做一些永远不会发生的事情。这也可能意味着您正在使用 NFS,而 NFS 服务器已关闭(它正在等待服务器恢复; 您还可以使用“ intr”选项来避免这个问题)。

最后,无法恢复的原因与内核等待返回到用户模式发送信号或终止进程的原因相同: 它可能会破坏内核的数据结构(等待可中断睡眠的代码可能会收到一个错误,告诉它返回用户空间,在那里进程可以被终止; 等待不可中断睡眠的代码不会期待任何错误)。

不可中断进程通常在页面错误之后等待 I/O。

想想这个:

  • 线程试图访问一个不在 core 中的页面(要么是一个需求加载的可执行文件,要么是一个已经被交换掉的匿名内存页面,要么是一个需求加载的 mmap ()’d 文件,这些都差不多)
  • 内核现在正在(试图)加载它
  • 在页面可用之前,进程无法继续。

进程/任务不能在这种状态下被中断,因为它不能处理任何信号; 如果中断了,就会发生另一个页面错误,并返回到原来的位置。

当我说“进程”时,我实际上是指“任务”,在 Linux (2.6)下大致翻译为“线程”,它可能在/proc 中有一个单独的“线程组”条目,也可能没有

在某些情况下,它可能需要等待很长时间。一个典型的例子就是可执行文件或 mmap 的文件位于服务器发生故障的网络文件系统中。如果 I/O 最终成功,任务将继续。如果最终失败,任务通常会得到一个 SIGBUS 或类似的东西。

回到你的第三个问题: 我认为您可以通过运行 sudo kill -HUP 1. 它将重新启动 init 而不会结束正在运行的进程,在运行它之后,我的不可中断进程就消失了。