操作系统(二)—— 进程管理(3):进程调度

操作系统系列内容的学习目录 → \rightarrow 操作系统学习系列内容汇总

3. 进程调度

   CPU调度是多道程序操作系统的基础。通过在进程间切换CPU,操作系统可以使得计算机更加高效。

3.1 基本概念

  • 对于单处理器系统,同一时间只有一个进程可以运行,其他进程都应等待,直到CPU空闲并可调度为止。
  • 多道程序的目的是始终允许某个进程运行以最大化CPU利用率。
  • 当一个进程等待时,操作系统就从该进程接管CPU控制,并将CPU交给另一个进程执行。
  • 操作系统选择进程,并交给CPU执行。

3.1.1 CPU、I/O执行周期

  • 进程执行包括周期进行CPU执行IO等待。进程在这两个状态之间不断交替。
  • 举一个CPU执行和IO执行交替执行的例子。

    在这里插入图片描述
  • IO密集型进程 & CPU密集型进程

3.1.2 CPU调度程序

  • 每当CPU空闲时,操作系统就应该从就绪队列中选择一个进程来执行。
  • 进程选择采用短期调度程序CPU调度程序。调度程序从内存中选择一个能够执行的进程,并为其分配CPU。
  • 就绪队列中的所有进程都要排队以便等待在CPU上运行。队列内的记录通常为进程控制块PCB

3.1.3 抢占调度

  • 需要进行CPU调度的情况可分为以下4种:
      1. 当一个进程从运行状态切换到等待状态(如IO请求)
      2. 当一个进程从运行状态切换到就绪状态(如出现中断)
      3. 当一个进程从等待状态切换到就绪状态
      4. 当一个进程终止时

  • 如果调度只能发生在第1种和第4种情况下,则调度方案称为非抢占的,否则,调度方案称为抢占的

  • 非抢占调度下,一旦某个进程分配到CPU,该进程就会一直使用CPU,直到它终止或切换到等待状态。

3.1.4 调度程序

  • 调度程序是一个模块,用来将CPU控制权交给由短期调度程序选择的进程。这个功能包括:
    - 切换上下文
    - 切换到用户模式
    - 跳转到用户程序的合适位置,以便重启程序。

  • 调度程序应尽可能快,因为在每次进程切换时都要使用。调度程序停止一个进程而启动另一个所需的时间称为调度延迟

3.2 调度准则

  不同的调度算法具有不同的特点,选择一个特定的算法会对某些进程更为有利。

  为了比较CPU调度算法,可以采用许多比较准则。选择哪些特征来比较,对于确定哪种算法是最好的有本质上的区别。这些准则包括:
    - CPU使用率: 应使CPU尽可能忙碌。CPU使用率0%~100%,范围应从40%(轻负荷系统)到90%(重负荷系统)。
    - 吞吐量: 单位时间内进程完成的数量。
    - 周转时间: 从进程提交到进程完成的时间段称为周转时间。周转时间为所有时间段之和,包括等待进程内存、在就绪队列等待、在CPU上执行、IO执行等。
    - 等待时间: 在就绪队列中等待所花时间之和。
    - 响应时间: 从提交请求到产生第一响应的时间。

  总结: 最大化CPU使用率和吞吐量。
      最小化周转时间、等待时间、和响应时间。

3.3 调度算法

  CPU调度处理的问题: 从就绪队列中选择进程以便为其分配CPU。那么按照怎样的策略或方式来选择进程,就是调度算法要处理的事情?

3.3.1 先到先服务调度

  • 最简单的CPU调度算法是先到先服务(First-Come First-Served,FCFS)调度算法。采用这种方案,先请求CPU的进程首先分配到CPU。可以通过FIFO队列容易地实现。
  • 当一个进程进入就绪队列时,它的PCB会被链接到队列尾部。当CPU空闲时,它会分配给位于队列头部的进程,并且这个运行进程从队列中移除。
  • 优点: 算法实现非常、特别、超级简单。
    缺点: 平均等待时间往往很长。

