在 Linux 中,当进程需要从磁盘读取块时,它的状态会发生什么变化?被封锁了吗?如果是这样,如何选择另一个进程来执行?
8 回答
当一个进程需要从磁盘获取数据时,它实际上会停止在 CPU 上运行以让其他进程运行,因为该操作可能需要很长时间才能完成——磁盘至少 5ms 的寻道时间是常见的,而 5ms 是 1000 万CPU 周期,从程序的角度来看是永恒的!
从程序员的角度来看(也称为“在用户空间中”),这称为阻塞系统调用。如果你调用write(2)
(它是同名系统调用的一个瘦 libc 包装器),你的进程不会完全停止在那个边界;它继续在内核中运行系统调用代码。大多数情况下,它一直到特定的磁盘控制器驱动程序(文件名→文件系统/VFS→块设备→设备驱动程序),其中获取磁盘上的块的命令被提交给适当的硬件,这是一个非常大部分时间快速操作。
然后进程进入睡眠状态(在内核空间中,阻塞被称为睡眠——从内核的角度来看,没有任何东西被“阻塞”)。一旦硬件最终获取了正确的数据,它将被唤醒,然后该进程将被标记为可运行并被调度。最终,调度程序将运行该进程。
最后,在用户空间中,阻塞系统调用返回正确的状态和数据,程序流程继续进行。
可以在非阻塞模式下调用大多数 I/O 系统调用(参见和)。在这种情况下,系统调用立即返回并且只报告提交磁盘操作。程序员稍后必须明确检查操作是否完成、成功与否,并获取其结果(例如,使用)。这称为异步或基于事件的编程。O_NONBLOCK
open(2)
fcntl(2)
select(2)
这里提到D 状态(TASK_UNINTERRUPTIBLE
在 Linux 状态名称中称为)的大多数答案都是不正确的。D状态是一种特殊的睡眠模式,仅在内核空间代码路径中触发,当该代码路径不能被中断时(因为它太复杂而无法编程),期望它只会阻塞很长时间短时间。我相信大多数“D 状态”实际上是不可见的;它们的寿命很短,无法通过“top”等采样工具观察到。
在少数情况下,您可能会遇到处于 D 状态的无法杀死的进程。NFS 以它而闻名,我遇到过很多次。我认为一些 VFS 代码路径之间存在语义冲突,它假定总是到达本地磁盘和快速错误检测(在 SATA 上,错误超时大约为 100 毫秒)和 NFS,它实际上从网络获取数据更具弹性并且恢复缓慢(300 秒的 TCP 超时很常见)。阅读这篇文章,了解Linux 2.6.25 中引入的酷炫解决方案TASK_KILLABLE
。在这个时代之前,有一个黑客可以通过向内核线程发送 SIGKILL 来向 NFS 进程客户端发送信号rpciod
,但忘记那个丑陋的技巧......
在等待文件描述符返回read()
或write()
从文件描述符返回时,进程将进入一种特殊的睡眠状态,称为“D”或“磁盘睡眠”。这是特殊的,因为在这种状态下进程不能被杀死或中断。等待从 ioctl() 返回的进程也将以这种方式进入睡眠状态。
一个例外是当文件(例如终端或其他字符设备)以O_NONBLOCK
模式打开时,在假定设备(例如调制解调器)需要时间初始化时通过。但是,您在问题中指出了块设备。此外,我从未尝试过ioctl()
可能会阻止以非阻塞模式打开的 fd(至少在不知情的情况下)。
如何选择另一个进程完全取决于您正在使用的调度程序,以及其他进程可能会在该调度程序中修改它们的权重。
某些用户空间程序在某些情况下会永远保持这种状态,直到重新启动。这些通常与其他“僵尸”组合在一起,但该术语并不正确,因为它们在技术上并未失效。
执行 I/O 的进程将被置于D 状态(不可中断睡眠),这会释放 CPU,直到有硬件中断告诉 CPU 返回执行程序。参见man ps
其他进程状态。
根据您的内核,有一个进程调度程序,它跟踪准备执行的进程的运行队列。它与调度算法一起,告诉内核将哪个进程分配给哪个 CPU。有内核进程和用户进程需要考虑。每个进程都被分配了一个时间片,这是它被允许使用的一块 CPU 时间。一旦进程使用了它的所有时间片,它就会被标记为过期并在调度算法中给予较低的优先级。
在2.6 内核中,有一个O(1) 时间复杂度调度程序,因此无论您运行多少进程,它都会在恒定时间内分配 CPU。但它更复杂,因为 2.6 引入了抢占和 CPU 负载平衡不是一个简单的算法。在任何情况下,它都是高效的,并且 CPU 在您等待 I/O 时不会保持空闲状态。
正如其他人已经解释的那样,处于“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
文件系统没有卸载,因为它很忙,但是相关进程确实唤醒了,我能够在不重新启动的情况下解决问题。
假设您的进程是单线程,并且您正在使用阻塞 I/O,您的进程将阻塞等待 I/O 完成。内核会根据niceness、优先级、上次运行时间等选择另一个进程在此期间运行。如果没有其他可运行的进程,内核将不会运行任何进程;相反,它会告诉硬件机器处于空闲状态(这将降低功耗)。
等待 I/O 完成的进程通常以状态 D 显示,例如ps
和top
。
是的,任务在 read() 系统调用中被阻塞。另一个准备好的任务运行,或者如果没有其他任务准备好,则空闲任务(针对该 CPU)运行。
正常的阻塞磁盘读取会导致任务进入“D”状态(正如其他人所指出的那样)。此类任务有助于平均负载,即使它们不消耗 CPU。
一些其他类型的 IO,尤其是 ttys 和网络,表现不完全相同 - 进程最终处于“S”状态并且可以被中断并且不计入平均负载。
是的,等待 IO 的任务被阻塞,其他任务被执行。选择下一个任务由Linux 调度程序完成。
一般进程会阻塞。如果读取操作是在标记为非阻塞的文件描述符上进行的,或者如果进程使用异步 IO,则它不会阻塞。此外,如果该进程有其他未阻塞的线程,它们可以继续运行。
接下来运行哪个进程取决于内核中的调度程序。