
(进程控制)
大家好,很高兴又和大家见面啦!!!
在上一篇内容中,我们共同探讨了进程的状态转换与组织方式。进程在其生命周期中会经历创建态、就绪态、运行态、阻塞态和终止态这五种状态的动态变迁,形成一个完整的生命周期循环。
理解进程状态转换的关键在于掌握三种基本状态间的转换规律:
这些转换构成了进程并发执行的基础。
操作系统通过进程控制块(PCB) 来记录每个进程的状态信息,并采用链接方式(如就绪队列、阻塞队列)或索引方式来组织这些PCB,确保系统能够高效地进行进程调度和资源管理。
理解了进程状态的“是什么”和“为什么”后,我们很自然会问:
操作系统是如何精准控制这些状态转换的? 进程是如何被创建、终止和调度的? 这些状态转换背后需要怎样的机制来保障其可靠性和一致性?
现在,就让我们带着这些问题,一起深入探讨进程控制的具体实现机制,揭开操作系统如何有效管理进程生命周期的神秘面纱。
进程控制 的主要功能是对系统中的所有进程实施有效的管理,它具有创建新进程、撤销已有进程、实现进程状态转换等功能。
简单来说,进程控制就是用来实现进程的状态转换。
graph LR
a[创建态]--->b[就绪态]--->c[运行态]--->d[终止态]
c--->e[阻塞态]--->b
c--->b一个进程从创建态转换到就绪态的过程就是创建一个新进程的过程;
一个进程从运行态转换到终止态的过程就是撤销已有进程的过程;
一个进程在运行过程中的状态转换就是就绪态、运行态以及阻塞态这三种基本状态之间的转换;
而要实现这一进程控制的方式,就是通过原语。在之前的内容中我们有介绍过原语:
原语是操作系统底层具有以下特点的公用小程序:
所谓的原子性指的是:一个操作或一系列操作被视为一个不可分割的整体。它们要么全部成功执行,要么完全不执行,不会出现“执行了一半”的中间状态。
为什么需要通过原语来实现进程控制呢?
这里我们以链接方式的进程组织形式进行说明:
graph LR
a[执行指针]--->PCB1
b[就绪指针]--->PCB2--->PCB3--->PCB4
c[阻塞指针]--->PCB5--->PCB6--->PCB7在操作系统中,进程会根据自己此时所处的状态而进入相应的队列中,而判断进程相应状态的方式,我们可以通过变量 state 来实现,如:
state == 1 表示该进程处于运行态state == 2 表示该进程处于就绪态state == 3 表示该进程处于阻塞态那么一个进程要完成状态的转换就需要执行两步操作:
state 更改为相应的状态值那么我们设想一下如果进程的状态转换不具备原子性,那么就会出现下面这种情况:
当 PCB1 的 CPU 时间片使用完后,其状态需要从运行态转换到就绪态。在整个转换的过程中,操作系统会执行以下指令:
state = 1 更改为 state == 2PCB1 排入就绪队列graph LR
a[执行指针]
b[就绪指针]--->PCB2[PCB2<br>state = 1]--->PCB3[PCB3<br>state = 2]--->PCB4[PCB4<br>state = 2]--->PCB1[PCB1<br>state = 2]
c[阻塞指针]--->PCB5[PCB5<br>state = 3]--->PCB6[PCB6<br>state = 3]--->PCB7[PCB7<br>state = 3]
classDef red fill: #ff9999, color: #000, stroke: #ff0000, stroke-width: 2px
class PCB1 red但是,在执行的过程中,在完成了状态变量的修改后,操作系统接收到了来自外部的中断指令,此时就无法执行第二步操作。
graph LR
a[执行指针]--->PCB1[PCB1<br>state = 2]
b[就绪指针]--->PCB2[PCB2<br>state = 2]--->PCB3[PCB3<br>state = 2]--->PCB4[PCB4<br>state = 2]
c[阻塞指针]--->PCB5[PCB5<br>state = 3]--->PCB6[PCB6<br>state = 3]--->PCB7[PCB7<br>state = 3]
classDef red fill: #ff9999, color: #000, stroke: #ff0000, stroke-width: 2px
class PCB1 red这就导致了上图所示的情况—— PCB1 始终保持着占用 CPU 无法进行解除。
为了避免这种情况的发生,我们就需要确保状态转换操作一气呵成,因此必须使用原语来实现状态转换操作。
那么现在问题来了,原语是如何实现的将操作一气呵成呢?
在操作系统中存在着两条特权指令——关中断与开中断。
这两条指令的作用分别为:
这里我们通过一个简单的例子进行说明:
经过前面的学习,我们知道,所有的程序都是由一条条的指令构成,而这些指令会在运行时,被存储到内存块中,逐个执行;并且每执行完一条指令,都需要检测一下中断信号;
graph LR
subgraph 内存块
a[...]
b[指令1]
close[关中断]
c[指令2]
d[指令3]
open[开中断]
e[指令4]
f[...]
end
g[中断信号]--->c上图所示的就是在操作系统的内核中执行的一个程序。当这个程序正常执行时,其执行的顺序为:
从图中我们可以看到,在执行完关中断后,执行指令2之前存在一条中断信号,如果在正常的执行逻辑下,CPU会执行以下操作:
但是,在检测到该中断信号之前,CPU执行了关中断指令,这时 CPU 会直接忽略该信号,而继续往下执行,并且在执行开中断之前,会屏蔽所有的可屏蔽中断请求;
当 CPU 执行了开中断之后,会重新开始进行中断信号的检测,继续回到 每执行一条指令,就检测一次中断信号 的状态;
正是因为这两条特权指令,这就确保了这两条指令中间所有指令的原子性,就比如上图中的指令2和指令3;
下面我们需要思考一个问题——为什么开中断与关中断属于特权指令?
这里咱们先卖个关子,具体的内容我们留到下一篇中再详细的进行探讨。
今天的内容到这里就全部结束了。通过今天的学习,我们深入理解了进程控制的核心机制。
我们重点探讨了进程控制的核心实现方式——原语。
这种"一气呵成"的执行特性,有效避免了因中断导致的进程状态不一致问题,为系统的稳定运行提供了坚实保障。
在接下来的内容中,我们将继续深入探讨具体的进程控制原语,了解操作系统是如何通过不同的原语来实现精确的进程管理。
互动与分享
感谢您的耐心阅读! 关注博主,不错过更多技术干货。我们下一篇再见!