就绪状态下的进程特点及管理策略
2021-08-047.7k 阅读
就绪状态下进程的基本概念
就绪状态的定义
在操作系统中,进程存在多种状态,就绪状态是其中重要的一种。当一个进程已经具备了运行所需的一切条件,只是因为CPU资源被其他进程占用而暂时不能运行时,该进程就处于就绪状态。可以将其类比为运动员在起跑线上,一切准备就绪,只等发令枪响(获得CPU时间片)就可以开始奔跑(执行任务)。
从操作系统的角度来看,处于就绪状态的进程已经分配到了除CPU之外的所有必要资源,如内存空间、打开的文件描述符等。它在就绪队列中等待调度程序根据一定的调度算法分配CPU时间,一旦获得CPU,进程就会从就绪状态转变为运行状态。
与其他进程状态的关联
- 从创建到就绪:进程在刚被创建时,首先会进入就绪状态。操作系统为其分配必要的资源,如进程控制块(PCB)、内存空间等,然后将其放入就绪队列。例如,当我们在Linux系统中通过
fork()
系统调用创建一个新进程时,新创建的子进程就会处于就绪状态,等待被调度运行。以下是简单的fork()
示例代码:
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid;
pid = fork();
if (pid == 0) {
// 子进程代码
printf("I am the child process, in ready state soon.\n");
} else if (pid > 0) {
// 父进程代码
printf("I am the parent process, child created.\n");
} else {
// fork失败
perror("fork");
return 1;
}
return 0;
}
- 从就绪到运行:当调度程序从就绪队列中选择一个进程,并为其分配CPU时间片时,该进程就从就绪状态转变为运行状态。调度算法的选择会影响哪个进程优先获得CPU,常见的调度算法有先来先服务(FCFS)、短作业优先(SJF)、时间片轮转等。
- 从运行到就绪:在时间片轮转调度算法中,当一个进程的时间片用完时,它会从运行状态回到就绪状态,重新进入就绪队列等待下一次被调度。另外,当一个更高优先级的进程进入就绪队列时,当前运行的进程可能会被抢占,也会回到就绪状态。例如,在实时操作系统中,为了保证实时任务的及时执行,低优先级进程在运行时如果有高优先级实时进程进入就绪状态,低优先级进程就会被抢占,回到就绪状态。
- 从就绪到阻塞:虽然就绪状态的进程已经具备运行条件,但某些外部事件可能导致它暂时无法继续运行,从而进入阻塞状态。比如,进程在就绪状态下发起了一个I/O请求,在I/O操作完成之前,进程无法继续执行,此时就会进入阻塞状态。以网络编程为例,当进程在就绪状态下调用
socket()
函数发起网络连接请求时,如果网络连接暂时无法建立,进程就会进入阻塞状态等待连接成功。
就绪状态下进程的特点
资源分配特点
- 已分配非CPU资源:如前文所述,处于就绪状态的进程已经获得了除CPU之外的必要资源。内存方面,进程有自己独立的地址空间,这保证了进程的数据和代码不会与其他进程混淆。例如,在32位操作系统中,每个进程理论上可以拥有4GB的虚拟地址空间。文件资源方面,进程如果在创建或运行前阶段打开了文件,会获得相应的文件描述符,用于后续对文件的读写等操作。在Linux系统中,进程通过
open()
函数打开文件后会返回一个文件描述符,即使进程处于就绪状态,该文件描述符依然有效,且对应的文件资源已分配给该进程。 - 资源等待复用:虽然进程已经分配了资源,但由于未获得CPU,这些资源处于一种等待被充分利用的状态。就像一辆加满油、检查好各项性能的汽车,停在停车场等待司机(CPU)来驾驶。例如,进程分配到的内存空间中存储了代码和数据,但只有在进程获得CPU并开始执行时,这些代码才能被真正执行,数据才能被处理。这种资源的预分配和等待复用,一方面保证了进程一旦获得CPU就能快速开始运行,另一方面也对操作系统的资源管理提出了挑战,需要合理规划资源,避免资源浪费。
调度相关特点
- 队列等待:就绪状态的进程都在就绪队列中等待调度。就绪队列可以是简单的先进先出(FIFO)队列,对应先来先服务调度算法;也可以是优先级队列,高优先级的进程排在队列前面,优先被调度。不同的队列结构和调度算法对进程的执行顺序和响应时间有很大影响。例如,在一个基于优先级队列的调度系统中,实时进程的优先级较高,会排在就绪队列的前端,能更快地获得CPU资源,保证其实时性要求。
- 可被抢占:在支持抢占式调度的操作系统中,处于就绪状态的高优先级进程可以抢占正在运行的低优先级进程的CPU资源。这使得操作系统能够及时响应高优先级任务,提高系统的整体性能和响应速度。例如,在处理紧急的系统任务(如内核中断处理)时,相关的高优先级进程一旦进入就绪状态,就可以抢占当前正在运行的普通用户进程的CPU,确保紧急任务得到及时处理。
状态标识特点
- 进程控制块(PCB)标识:每个进程都有一个对应的进程控制块,PCB中包含了进程的各种信息,其中就有标识进程当前状态的字段。当进程处于就绪状态时,该字段会被设置为相应的就绪状态标识。在Linux内核中,
task_struct
结构体就是进程控制块,其中的state
字段用于表示进程状态,当state
的值为TASK_RUNNING
(实际在Linux中,TASK_RUNNING
表示进程处于就绪或运行状态,但从概念上可理解为就绪状态时也会设置为此值)时,就表示进程处于就绪状态,等待被调度运行。 - 系统全局状态感知:操作系统通过对所有进程状态的监控,能够全局感知哪些进程处于就绪状态。这有助于调度程序根据系统的整体情况,如CPU负载、内存使用等,合理地选择就绪进程进行调度。例如,当系统CPU负载较高时,调度程序可能会优先选择执行时间短的就绪进程,以提高系统的吞吐量;当系统内存紧张时,可能会优先调度对内存需求小的就绪进程。
就绪状态下进程的管理策略
调度算法策略
- 先来先服务(FCFS):这是一种最简单的调度算法,按照进程进入就绪队列的先后顺序进行调度。就像排队买票,先到的人先买。在FCFS算法中,就绪队列是一个FIFO队列,新进入就绪状态的进程被添加到队列末尾,调度程序每次从队列头部取出进程并分配CPU。优点是实现简单,公平性好,每个进程都按照到达顺序获得服务。缺点是如果前面有一个执行时间很长的进程,后面的短进程就需要等待很长时间,导致平均周转时间和平均等待时间较长。以下是一个简单的FCFS调度算法模拟代码(以Python为例):
class Process:
def __init__(self, pid, arrival_time, burst_time):
self.pid = pid
self.arrival_time = arrival_time
self.burst_time = burst_time
self.waiting_time = 0
self.turnaround_time = 0
def fcfs(processes):
processes.sort(key=lambda p: p.arrival_time)
total_waiting_time = 0
total_turnaround_time = 0
current_time = 0
for process in processes:
if current_time < process.arrival_time:
current_time = process.arrival_time
process.waiting_time = current_time - process.arrival_time
process.turnaround_time = process.waiting_time + process.burst_time
total_waiting_time += process.waiting_time
total_turnaround_time += process.turnaround_time
current_time += process.burst_time
avg_waiting_time = total_waiting_time / len(processes)
avg_turnaround_time = total_turnaround_time / len(processes)
return avg_waiting_time, avg_turnaround_time
# 示例数据
processes = [
Process(1, 0, 24),
Process(2, 0, 3),
Process(3, 0, 3)
]
avg_waiting_time, avg_turnaround_time = fcfs(processes)
print(f"Average Waiting Time: {avg_waiting_time}")
print(f"Average Turnaround Time: {avg_turnaround_time}")
- 短作业优先(SJF):该算法优先调度预计执行时间最短的就绪进程。这种算法的优点是可以有效降低平均周转时间和平均等待时间,提高系统的吞吐量。但缺点是需要预先知道每个进程的执行时间,这在实际中往往很难做到,而且如果不断有短进程进入就绪队列,可能会导致长进程饥饿。例如,在一个服务器系统中,如果不断有小的网络请求(可视为短作业)进入就绪队列,长的计算任务可能长时间得不到执行。为了解决这个问题,可采用动态SJF算法,即根据进程的执行历史动态调整其预计执行时间。
- 时间片轮转:在时间片轮转调度算法中,每个就绪进程被分配一个固定时间片(如100毫秒),当时间片用完时,无论进程是否执行完毕,都会被放回就绪队列末尾,等待下一次调度。这种算法的优点是能保证每个进程都能在一定时间内获得CPU执行,适用于交互式系统,用户感觉系统响应比较及时。缺点是如果时间片设置过小,会导致进程上下文切换频繁,增加系统开销;如果时间片设置过大,又会退化为FCFS算法,无法满足交互式系统的快速响应需求。在Linux系统中,内核通过
SCHED_RR
调度策略实现类似时间片轮转的调度,具体时间片长度可根据系统配置和需求进行调整。
优先级管理策略
- 静态优先级:在进程创建时就为其分配一个固定的优先级。优先级可以根据进程的类型(如系统进程优先级高于用户进程)、任务的紧急程度等因素确定。例如,在一个工业控制系统中,用于实时监控设备状态的进程优先级较高,而用于数据统计分析的进程优先级较低。静态优先级的优点是实现简单,缺点是缺乏灵活性,如果系统环境发生变化,固定的优先级可能无法满足实际需求。
- 动态优先级:进程的优先级可以根据运行情况动态调整。比如,随着进程等待时间的增加,逐渐提高其优先级,以防止进程饥饿;或者根据进程占用资源的情况,如CPU使用率、内存占用等,调整优先级。在Linux系统中,内核会根据进程的CPU使用情况等因素动态调整进程的优先级。具体来说,使用
nice
值来表示进程优先级,取值范围一般为 -20(最高优先级)到19(最低优先级),默认值为0。进程在运行过程中,内核会根据其CPU使用时间等因素动态调整nice
值,从而改变进程优先级。
就绪队列管理策略
- 队列结构选择:常见的就绪队列结构有简单的线性队列(如FIFO队列)、优先级队列等。线性队列适用于FCFS等调度算法,实现简单,易于理解。优先级队列则适用于基于优先级的调度算法,能够快速找到高优先级进程进行调度。在实际应用中,还可以采用多级队列结构,将不同类型的进程(如系统进程、实时进程、普通用户进程)分别放入不同的队列,每个队列采用不同的调度算法。例如,实时进程队列采用优先级调度算法,保证实时任务的及时执行;普通用户进程队列采用时间片轮转调度算法,提供公平的服务。
- 队列操作优化:为了提高就绪队列的管理效率,需要对队列的插入、删除和查找操作进行优化。在优先级队列中,可以采用堆数据结构来实现,堆的插入和删除操作时间复杂度为O(log n),能够快速调整队列顺序,找到最高优先级进程。对于线性队列,采用链表结构可以提高插入和删除操作的效率,避免数组结构在插入和删除元素时需要移动大量数据的问题。另外,在多处理器系统中,还可以采用分布式就绪队列,将进程分配到不同处理器对应的就绪队列中,减少队列竞争,提高调度效率。
上下文切换管理策略
- 保存与恢复进程状态:当从就绪队列中调度一个进程运行,或者进程从运行状态回到就绪状态时,都需要进行上下文切换。上下文切换时,操作系统需要保存当前进程的CPU寄存器状态、程序计数器(PC)的值等信息,以便下次该进程再次获得CPU时能够恢复到原来的执行状态。在x86架构的CPU中,通用寄存器(如EAX、EBX等)、段寄存器(如CS、DS等)以及程序计数器(EIP)的值都需要被保存。这些信息通常被保存在进程的PCB中。当调度新的进程运行时,操作系统从新进程的PCB中读取这些信息,恢复到CPU寄存器中,使得新进程能够从上次中断的地方继续执行。
- 减少上下文切换开销:上下文切换会带来一定的系统开销,包括CPU时间和内存访问时间等。为了减少上下文切换开销,可以采用一些优化策略。例如,采用线程池技术,将一些小任务预先创建好线程并放入线程池,任务到来时直接从线程池中获取线程执行,避免频繁的进程或线程创建和上下文切换。另外,在调度算法中,可以尽量减少不必要的上下文切换,如在时间片轮转调度算法中,合理设置时间片长度,避免时间片过短导致频繁上下文切换。同时,现代CPU提供了一些硬件机制来加速上下文切换,如TLB(Translation Lookaside Buffer)缓存,减少内存地址转换的时间,从而间接减少上下文切换开销。