什么是不间断进程?

183

有时在Linux中编写程序时,如果由于某种错误导致崩溃,它将变成不可中断的进程,并继续运行直到我重启计算机(即使我注销)。我的问题如下:

  • 是什么原因导致进程变成不可中断?
  • 如何防止这种情况发生?
  • 这可能是个蠢问题,但是否有任何方法可以在不重新启动计算机的情况下中断它?

是否有可能编写一个程序,使其在系统不处于空闲状态时进入“TASK_UNINTERUPTIBLE”状态,强制收集数据,并等待超级用户退出后再进行传输?这将是黑客检索信息、返回僵尸状态并通过空闲网络传输信息的一大机遇。有人会认为这是为当权者创建“后门”以便随意进出任何系统的途径之一。我坚信,通过消除“TASK_UNINTERUPTIB”,可以永久地封堵这个漏洞。 - Nuuwski
2
请分享一下代码? - again
5个回答

233

一个不可中断的进程是指正在进行系统调用(内核函数)的进程,该调用无法被信号中断。

要理解这是什么意思,您需要了解可中断系统调用的概念。经典的例子是 read()。这是一个可能需要很长时间(几秒钟)的系统调用,因为它可能涉及到启动硬盘或移动磁头等操作。在大部分时间里,进程将会睡眠,阻塞在硬件上。

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

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

从系统调用中提前返回使得用户空间代码能够立即根据信号改变其行为。例如,在响应 SIGINT 或 SIGTERM 时进行清理终止。

另一方面,有些系统调用不允许以这种方式中断。如果系统调用由于某种原因而停滞不前,则该进程可能会无限期地保持在这种不可杀死的状态中。

LWN在7月份发表了一篇涉及此主题的好文章

回答原始问题:

  • 如何防止这种情况发生:找出导致问题的驱动程序,然后停止使用它,或者成为内核黑客并修复它。

  • 如何在不重新启动计算机的情况下终止一个不可中断的进程:以某种方式使系统调用终止。通常,在不使用电源开关的情况下最有效的方法是拔掉电源线。您也可以成为内核黑客,并使驱动程序使用 TASK_KILLABLE,如 LWN 文章所述。


62
我把笔记本电脑的电源线拔掉了,但很遗憾它现在无法工作。;-) - thecarpy
2
难道不应该是EINTR而不是EAGAIN吗?此外,read()返回-1并设置errno为错误代码。 - lethalman
2
@Dexter:你确实没有抓住重点。请阅读LWN文章:http://lwn.net/Articles/288056/。这些问题是由于懒惰的设备驱动程序员引起的,需要在设备驱动程序代码中进行修复。 - ddaa
4
“Unix传统(因此几乎所有应用程序)认为文件存储写操作是不可中断的。改变这个保证既不安全也不实际。” -> 在我看来,这恰恰是整个问题中最错误的部分。只需中断驱动程序的读/写请求,当实际设备(硬盘/网络卡等)传送数据时,忽略它即可。操作系统内核应该设计成无论开发人员如何操作都不会出错。 - Dexter
2
@ddaa,我知道Linux不是微内核,但我不确定我的评论中哪部分与此有关...那么,你的评论是否意味着微内核操作系统没有这些“不可中断”的进程问题?因为如果没有,也许现在是我成为微内核粉丝的时候了... :D - Dexter
显示剩余7条评论

62

当一个进程处于用户模式时,它可以在任何时候被中断(切换到内核模式)。当内核返回用户模式时,它会检查是否有任何未处理的信号(包括用于杀死进程的信号,如SIGTERMSIGKILL)。这意味着只有在返回用户模式时才能杀死进程。

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

当内核需要做某些可能需要很长时间的事情(例如等待另一个进程写入管道或等待硬件执行某些操作),它会通过标记自身为睡眠状态并调用调度程序来切换到另一个进程进行睡眠(如果没有非睡眠进程,则切换到“虚拟”进程,告诉CPU稍微慢一点并进入循环,即空闲循环)。

如果发送信号给正在睡眠的进程,则必须先将其唤醒,然后才能返回到用户空间,从而处理未处理的信号。这里我们有两种主要类型的睡眠之间的区别:

  • TASK_INTERRUPTIBLE,可中断睡眠。如果任务被标记为此标志,则它正在睡眠,但可以通过信号唤醒。这意味着标记任务为睡眠的代码预计可能会收到信号,并在唤醒后检查并从系统调用返回。信号处理完成后,系统调用有可能会自动重新启动(我不会详细介绍如何工作)。
  • TASK_UNINTERRUPTIBLE,不可中断睡眠。如果任务被标记为此标志,则它不希望被任何东西以外的东西唤醒,除非它在等待的内容,因为它不能轻松地重新启动,或者因为程序期望系统调用是原子的。这也可用于众所周知的短暂睡眠。