在这里插入图片描述
  FCFS调度算法是非抢占的。一旦CPU分配给了一个进程,该进程就会一直使用CPU直到释放CPU。

3.3.2 最短作业优先调度

  • 最短作业优先(Shortest-Job-First,SJF)调度算法将每个进程与其下次CPU执行的长度关联起来。当CPU空闲时,它会被赋给具有最短CPU执行的进程。
  • SJF算法也叫也叫最短下次CPU执行算法,因为调度取决于进程的下次CPU执行的长度,而不是其总的长度。

在这里插入图片描述

  • 优点: SJF算法是最优的,因为平均等待时间最小(即CPU更忙碌)。短进程等待时间减少,长进程等待时间增加,从而平均等待时间减少。
  • 问题:如何知道下次CPU执行的长度?
    - 对于批处理系统的长期调度,可以将用户提交作业时指定的进程时限作为长度。SJF调度经常用于长期调度。
    - SJF算法不能在短期CPU调度级别上加以实现,因为没有办法知道下次CPU执行的长度。一种方法是试图近似SJF调度,即预测下次CPU执行长度,可以认为下次CPU执行长度与以前的相似。
  • SJF算法可以是抢占式或非抢占式的。当一个新进程到达就绪队列而以前进程正在执行时,就需要选择了。新进程的下次CPU执行,与当前运行进程的尚未完成的CPU执行相比,可能还要小。
  • 抢占SJF算法会抢占当前运行进程,而非抢占SJF算法会允许当前运行进程先完成CPU执行。抢占SJF调度有时称为最短剩余时间优先调度

在这里插入图片描述

3.3.3 优先级调度

  • 每个进程都有一个优先级与其关联,而具有最高优先级的进程会分配到CPU。具有相同优先级的进程按FCFS顺序调度。
  • SJF算法是优先级调度算法的一个特例。其优先级为下次CPU执行的倒数。CPU执行越长,则优先级越小。

在这里插入图片描述

  • 优先级调度可以是抢占的或非抢占的
  • 优先级调度算法的一个主要问题是无穷阻塞或饥饿问题。
  • 低优先级进程的饥饿问题的解决方案之一是老化。老化逐渐增加在系统中等待很长时间的进程的优先级。

3.3.4 轮转调度

  • 轮转(Round-Robin,RR)调度算法是专门为分时系统设计的。它类似于FCFS调度,但增加了抢占以切换进程。
  • 将一个较小时间单元(通常10ms~100ms)定义为时间片。就绪队列作为循环队列,CPU调度程序循环整个就绪队列,为每个进程分配不超过一个时间片的CPU执行。
  • 时间片到,中断操作系统,进行上下文切换。
  • RR调度的平均等待时间通常较长。

在这里插入图片描述

  • RR算法中,没有进程被连续分配超过一个时间片的CPU。如果进程的CPU执行超过一个时间片,那么该进程会被抢占,并被放回到就绪队列。因此,RR调度算法是抢占的
  • RR算法的性能很大程度取决于时间片的大小。如果时间片很大,那么RR算法与FCFS算法一样,如果时间片很小,会导致频繁的上下文切换。
  • 更小时间片会增加上下文切换。

    在这里插入图片描述

3.3.5 多级队列调度

  • 多级队列调度算法将就绪队列分成多个单独队列

    在这里插入图片描述
  • 根据进程属性,如内存大小,进程优先级、进程类型等,一个进程永久分到一个队列。每个队列有自己的调度算法。
  • 每个队列与更低层队列相比有绝对的优先。
  • 优点: 优先级高的进程优先执行。
    缺点: 可能存在饥饿问题。

