在我们学操作系统时,无论是学校的操作系统课程还是某款操作系统书籍,大概率会有下面这张操作系统进程状态图:
展示了操作系统的几种进程状态:
-
就绪(Ready):进程已经准备好运行,等待被调度到CPU上。
-
运行(Running):进程当前正在使用CPU执行其指令。
-
阻塞(Blocked 或 Waiting):进程由于等待某个事件或资源而暂时不能运行。
-
挂起(Suspended 或 Inactive):这是指进程被临时移出了可调度队列,可能是因为内存管理等原因。此这种状态下的进程不会参与调度。
-
新建(New):进程刚刚创建完毕,还没有被放入任何队列。
-
终止(Terminated 或 Exit):进程已完成其工作或已被操作系统终止,不再存在于进程表中。
进程的新建状态和终止状态不重要,这里暂不讨论。
因此,操作系统中主要存在三大进程状态:
- 运行(Running):在广义上,正在使用CPU的进程 和 执行处在就绪队列中的就绪状态进程都算作运行状态(后面会解释原因)。
- 阻塞(Blocked):进程因等待某些事件或资源而无法运行。
- 挂起(Suspended):进程暂时被移出调度队列。
一、运行状态
运行状态(Running/Executing)
在操作系统理论中,当一个进程被调度器选中并在CPU上执行时,它就处于运行状态。这意味着进程正在利用CPU资源来执行其指令。
就绪队列:运行队列?
就绪队列:概念层面
处于就绪状态的进程已经准备好执行,但当前没有得到CPU的时间片。这意味着进程在就绪队列中等待被调度器选中以获取CPU资源。一旦被调度器选中,进程就可以进入运行状态。
就绪队列:数据结构层面
在操作系统理论中,每个CPU都会对应一个 名为 ` struct runQueue ` 就绪队列的结构体,该就绪队列中有一个属性 `task_struct *head` :作为就绪队列的头指针,管理着一串链表的进程 ` task_struct` ,即管理就绪队列。
当 CPU
需要运行进程时,进程调度器会类似于 将就绪队列头节点(即第一个进程)调度进入 CPU
中运行,该进程时间片结束后,再将该进程链入链表的尾部(如果该进程还未执行完)。CPU
通过控制这条链式队列,以先进先出的方式,轮转交替执行着每一个进程。
下图演示一个CPU对应的就绪队列中的进程,如何从就绪队列中被调度到CPU中运行(我这里将就绪队列称为运行队列(后面会解释原因)):
广义上的就绪队列
然而,在现代操作系统中,特别是那些采用多任务处理技术的操作系统中,"运行"状态也可以扩展到包括那些已经准备好等待CPU调度执行的进程,即位于就绪队列中的进程。一旦CPU空闲并根据调度算法选择下一个要执行的进程时,这些进程就可以立即获得CPU时间片并开始执行。
因此,可以认为处于就绪队列中的进程也处于一种广义上的"运行状态",就绪队列也可以称为 "运行队列" ,因为它们具备了执行的所有条件,并且只等待调度器分配给它们的CPU时间,由此,处于就绪队列中的进程,实际上也算作处于运行状态。
由此,我们后续文章中,为了更加契合 Linux
系统的进程状态,使理解 Linux
进程状态更加清楚,我们会将正在CPU运行的进程和处于就绪队列的进程,都称为运行状态进程,就绪队列即为运行队列。
二、阻塞状态
理论引入
操作系统无论是上层管理进程,还是下层管理硬件设备,都是先描述再组织,即先将目标描述成一个结构体(创建学生档案),再组织(拿着学生档案进行组织管理)
操作系统将每个外设描述成一个结构体,这个结构体通常被称为设备结构体(device structure)或设备驱动结构体(driver structure),它包含了与特定设备相关的各种信息和控制字段。
在该每个外设的设备结构体 中,维护着一个等待队列。等待队列用于管理那些因为等待设备操作完成而暂时不能继续执行的进程或线程。当一个进程因为等待设备输入而被阻塞时,该进程的上下文(通常是内核中的等待队列项)会被插入到这个等待队列中。
当一个进程试图从一个外部设备 (如键盘)读取数据,如遇到 scanf
这样需要用户输入的程序,但由于暂时没有输入数据而无法继续执行时,该进程会被置为阻塞状态 ,并被插入到与 该设备的设备结构体中的等待队列 中。这意味着该进程暂时不会占用CPU资源,直到它接收到所需的输入为止。
一旦该外设提供了输入(如用户通过键盘输入),操作系统会检测到这一事件,并将相应的进程从等待队列中移出,使其重新进入运行队列。此时,该进程被标记为可运行状态。当进程被调度器选中并分配到CPU时间片后,就可以继续执行而不被阻塞。
动图演示过程
下图演示,进程 1
被调度到CPU运行时,遇到类似 scanf
的程序,需要获取键盘输入的数据,但此时键盘还没有输入数据,则 进程 1
因为等待设备输入而被阻塞,系统将 进程 1
插入到 键盘
的设备结构体中的等待队列中等待 键盘
响应数据,而运行队列中的进程会继续被调度运行,当有数据输入时,操作系统系统将 进程 1
插回到运行队列,恢复运行状态
(wait_queue 就是每个外部设备所对应的 等待队列)
如何知道硬件上响应出了数据?
操作系统作为硬件的管理者,操作系统会检测当前该硬件外设上是否有数据
若响应出数据,则操作系统会将进程结构体重新链接回CPU的运行队列,轮到该进程被CPU运行时,该程序中的 scanf 就能直接从外设(键盘)上读取到设备
三、运行状态和阻塞状态小结
总而言之,进程的阻塞和运行状态,本质上是让进程处在不同的队列中(阻塞时处在外设的等待队列中,运行时处在CPU的运行队列中)
整个调度过程就是不同链表的增删查改操作
运行时程序卡住的本质
即CPU没有在调度该进程,不调度有两点原因:
一是正在CPU运行队列中等待:
运行队列过长 :
当你CPU运行队列的进程一次链接过多,会导致一次轮转周期时间加长,进程的等待时间也对应加长,加长到人能感知到的时间间隔时,就会显示出该程序发生肉眼可见的卡顿了
调度策略:
- 不同的调度算法会影响进程的等待时间。例如,时间片轮转(Round Robin)调度算法会给每个进程分配固定的时间片,而优先级调度则会优先调度高优先级的进程。
- 如果调度策略不合理,可能导致某些进程长时间得不到执行机会。
二是在外设等待队列中阻塞:
等待着外设响应数据
外设竞争加剧 :多个进程同时请求同一个外设,那么外设的资源竞争会加剧,进程的等待阻塞时间加长,也导致了程序明显卡顿
外设响应延迟:外设本身的响应速度可能受限于硬件性能或当前的工作负载。
四、挂起状态:
可以点击跳转博客观看:【Linux】进程的挂起状态-CSDN博客