目录
- 前言
- [1. 运行状态](#1. 运行状态)
- [2. 阻塞状态](#2. 阻塞状态)
- [3. 挂起状态](#3. 挂起状态)
前言
接着上一篇文章 进程概念(三) 讲到,我们了解到了进程属性中的 PID,也了解了 ./test 这是在系统层面上系统自动创建的进程,进而初始 fork ---- 代码层面上手动创建进程,以及关于 fork背后的一些原理,而这篇文章,我们主要介绍进程状态当中的运行、阻塞、挂起。
R 运行状态(running): 并不意味着进程一定在运行中,它表明进程要么是在运行中要么在运行队列里。
S 睡眠状态(sleeping): 意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠(interruptible sleep))。
D 磁盘休眠状态(Disk sleep): 有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。
T 停止状态(stopped):可以通过发送 SIGSTOP 信号给进程来停止(T)进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。
X 死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。
接下来我们会较为详细的介绍什么是运行、阻塞、挂起状态
1. 运行状态
众所周知,存在于系统中的进程并不是一个两个,可能十几个,甚至几十个,上百个进程。而操作系统为了方便管理这些进程,对待进程都是 "先描述,后组织" 的理念,而其中的 PCB 在操作系统中都是以双链表的形势被组织起来的。但是,进程有多个,CPU 一般只有一个啊,所以就一定存在一种现象,众多的进程一定要竞争这一个 CPU 的资源。而先运行谁,是调度器说了算,但是调度器也要保证,CPU 的资源被合理的使用。所以每个 CPU 都要维护一个类似如下的运行队列:
struct runqueue
{
// 运行队列
struct task_struct* head;
struct task_struct* tail;
......
}
而进程想要被 cpu 执行,那么就需要先被列入到运行队列当中,然后在队列中排队(排队的是 PCB,而不是这个进程的代码和数据),所以 cpu 想要执行某个进程,就直接从运行队列中的队头进程拿去运行就可以了。而调度器可以理解为一个函数,将运行队列作为参数传递给调度器之后,调度器就可以通过 head 和 tail 指针找到所有在排队的进程。所以凡是处于运行队列当中的进程,这些进程的状态,称之为 运行状态 (也即运行态)( R )。
问题一:一个进程只要被CPU开始运行了,就一直到执行完毕才会停下来吗?
不是的。如果是这样的话,那么当我们代码中存在 while(1)
这样的死循环的时候,那么这个进程就会一直被 CPU 执行下去,反之,其它进程就无法被 CPU 进行调度运行,我们看到的应该是其它进程一直处于 "卡着不动" 的状态。但事实上,我们最多就是感觉到多了一点卡顿,并不至于无法正常运行其它的进程。
所以为了防止某个进程一直赖在 CPU 上面不下来的情况,每个进程都有一个叫做 时间片 的概念(比如一次调度最多执行 10ms,之后就会被重置到运行队列的队尾重新排队等待调度)。这样就可以使得,一段时间内,所有的进程都会被调度运行。所以这样就会出现大量的进程从 CPU 上放上去,再被拿下来的动作,而这个过程我们称为 进程切换。而 cpu 的速度是非常块的,即使 cpu 不断的进行进程切换,我们肉眼也无法有所察觉。
2. 阻塞状态
在操作系统的底层中,存在各种设备(键盘,鼠标,网卡等),而这些设备也称为外设。对于系统而言,无非就是从外设做读取数据,或者将内存中的数据写入到外设中。而之前的文章里面,我们已经谈到了操作系统是一款对软硬件资源做管理的软件,而之前所说的操作系统对进程做管理,而进程背后的本质就是软件(系统中的进程的本质可以理解为代码和数据,PCB也是由操作系统创建出来的结构体对象),所以对进程做管理,就是在对软件做管理。对进程做调度管理,有运行队列,那么操作系统对硬件做管理,有什么呢?又或者操作系统是如何对硬件做管理的?
只要是管理,就是 "先描述,再组织",所以硬件也是如此。所以系统对进程的描述可以类似如下:
struct dev(网卡/键盘/鼠标)
{
int type;
int status;
struct task_struct* waitqueue;
}
所以面对各种各样的设备,操作系统都可以以结构体的形式进行描述,再以链表的形式将这些外设链接起来。假设今天我们所编写的 C 语言,我们要从键盘当中读取数据,但是当我调用了 scanf 之后,我就是不输入。那么这个进程就无法在运行队列中排队等待调度,因为此刻这个进程需要访问的是键盘这个外设,而我们不输入,就相当于这个外设还没有准备就绪,那么这个进程就需要等待外设资源,所以它就会被链入到键盘这个设备的等待队列中,而不是在运行队列中等待调度。如果后续还有其它进程也需要键盘这个外设资源,它也到键盘的等待队列中进行排队,直到驱动程序发现从键盘当中读取到数据了,系统就会将原本处于键盘的等待队列中的进程,放入到 cpu 的运行队列中排队等待调度。所以我们将正在等待特定设备的进程,我们称为 这个进程处于阻塞状态!
不过,与运行队列不同的是,一个操作系统中,如果 cpu 只有一个,那么它一定只有一个运行队列。但是阻塞队列可能有十几个,几十个甚至更多,因为每一个设备都有自己的等待队列,而进程中也可能存在等待队列。
所以什么是阻塞状态? ---- 当一个进程需要访问某种资源的时候,但是该资源没有就绪,那么只需要将这个进程的 PCB 链入到这个设备的等待队列中即可,而处于等待队列中的进程,称为阻塞状态。当资源就绪时,操心系统会对正在处于等待队列中的进程进行唤醒,而唤醒的本质就是,将进程的阻塞状态改为运行状态,然后放在运行队列中,这就叫做进程唤醒。
3. 挂起状态
假设现在操作系统内部的内存资源严重不足了,但是操作系统发现,有很多的进程在等待着某个设备,处于阻塞状态中。而操作系统需要保证系统的正常运行,现在内存不足了,它就得想办法把内存资源腾出来。我们需要知道的是,处于阻塞状态的进程,它的代码和数据在内存中是处于空闲状态的(就是没有被执行)。于是,在操作系统内存资源严重不足的情况下,就会将处于等待队列中的进程的 pcb 在内存中保留,而进程的代码和数据就被交换到磁盘等外设当中(这个过程我们称为换出 )。 等到资源就绪了,系统再重新考虑将这个进程的代码和数据重新写回到内存当中,而这个过程我们称为 换入 。所以这种只有 pcb 在内存中,而它的代码和数据被换出到外设的进程,我们称为 挂起状态。而在这些进程等待某种资源的就绪时,这些被换出的空间,操作系统就可以分配给其它有需要的进程使用。而操作系统也不会针对部分进程这么做,这种策略,针对系统中所有正在处于阻塞状态的进程!
上述这三种状态,都是操作系统学科所涉及到的三个进程状态,后续我们会进一步谈论 linux 系统当中是如何维护这种进程状态的,在 linux 当中,运行状态是什么,阻塞状态又是什么,挂起状态又是什么?后续文章也会谈论操作系统的状态和具体的 linux 操作系统的状态有什么异同。
如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!
感谢各位观看!