Linux 进程状态

95
在Linux中,当一个进程需要从磁盘读取块时,它的状态会发生什么变化?它会被阻塞吗?如果被阻塞,另一个进程是如何被选择执行的?
8个回答

145

当一个进程需要从磁盘中获取数据时,它会有效地停止在CPU上运行,以便让其他进程运行,因为这个操作可能需要很长时间才能完成 - 至少5毫秒的寻道时间对于磁盘来说是常见的,而5毫秒相当于1000万个CPU周期,从程序的角度看,这是永恒的!

从程序员的角度来看(也称为“用户空间”),这称为阻塞系统调用。 如果您调用write(2)(它是系统调用名称的薄libc封装程序),则您的进程不会在该边界处完全停止; 它将继续在内核中运行系统调用代码。 大多数情况下,它一直走到特定的磁盘控制器驱动程序(文件名→文件系统/VFS→块设备→设备驱动程序),在那里会向适当的硬件提交获取磁盘块的命令,这通常是非常快的操作。

然后,进程被置于睡眠状态(在内核空间中,阻塞被称为睡眠-从内核的角度来看,没有任何东西是“被阻塞”的)。 一旦硬件最终获取了正确的数据,进程将被唤醒,然后进程将被标记为可运行状态并且将被调度。 最终,调度程序将运行该进程。

最后,在用户空间,阻塞系统调用会以适当的状态和数据返回,并且程序流程会继续进行。

大多数I/O系统调用可以在非阻塞模式下调用(请参见open(2)中的O_NONBLOCKfcntl(2))。 在这种情况下,系统调用会立即返回,并仅报告提交磁盘操作。 程序员必须在稍后的时间内显式检查操作是否已成功完成,并获取其结果(例如,使用select(2))。 这称为异步或基于事件的编程。

大部分提到D状态的回答(在Linux状态名称中被称为TASK_UNINTERRUPTIBLE)都是错误的。 D 状态是一种特殊的休眠模式,只有在内核空间代码路径中触发时才会出现,当该代码路径无法中断(因为编程过于复杂)时,并期望它只会阻塞很短的时间。我相信大多数“D状态”实际上是看不见的,它们存在时间非常短暂,不能被像“top”这样的采样工具观察到。

你可能会在一些情况下遇到无法被终止的处于D状态的进程。NFS以这种方式闻名,我遇到过许多次。我认为在一些VFS代码路径之间存在语义冲突,它们假设总是能够访问本地磁盘并快速检测错误(在SATA上,错误超时大约是几百毫秒),而NFS实际上从网络获取数据,网络更具有弹性并具有较慢的恢复速度(300秒的TCP超时是常见的)。请参阅此文章,了解在Linux 2.6.25中引入的TASK_KILLABLE状态的解决方案。在此之前,有一个技巧,可以通过向内核线程rpciod发送SIGKILL信号来实际发送信号给NFS进程客户端,但请忘记那个丑陋的技巧……


2
感谢您的详细回答,但请注意,这个帖子已经有一个被接受的答案将近两年了。如果您想在更近期的问题上提供帮助,请点击“问题”链接。欢迎来到Stack Overflow,并感谢您的贡献! - GargantuChet
21
这个回答是唯一提到NFS的,而在某些环境中,NFS是进程处于D状态的最常见解释。+1. - Pinko
15
非常好的回答,谢谢。同时请注意,当进程等待被换出的页面时,它们会进入D状态,因此一个频繁换页的进程将长时间处于D状态。 - cha0site
@zerodeux 很好的回答,但我认为你的模式(文件名->文件系统/VFS->块设备->设备驱动程序)应该是(文件名->VFS->文件系统(ext3)->块设备->设备驱动程序)。 - c4f4t0r
1
可以安全地假设,在内核等待自旋锁的时间(可能与磁盘I/O有关,也可能不相关),所有这些时间都会在/proc/stat中报告为D状态吗? - wick
另一个D状态的案例:慢的FUSE文件系统。 - Jonathon Reinhart

90

在等待从文件描述符读取(read())或向其写入(write())时,该进程将处于一种特殊的休眠状态,称为“D”或“磁盘休眠”。这是特殊的,因为在此状态下无法杀死或中断该进程。等待ioctl()返回的进程也将以这种方式被置于休眠状态。

唯一的例外是当以O_NONBLOCK模式打开文件(例如终端或其他字符设备),假定设备(如调制解调器)需要时间来初始化时。但是,您在问题中提到的是块设备。此外,我从未尝试过在非阻塞模式下打开的fd上可能会阻塞的ioctl()(至少不是有意识的)。

另一个进程如何选择完全取决于您使用的调度程序,以及其他进程可能已经执行的操作以修改其在该调度程序中的权重。