3.3.6 多级反馈队列调度

  • 使用多级队列算法时,进程进入系统时被永久分配到某个队列。而多级反馈队列调度算法允许进程在队列之间迁移
  • 这种算法根据不同的CPU执行的特点来区分进程。如果进程使用过多的CPU时间,那么它会被移到更低的优先级队列。
  • 这种方案将IO密集型和交互进程放在更高优先级队列上。在较低优先级队列中等待过长的进程会被移到更高优先级队列,阻止饥饿的发生。

在这里插入图片描述

3.4 多处理器调度

  之前讨论的主要是单处理器系统的CPU调度问题。如果多个CPU,则负载分配成为可能,但是调度问题更为复杂。

3.4.1 多处理器调度的方法

  • 对于多处理器系统,CPU调度的一种方法是让一个处理器(主服务器)处理所有调度决定、IO处理以及其他系统活动,其他的处理器只执行用户代码。这种非对称多处理很简单,因为只有一个处理器访问系统数据结构,减少了数据共享的需要。
  • 第二种方法多对称处理(Symmetric MultiProcessing,SMP),即每个处理器自我调度。所有进程可能处于一个共同的就绪队列中,或处理器都有它自己的私有就绪进程队列。调度这样进行:每个处理器的调度程序都检查共同就绪队列,以便选择执行一个进程。

3.4.2 处理器亲和性

  • 当一个进程运行在一个特定处理器上时缓存会发生什么。 进程最近访问的数据更新了处理器的缓存。进程的后续内存访问通常通过缓存来满足。
  • 如果进程移到其他处理器上则会发生什么。 第一个处理器缓存的内容变为无效,第二个处理器缓存重新填充。大多数系统试图避免将进程从一个处理器移到另一个处理器,而是试图让一个进程运行在同一个处理器上。这称为处理器亲和性,即一个进程对它运行的处理器具有亲和性。
  • 处理器的亲和性具有多种形式。当一个操作系统试图保持进程运行在同一处理器上时(但不保证它会这么做,这个进程也可迁移到其他处理器上),这种情况称为软亲和性
  • 有的SMP系统提供系统调用以便支持硬亲和性,从而允许某个进程运行在某个处理器子集上。
  • 许多系统提供软亲和性和硬亲和性。

3.4.3 负载平衡

  • 对于SMP系统,重要的是保持所有处理器的负载平衡,以便充分利用多处理器的优点。否则一个或多个处理器会空闲,而其他处理器会处于高负载状态,且有一系列进程处于等待状态。
  • 负载平衡设法将负载平均分配到SMP系统的所有处理器。
  • 负载平衡通常有两种方法:推迁移拉迁移
    - 对于推迁移,一个特定的任务周期性地检查每个处理器的负载,如果发现不平衡,那么通过将进程从超载处理器推到(push)空闲或不太忙的处理器,从而平均分配负载。
    - 当空闲处理器从一个忙的处理器上拉(pull)一个等待任务时,发生拉迁移
    - 推迁移和拉迁移不必相互排斥,事实上,在负载平衡系统中它们常被并行实现。
  • 负载平衡往往会抵消处理器亲和性的好处。

3.5 实时CPU调度

  一般来说,我们可以区分软实时系统硬实时系统
  软实时系统不保证调度关键实时进程,而只保证这类进程会优先于非关键进程。
  硬实时系统有更严格的要求。一个任务应在它的截止期限之前完成,在截止期限之后完成,与没有完成,是完全一样的。

3.5.1 最小化延迟

  • 从事件发生到事件得到服务的这段时间称为事件延迟。(时间延迟如下图所示。)
    在这里插入图片描述
  • 两种类型的延时影响实时系统的性能:中断延时调度延时
    - 中断延时是从CPU收到中断到中断处理程序开始的时间。当一个中断发生时,操作系统应先完成正在执行的指令,再确定发生中断的类型。然后,应保存当前进程的状态,再采用特定的中断服务程序(Interrupt Service Routine ,ISR)来处理中断。执行这些任务需要的总时间为中断延迟
      对于实时操作系统至关重要的是:尽量减少中断延迟,以确保实时任务得到立即处理。(中断延迟如下图所示。)

    在这里插入图片描述
    - 调度程序从停止一个进程到启动另一个进程所需的时间量称为调度延迟。实时操作系统应最大限度的减少这种延迟。保持调度延迟尽可能低的最有效技术是:提供抢占式内核
      下图说明了调度延迟的组成部分。调度延迟的冲突部分有两部分:1.抢占在内核中运行的任何进程;2.释放高优先级进程所需的、低优先级进程占有的资源。(调度延迟如下图所示。)

    在这里插入图片描述

