有时,每当我在Linux中编写程序并因某种错误而崩溃时,它将成为一个不间断的过程并继续运行,直到我重新启动计算机(即使我退出).我的问题是:
是什么导致流程变得不间断?
我如何阻止这种情况发生?
这可能是一个愚蠢的问题,但有没有办法在不重新启动计算机的情况下中断它?
ddaa.. 189
不间断进程是一个恰好在系统调用(内核函数)中的过程,它不能被信号中断.
要理解这意味着什么,您需要了解可中断系统调用的概念.经典的例子是read()
.这是一个系统调用,可能需要很长时间(秒),因为它可能涉及旋转硬盘驱动器或移动磁头.在大多数情况下,该过程将处于休眠状态,阻塞硬件.
当进程在系统调用中休眠时,它可以接收到unix异步信号(比如SIGTERM),然后发生以下情况:
系统过早地调用退出,并设置为将-EINTR返回给用户空间.
执行信号处理程序.
如果进程仍在运行,它将从系统调用中获取返回值,并且可以再次进行相同的调用.
从系统调用早期返回使用户空间代码能够立即改变其响应信号的行为.例如,在对SIGINT或SIGTERM作出反应时干净地终止.
另一方面,不允许以这种方式中断某些系统调用.如果系统由于某种原因调用了停顿,则该过程可以无限期地保持在这种不可杀死状态.
LWN 在7月发表了一篇很好的文章,触动了这个话题.
回答原来的问题:
如何防止这种情况发生:找出哪个驱动程序导致您遇到麻烦,并停止使用或成为内核黑客并修复它.
如何在不重启的情况下终止不间断进程:以某种方式使系统调用终止.在不敲击电源开关的情况下,最有效的方法是拉动电源线.您也可以成为内核黑客并使驱动程序使用TASK_KILLABLE,如LWN文章中所述.
我把笔记本电脑上的电源线拉了下来,遗憾的是它无法正常工作.;-) (28认同)
@ddaa"Unix传统(因此几乎所有应用程序)认为文件存储写入是非信号中断的.改变这种保证是不安全或不实际的." - >这正是所有IMO中最错误的部分.只需中断驱动程序的读/写请求,当实际设备(硬盘/网卡/等)传送数据时,忽略它.操作系统内核的制作方式应该是开发人员无法搞砸的. (4认同)
@Dexter:你确实错过了这一点.阅读LWN文章:http://lwn.net/Articles/288056/.这些问题是由懒惰的设备驱动程序编程人员引起的,需要在设备驱动程序代码中修复. (2认同)
@ddaa我知道Linux不是微内核,虽然我不确定我的评论的哪一部分与它相关...然后,你的评论是否意味着微内核操作系统对那些"不间断"进程没有问题?因为如果没有,也许是时候让我成为一个微内核粉丝......:D (2认同)
CesarB.. 46
当进程处于用户模式时,它可以随时中断(切换到内核模式).当内核返回到用户模式时,它会检查是否有任何待处理的信号(包括那些用于终止进程的信号,例如SIGTERM
和SIGKILL
).这意味着只有在返回用户模式时才能终止进程.
在内核模式下无法终止进程的原因是它可能会破坏同一台机器中所有其他进程使用的内核结构(杀死线程的方式可能会破坏同一进程中其他线程使用的数据结构) .
当内核需要做一些可能需要很长时间的事情(等待另一个进程写入的管道或等待硬件做某事时),它会通过将自己标记为休眠并调用调度程序切换到另一个进程来休眠进程(如果没有非休眠进程,它会切换到一个"虚拟"进程,它告诉cpu减慢一点并坐在循环中 - 空闲循环).
如果信号被发送到休眠过程,则必须在它返回用户空间之前将其唤醒,从而处理待处理信号.这里我们区分了两种主要的睡眠类型:
TASK_INTERRUPTIBLE
,可中断的睡眠.如果任务标记有此标志,则它正在休眠,但可以被信号唤醒.这意味着将任务标记为休眠的代码期望可能的信号,并且在唤醒之后将检查它并从系统调用返回.处理完信号后,系统调用可能会自动重启(我不会详细介绍它是如何工作的).
TASK_UNINTERRUPTIBLE
,不间断的睡眠.如果一个任务被标记了这个标志,那么它不会被它正在等待的任何东西唤醒,因为它不能轻易地重新启动,或者因为程序期望系统调用是原子的.这也可用于已知非常短的睡眠.
TASK_KILLABLE
(在与ddaa的回答相关联的LWN文章中提到)是一种新的变体.
这回答了你的第一个问题.关于你的第二个问题:你无法避免不间断的睡眠,它们是正常的事情(例如,每当一个进程从磁盘读取/写入磁盘时就会发生这种情况); 但是,它们应该只持续一秒钟.如果它们持续的时间更长,通常意味着硬件问题(或设备驱动程序问题,看起来与内核相同),其中设备驱动程序正在等待硬件做一些永远不会发生的事情.它也可能意味着您正在使用NFS并且NFS服务器已关闭(它正在等待服务器恢复;您还可以使用"intr"选项来避免此问题).
最后,你无法恢复的原因与内核等到返回用户模式以传递信号或终止进程的原因相同:它可能会破坏内核的数据结构(等待可中断睡眠的代码可能会收到一个错误,告诉它返回到用户空间,可以杀死进程;等待不间断睡眠的代码不会出现任何错误).
不间断进程是一个恰好在系统调用(内核函数)中的过程,它不能被信号中断.
要理解这意味着什么,您需要了解可中断系统调用的概念.经典的例子是read()
.这是一个系统调用,可能需要很长时间(秒),因为它可能涉及旋转硬盘驱动器或移动磁头.在大多数情况下,该过程将处于休眠状态,阻塞硬件.
当进程在系统调用中休眠时,它可以接收到unix异步信号(比如SIGTERM),然后发生以下情况:
系统过早地调用退出,并设置为将-EINTR返回给用户空间.
执行信号处理程序.
如果进程仍在运行,它将从系统调用中获取返回值,并且可以再次进行相同的调用.
从系统调用早期返回使用户空间代码能够立即改变其响应信号的行为.例如,在对SIGINT或SIGTERM作出反应时干净地终止.
另一方面,不允许以这种方式中断某些系统调用.如果系统由于某种原因调用了停顿,则该过程可以无限期地保持在这种不可杀死状态.
LWN 在7月发表了一篇很好的文章,触动了这个话题.
回答原来的问题:
如何防止这种情况发生:找出哪个驱动程序导致您遇到麻烦,并停止使用或成为内核黑客并修复它.
如何在不重启的情况下终止不间断进程:以某种方式使系统调用终止.在不敲击电源开关的情况下,最有效的方法是拉动电源线.您也可以成为内核黑客并使驱动程序使用TASK_KILLABLE,如LWN文章中所述.
当进程处于用户模式时,它可以随时中断(切换到内核模式).当内核返回到用户模式时,它会检查是否有任何待处理的信号(包括那些用于终止进程的信号,例如SIGTERM
和SIGKILL
).这意味着只有在返回用户模式时才能终止进程.
在内核模式下无法终止进程的原因是它可能会破坏同一台机器中所有其他进程使用的内核结构(杀死线程的方式可能会破坏同一进程中其他线程使用的数据结构) .
当内核需要做一些可能需要很长时间的事情(等待另一个进程写入的管道或等待硬件做某事时),它会通过将自己标记为休眠并调用调度程序切换到另一个进程来休眠进程(如果没有非休眠进程,它会切换到一个"虚拟"进程,它告诉cpu减慢一点并坐在循环中 - 空闲循环).
如果信号被发送到休眠过程,则必须在它返回用户空间之前将其唤醒,从而处理待处理信号.这里我们区分了两种主要的睡眠类型:
TASK_INTERRUPTIBLE
,可中断的睡眠.如果任务标记有此标志,则它正在休眠,但可以被信号唤醒.这意味着将任务标记为休眠的代码期望可能的信号,并且在唤醒之后将检查它并从系统调用返回.处理完信号后,系统调用可能会自动重启(我不会详细介绍它是如何工作的).
TASK_UNINTERRUPTIBLE
,不间断的睡眠.如果一个任务被标记了这个标志,那么它不会被它正在等待的任何东西唤醒,因为它不能轻易地重新启动,或者因为程序期望系统调用是原子的.这也可用于已知非常短的睡眠.
TASK_KILLABLE
(在与ddaa的回答相关联的LWN文章中提到)是一种新的变体.
这回答了你的第一个问题.关于你的第二个问题:你无法避免不间断的睡眠,它们是正常的事情(例如,每当一个进程从磁盘读取/写入磁盘时就会发生这种情况); 但是,它们应该只持续一秒钟.如果它们持续的时间更长,通常意味着硬件问题(或设备驱动程序问题,看起来与内核相同),其中设备驱动程序正在等待硬件做一些永远不会发生的事情.它也可能意味着您正在使用NFS并且NFS服务器已关闭(它正在等待服务器恢复;您还可以使用"intr"选项来避免此问题).
最后,你无法恢复的原因与内核等到返回用户模式以传递信号或终止进程的原因相同:它可能会破坏内核的数据结构(等待可中断睡眠的代码可能会收到一个错误,告诉它返回到用户空间,可以杀死进程;等待不间断睡眠的代码不会出现任何错误).
在页面错误之后,不间断进程通常会等待I/O.
考虑一下:
该线程试图访问一个不在核心的页面(一个是需求加载的可执行文件,一个已被换出的匿名内存页面,或一个需要加载的mmap()'文件,这些都是一样)
内核现在(尝试)加载它
在页面可用之前,该过程无法继续.
在这种状态下,进程/任务不能被中断,因为它无法处理任何信号; 如果是这样的话,会发生另一个页面错误,它会回到原来的位置.
当我说"进程"时,我的意思是"任务",它在Linux(2.6)下大致转换为"线程",它可能有也可能没有/ proc中的单个"线程组"条目
在某些情况下,它可能会等待很长时间.一个典型的例子是可执行文件或mmap'd文件位于服务器发生故障的网络文件系统上.如果I/O最终成功,则任务将继续.如果它最终失败,任务通常会得到一个SIGBUS或其他东西.