某些用户空间程序在某些情况下已知会永远处于此状态,直到重新启动。这些通常与其他“僵尸”一起分组,但该术语并不正确,因为它们在技术上并不失效。


1
一个等待ioctl()返回的进程也会以这种方式被挂起。我刚刚杀死了我的用户空间进程,因为它在等待阻塞IOCTL,所以这不是真的。除非我误解了什么。 - Hamzahfrq
这样的测试将会非常困难。无法中断的进程无法被终止;如果您能够杀死它,那么它只是阻塞了(内核不在任何ioctl的任何部分中,并将任何相应的响应复制到您传递的位置的用户空间(或者至少没有在复制的过程中))。自2009年撰写此文以来,Linux也发生了很多变化;这种现象已经不像过去那样明显了。 - Tim Post
这是否意味着异步IO不会导致D状态? - Ted
@Ted Async io 的意思是,如果系统调用在 I/O 上挂起,进程不会完全阻塞,而是会继续轮询描述符以获取更新。 - Tim Post

8
执行I/O操作的进程将被置于D状态(不可中断睡眠),这会释放CPU,直到有硬件中断通知CPU返回执行程序。请参阅man ps了解其他进程状态。
根据您的内核版本,有一个进程调度器,它跟踪准备执行的进程运行队列。通过使用调度算法,它告诉内核将哪个进程分配给哪个CPU。需要考虑内核进程和用户进程。每个进程分配一个时间片,即允许使用的CPU时间块。一旦进程使用完其所有时间片,它将被标记为过期,并在调度算法中降低优先级。
2.6内核中,有一个O(1)时间复杂度调度器,因此无论您运行多少个进程,它都会以恒定时间分配CPU。然而,由于2.6引入了抢占和CPU负载平衡不是易于实现的算法,所以它更加复杂。无论如何,它很高效,CPU不会闲置,而您等待I/O操作。

3
如其他人所解释的那样,“D”状态(不可中断的睡眠)的进程是导致ps进程挂起的原因。我在RedHat 6.x上遇到过许多次,其中包括自动挂载的NFS家目录。
您可以使用以下命令列出处于D状态的进程:
cd /proc
for i in [0-9]*;do echo -n "$i :";cat $i/status |grep ^State;done|grep D

要查看进程的当前目录,或者可能存在问题的已挂载NFS磁盘,您可以使用类似以下示例的命令(将31134替换为休眠进程号):

# ls -l /proc/31134/cwd
lrwxrwxrwx 1 pippo users 0 Aug  2 16:25 /proc/31134/cwd -> /auto/pippo

我发现使用带有 -f(强制)开关的 umount 命令,针对相关的挂载 nfs 文件系统,可以唤醒休眠中的进程:
umount -f /auto/pippo

文件系统由于被占用而未能卸载,但相关进程已经被唤醒,我成功解决了问题而不需要重新启动系统。

1

是的,在read()系统调用中,任务会被阻塞。另一个准备好的任务会运行,或者如果没有其他准备好的任务,则会运行空闲任务(对于该CPU)。

正常的阻塞磁盘读取会导致任务进入“D”状态(如其他人所述)。即使它们不消耗CPU,这些任务也会对负载平均值产生影响。

一些其他类型的IO,特别是tty和网络,不会完全相同 - 进程最终进入“S”状态,可以被中断,并且不计入负载平均值。


1

假设您的进程是单线程的,并且您正在使用阻塞式 I/O,则您的进程将会被阻塞等待 I/O 完成。内核将根据 niceness、优先级、上次运行时间等在此期间选择另一个进程来运行。如果没有其他可运行的进程,内核将不会运行任何进程;相反,它将告诉硬件机器处于闲置状态(这将导致功耗降低)。

等待 I/O 完成的进程通常会显示为状态 D,例如 pstop


我使用了约10%的总内存来启动多个进程。我注意到其中许多进程处于D状态。这是由于特定计算机上IO速度慢造成的吗?比如我有9个进程,它们可能会竞争IO,其中许多进程都处于D状态。 - Kemin Zhou
@KeminZhou 相对于 CPU 速度,I/O 速度相当慢,即使是快速的 I/O。一个 I/O 密集型进程可以轻松地占用磁盘,甚至是固态硬盘。10 个 I/O 密集型进程可能会占用相当多的资源。 - derobert

0

是的,等待IO的任务会被阻塞,其他任务会得到执行。选择下一个任务是由Linux调度程序完成的。


0
通常该过程将会阻塞。如果读操作在标记为非阻塞的文件描述符上进行,或者进程正在使用异步IO,则不会阻塞。此外,如果进程有其他未阻塞的线程,它们可以继续运行。
下一个运行的进程的决定由内核中的调度程序做出。

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