3.5.2 优先权调度

  • 实时操作系统最重要的功能是:当一个实时进程需要CPU时,立即响应。因此,用于实时操作系统的调度程序应支持抢占的、基于优先权的算法。
    - 基于优先级的调度算法根据每个进程的重要性而分配优先级。进程越重要,它分配的优先级也越高。
    - 如果调度程序还支持抢占,并且有一个更高优先级的进程处于就绪,那么正在运行的、低优先级的进程被抢占。
    - 提供抢占的、基于优先级的调度程序仅保证软实时功能
    - 硬实时系统应进一步保证实时任务应在截止期限内得到服务,做出这样的保证需要附加的调度特征
  • 在讨论各个调度程序的细节之前,我们应当分析需要调度进程的一些特性
    - 首先这些进程是周期性的。也就是说,它们定期需要CPU。一旦周期性进程获得CPU,它具有固定的处理时间t、CPU应处理的截止期限d和周期p。
    - 周期任务的速率为1/p。
    - 处理时间t、截止期限d和周期p之间的关系为:0 <= t <= d <= p。
  • 下图描述了一个周期性进程随时间的执行情况,调度程序可以利用这些特性,根据进程的截止期限速率要求来分配优先级。
    在这里插入图片描述
  • 调度程序做两件事之一:它承认进程,保证进程完成;如果它不能保证任务能在截止期限之前得到服务,拒绝请求。

3.5.3 单调速率调度

  • 单调速率(rate-monotonic) 调度算法采用抢占的、静态优先级的策略, 调度周期性任务。
  • 当较低优先级的进程正在运行并且较高优先级的进程可以运行时,较高优先级进程将会抢占低优先级。
  • 在进入系统时,每个周期性任务会分配一个优先级,它与其周期成反比。周期越短,优先级越高;周期越长,优先级越低。
  • 这种策略背后的理由是:更频繁地需要CPU的任务应分配更高的优先级。此外, 单调速率调度假定:对于每次CPU执行,周期性进程的处理时间是相同的。也就是说, 在每次进程获取CPU时, 它的CPU执行长度是相同的。
  • 单调速率调度可认为是最优的,因为如果一组进程不能由此算法调度,它不能由任何其他分配静态优先级的算法来调度。
  • 尽管是最优的, 然而单调速率调度有一个限制:CPU的利用率是有限的, 并不总是可能完全最大化CPU资源。调度N个进程的最坏情况下的CPU利用率为: N ( 2 1 / N − 1 ) N(2^{1/N}-1) N(21/N1)
  • 对于具有一个进程的系统, CPU利用率是100%; 但是当进程数量接近无穷时, 它大约接近69%。对于具有两个进程的系统, CPU利用率是83%。

3.5.4 最早截止期限优先调度

  • 最早截止期限优先(Earliest-Deadline-First, EDF) 调度根据截止期限动态分配优先级。截止期限越早, 优先级越高; 截止期限越晚, 优先级越低。
  • 根据EDF策略, 当一个进程可运行时,它应向系统公布截止期限要求。优先级可能需要进行调整,以便反映新可运行进程的截止期限。
  • 注意单调速率调度与EDF调度的不同, 前者的优先级是固定的。
  • 与单调速率调度不一样, EDF调度不要求进程应是周期的, 也不要求进程的CPU执行的长度是固定的唯一的要求是:进程在变成可运行时, 应宣布它的截止期限。
  • EDF调度具有吸引力的地方是:它是理论上最佳的。从理论上说,它可以调度进程,使得每个进程都可以满足截止期限的要求并且CPU利用率将会是100%。然而, 在实际中, 由于进程的上下文切换和中断处理的代价, 这种级别的CPU利用率是不可能的。