TASK_KILLABLE(dda a答案链接的LWN文章中提到的)是一种新的变体。

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

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


1
文件系统锁定漏洞也可能是原因,以我个人经验来看。 - Tobu
4
我不理解这些内容的全部意思。 “你无法避免不可中断的睡眠”-操作系统不能被设计成不允许不可中断的睡眠作为一种状态吗? 接下来是有关污染的部分 - 不能终止过程的内核模式部分(或其他可能导致污染的部分),或者修改其代码使其返回吗? 请解释为什么这是如此困难/不可能做到,即使Linux也没有做到。 (我以为这个问题只存在于Windows上) - Dexter
我能想到的唯一情况,使得(安全地)杀死这些进程真正不可能(而不仅仅是说,让我们说,异常困难),就是硬件本身可能会导致损坏。硬件无法被控制;内核可以。但正是内核从硬件获取数据并修改内存(这就是为什么在进程返回用户模式之前必须不释放它,以及为什么可能会发生损坏)...更改内存中的内核代码,问题就解决了。 - Dexter
@Dexter,把内核看作一个单一的多线程进程,其中每个进程的内核模式部分是内核中的一个线程。你的建议就像在多线程程序中杀死一个线程一样糟糕:它可能会留下悬空锁、数据结构暂时修改或正在被修改等问题。 - CesarB
1
@CesarB 嗯,你说得没错,可以杀死一个线程...但是“主”线程(也就是操作系统内核,其他线程可能是驱动程序)不能以某种方式处理它吗?虽然那些“正在修改中”的结构似乎是一个非常棘手的问题...也许我们真的永远不会看到一个无法中断进程的操作系统 :( - Dexter

31

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

考虑下面的情况:

  • 线程试图访问不在内核中的页面(可能是需要加载的可执行文件,已交换出去的匿名内存页面,或者是mmap()需要加载的文件,这些基本上是相同的)
  • 内核现在正在(尝试)将其加载到内存中
  • 进程在页面可用之前无法继续。

在此状态下,进程/任务无法被中断,因为它无法处理任何信号;如果它这样做,另一个页面错误会发生,它会回到先前的状态。

当我说“进程”时,实际上指的是“任务”,在Linux(2.6)下大致对应于“线程”,它可能具有/proc中的单独的“线程组”条目。

在某些情况下,可能需要等待很长时间。这种情况的典型示例是可执行文件或mapped文件位于网络文件系统上并且服务器失败了。如果I/O最终成功,则任务将继续进行。如果最终失败,则任务通常会收到SIGBUS或类似信号。


1
如果最终失败,任务通常会收到SIGBUS或其他信号。等等,内核不能这样做吗?当杀死那些“不可中断”的进程时,它可以简单地告诉它们I/O操作失败了吗?然后该进程将返回用户模式并消失?肯定有一种安全地杀死那些处于'D'状态的进程的方法。我猜这只是不容易,这就是为什么Windows或Linux尚未具备这种可能性的原因。另一方面,我希望能够至少不安全地杀死这些进程。我不在乎可能的系统崩溃或其他问题... - Dexter
@Dexter 嗯,我在Windows上从未遇到过这个问题。有什么方法可以在那里重现它吗?至少根据此帖子,所有I/O请求都可以在Windows中被中断。 - Ruslan
请查看Sysinternals制作的“Notmyfault”示例。它的驱动程序名为“myfault”,其中包含导致崩溃(蓝屏)或使进程处于不可中断状态的例程。 - Dexter

1
针对您的第三个问题: 我认为您可以通过运行 sudo kill -HUP 1 来终止无法中断的进程。这将重新启动 init 而不会结束正在运行的进程,而在运行此命令后,我的无法中断的进程已经消失了。

-3

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

请问您如何描述“不可中断进程”?它是否能够幸存于“kill -9”命令并继续运行?如果是这种情况,那么它可能会卡在某个系统调用上,该系统调用又卡在某个驱动程序上,直到重启(有时最好尽快重启)或卸载相关驱动程序(这不太可能发生),您将一直被困在这个进程中。您可以尝试使用“strace”来查找进程卡在哪里,并在未来避免它。


驱动程序不能像进程被终止一样被强制卸载吗?我知道内核模式比用户模式具有更高的特权访问,但它永远不可能比操作系统本身更具特权。在内核模式下执行的任何内容都可以随意干扰其他在内核模式下执行的内容 - 没有任何控制。 - Dexter

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接