3.5.5 比例分享调度

  • 比例分享(proportional share)调度程序在所有应用之间分配T股。如果一个应用程序接收N股的时间,那么确保了它将有N/T的总的处理器时间。例如,假设总的T=100股要在三个进程A、B和C之间进行分配。A分配50股,B分配15股,而C分配20股。
  • 这种方案确保:A有50%的总的处理器时间,B有15%,C有20%。比例分享调度程序应采用准入控制策略,以便确保每个进程能够得到分配时间。准入控制策略是:只有客户请求的股数小于可用的股数,才能允许客户进入。对于本例,我们现在只有100-(50+15+20)=15股可用。如果一个新进程D请求30股,那么准入控制器会推绝D进入系统。
  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,进程调度算法是操作系统中非常重要的一部分。常见的进程调度算法有先来先服务(FCFS)、短作业优先(SJF)、高响应比优先(HRRN)、时间片轮转(RR)等。下面是这些算法的 Python 实现: 1. 先来先服务(FCFS): ```python def FCFS(processes): waiting_time = 0 turn_around_time = 0 completion_time = 0 for process in processes: completion_time += process['burst_time'] turn_around_time += completion_time - process['arrival_time'] waiting_time += turn_around_time - process['burst_time'] return waiting_time / len(processes), turn_around_time / len(processes) ``` 2. 短作业优先(SJF): ```python def SJF(processes): processes = sorted(processes, key=lambda x: x['burst_time']) waiting_time = 0 turn_around_time = 0 completion_time = 0 for process in processes: completion_time += process['burst_time'] turn_around_time += completion_time - process['arrival_time'] waiting_time += turn_around_time - process['burst_time'] return waiting_time / len(processes), turn_around_time / len(processes) ``` 3. 高响应比优先(HRRN): ```python def HRRN(processes): waiting_time = 0 turn_around_time = 0 completion_time = 0 for i, process in enumerate(processes): if i == 0: completion_time = process['burst_time'] else: response_ratio_list = [] for j in range(i): response_ratio = (completion_time - processes[j]['arrival_time'] + processes[j]['burst_time']) / processes[j]['burst_time'] response_ratio_list.append(response_ratio) max_response_ratio_index = response_ratio_list.index(max(response_ratio_list)) selected_process = processes.pop(max_response_ratio_index) completion_time += selected_process['burst_time'] turn_around_time += completion_time - selected_process['arrival_time'] waiting_time += turn_around_time - selected_process['burst_time'] return waiting_time / len(processes), turn_around_time / len(processes) ``` 4. 时间片轮转(RR): ```python def RR(processes, time_slice): waiting_time = 0 turn_around_time = 0 completion_time = 0 while processes: for i in range(len(processes)): if processes[i]['burst_time'] > time_slice: completion_time += time_slice processes[i]['burst_time'] -= time_slice else: completion_time += processes[i]['burst_time'] turn_around_time += completion_time - processes[i]['arrival_time'] waiting_time += turn_around_time - processes[i]['burst_time'] processes.pop(i) break return waiting_time / len(processes), turn_around_time / len(processes) ``` 这里的 `processes` 是一个列表,其中每个元素是一个字典,表示一个进程的信息,如下所示: ```python processes = [ {'name': 'P1', 'arrival_time': 0, 'burst_time': 8}, {'name': 'P2', 'arrival_time': 1, 'burst_time': 4}, {'name': 'P3', 'arrival_time': 2, 'burst_time': 9}, ... ] ``` 在这个列表中,每个进程有一个名称、到达时间和执行时间。你可以根据自己的需要修改这些信息,来测试这些进程调度算法的实现。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值