在计算机的世界里,进程调度策略就如同一位高效的指挥家,精心安排着各个进程对系统资源的使用,确保整个计算机系统能够有条不紊地运行。当我们打开电脑、运行各种软件程序时,背后都有一套复杂而精妙的进程调度机制在默默工作。那么,究竟什么是进程调度策略?它又是如何影响我们日常使用计算机的体验呢?让我们一同深入探索进程调度策略的奥秘。
一、进程调度简介
进程调度是操作系统中非常重要的一部分,它决定了哪些进程能够获得CPU的使用权。进程调度策略可以分为三种:高级调度、中级调度和低级调度。高级调度(作业调度)决定哪些作业可以进入内存运行;中级调度(内存调度)决定哪些进程可以在内存和磁盘之间对换;低级调度(进程调度)决定哪个就绪进程可以获得CPU的使用权。
进程调度的基本状态:在进程的生命周期中,存在三种基本状态:就绪状态、执行状态和阻塞状态。就绪状态表示进程已经准备好运行,但需要等待CPU资源;执行状态表示进程正在使用CPU执行任务;阻塞状态表示进程因为等待某些事件(如I/O操作完成)而暂时无法执行。
进程调度是操作系统中一种管理进程执行顺序的机制,而调度策略则是实现这种机制的具体方法或算法。进程调度是操作系统中一种管理进程执行顺序的机制,负责动态地将CPU分配给各个进程。可以参考这篇文章☞《进程调度机制原理》。其主要功能包括:
-
记住进程状态。
-
决定哪个进程何时获取CPU及其占用多长时间。
-
把CPU分配给进程,即将选中进程的相关信息送入CPU的相应寄存器中,从而让该进程占用CPU去运行。
-
收回CPU。
调度策略则是实现进程调度的具体方法或算法,常见的调度策略包括:
-
先来先服务算法(FCFS):按照进程到达系统的顺序进行调度。
-
最短作业优先算法(SJF):优先调度运行时间最短的进程。
-
轮转算法(RR):将CPU时间分成若干个短时间段,按照进程到达的顺序,每个进程轮流使用一段时间的CPU。
-
优先级调度:根据进程的优先级进行调度,优先级高的进程优先执行。
-
多级队列调度和多级反馈队列调度:根据进程的性质(如CPU密集型或I/O密集型)将其分配到不同的队列中,以提高调度效率。
调度策略目标:
-
(1)进程响应时间尽可能快
-
(2)后台作业吞吐量尽可能高
-
(3)尽可能避免进程饥饿
-
(4)低优先级,高优先级进程的需要尽可能调和
-
…
调度策略:决定什么时候以怎样方式选择一个新进程运行。Linux的调度基于分时技术:多个进程以"时间多路复用"方式运行。CPU时间分成片,每个可运行进程分配一片。如当前运行进程的时间片或时限到期,进程还没结束,进程切换就可以发生。
分时以来定时中断。调度策略也是根据进程的优先级对它们进行分类。每个进程都与一个值关联,这个值表示把进程如何适当地分配给CPU。Linux中,进程的优先级是动态的。调度程序跟踪进程在做什么,周期性地调整它们的优先级。此方式下, 较长时间间隔内没使用CPU的进程,通过动态地增加它们的优先级来提升它们。对已经在CPU上运行了较长时间的进程,通过减少它们的优先级来处罚它们。谈及有关调度问题时,传统上把进程分类为"I/O受限"或"CPU受限"。
另一种分类把进程区分为三类:
-
(1)交互式进程:花比较多时间等待键盘和鼠标操作。如shell,文本编辑程序,图形应用程序。
-
(2)批处理进程:后台运行。如程序设计语言的编译程序,数据库搜索引擎及科学计算。
-
(3)实时进程绝不会被低优先级进程阻塞,应该有一个短的响应时间。
如视频和音频应用程序,机器人控制程序,从物理传感器上收集数据的程序。
Linux 2.6调度程序实现了基于进程过去行为的启发式算法,以确定进程应被当作交互式进程还是批处理进程:
-
nice:改变一个普通进程的静态优先级
-
getpriority:获得一组普通进程的最大静态优先级
-
setpriority:设置一组普通进程的静态优先级
-
sched_getscheduler:获得一个进程的调度策略
-
sched_setscheduler :设置一个进程的调度策略和实时优先级
-
sched_getparam:获得一个进程的实时优先级
-
sched_setparam:设置一个进程的实时优先级
-
sched_yield:自愿放弃处理器而不阻塞
-
sched_get_priority_min:获得一种策略的最小实时优先级
-
sched_get_priority_max:获得一种策略的最大实时优先级
-
sched_rr_get_interval:获得时间片轮转策略的时间片值
-
sched_setaffinity:设置进程的CPU亲和力掩码
-
sched_getaffinity:获得进程的CPU亲和力掩码
公平性是进程调度的基本原则之一。每个进程都应该有平等的机会获得 CPU 资源,不能因为某些进程的特殊性质而长期被忽视。例如,在轮转调度算法中,每个进程轮流获得固定时间片的 CPU 使用权,保证了公平性。高效性要求进程调度算法能够快速地选择下一个要执行的进程,减少不必要的等待时间和上下文切换开销。稳定性则意味着调度算法在各种负载情况下都能可靠地工作,不会因为系统负载的变化而出现异常情况。例如,多级反馈队列调度算法结合了多种调度策略,能够较好地适应不同类型的进程和系统负载,体现了稳定性。
二、常见调度算法
非抢占式进程调度策略在操作系统中起着重要作用,它不允许正在运行的进程被强制中断,只有当该进程主动放弃 CPU 资源或者运行结束时,才会进行调度。
普通进程的调度:每个普通进程有它自己的静态优先级,调度程序使用静态优先级来估价系统中这个进程和其他普通进程间调度的程度。内核用100(高)到139(低)的数表示普通进程的静态优先级。值越大静态优先级越低。新进程总是继承其父进程的静态优先级。通过把某些"nice"值传递给系统调用nice和setpriority,用户可改变自己拥有的进程的静态优先级。
2.1非抢占式进程调度策略
⑴先来先服务调度算法(FCFS)
先来先服务调度算法是一种简单直观的非抢占式调度算法。它按照进程到达的先后顺序进行调度,先到达的进程先获得 CPU 资源并一直运行直到完成或阻塞。
其优点是易于理解和实现,具有公平性,每个进程都按照到达的顺序依次执行。然而,FCFS 也存在明显的缺点。对于长作业来说,可能会导致短作业等待时间过长。例如,如果一个长作业先到达,即使后面有很多短作业,这些短作业也必须等待长作业完成后才能执行。这不利于短作业的快速响应,可能会影响系统的整体性能。此外,对 I/O 密集型进程也不利,因为这种进程每次进行 I/O 操作之后又得重新排队。
FCFS 算法可用于作业调度和进程调度。在作业调度中,系统按照作业进入后备作业队列的先后顺序选择作业进入内存,为其分配资源、创建进程并放入就绪队列。在进程调度中,每次从就绪队列中选择最先进入的进程,将处理机分配给它,使之投入运行,该进程一直运行到完成或因等待某事件发生而不能继续运行时才释放处理机。
FCFS 调度算法的原理是只考虑每个作业的到达时间,将所有作业的到达时间进行排序,然后依次进行作业。例如,有五个进程并发执行的模拟调度程序,每个程序由一个 PCB 表示,可使用 FCFS 算法进行调度。算法设计思路是将所有作业的到达时间进行排序,然后依次进行作业即可。
FCFS 算法具有公平、实现简单的优点,有利于长进程调度及 CPU 繁忙型进程,用于批处理系统。但它也存在一些缺点,对短作业不利,排在长作业后面的短作业需要等待很长时间,带权周转时间很大,用户体验不好。而且平均等待时间往往很长,例如有一组进程按不同顺序到达时,平均等待时间会有很大变化。另外,如果有一个 CPU 密集型进程和多个 I/O 密集型进程,可能会出现 CPU 密集型进程占用 CPU 时,所有 I/O 密集型进程在就绪队列中等待,而当 CPU 密集型进程完成后又回到就绪队列继续分配到 CPU,导致 I/O 密集型进程长时间等待的情况。
FCFS 策略可以通过 FIFO 队列容易地实现。当一个进程进入就绪队列时,它的 TCB 会被链接到队列尾部。当 CPU 空闲时,它会分配给位于队列头部的进程,并且这个运行进程从队列中移去。例如,在模拟 FCFS 调度算法中,可以使用节点模拟进程,创建一个节点为 n 的队列模拟就绪队列,通过输入到达时间和服务时间模拟进程到达和服务,最后出队列模拟完成所有进程工作。
★先来先服务算法在作业调度中的应用
先来先服务(FCFS)算法在作业调度中,系统按照作业进入后备作业队列的先后顺序选择作业进入内存。即先进入后备作业队列的作业被优先选择进入内存,然后为选中的作业创建进程并分配该作业所需资源。例如,假设有多个作业等待调度,作业 A 最先进入后备队列,那么系统会优先处理作业 A。无论作业 A 的执行时间长短如何,只要它是最先到达的,就会先得到处理。这种方式保证了公平性,每个作业都有机会被处理,按照先来后到的顺序依次进行。但是,如果先到达的作业执行时间很长,那么后面的短作业就需要等待很长时间。比如有一个大型的数据处理作业先到达,后面陆续来了一些小型的计算作业,这些小型作业就不得不等待大型作业完成后才能被处理,这可能导致整体的作业处理效率降低。
★先来先服务算法在进程调度中的应用
在进程调度中采用 FCFS 算法时,每次从内存的进程就绪队列中选择一个最先进入的进程,然后由进程调度程序将 CPU 分配给它并使其运行。一旦一个进程占有了处理机,它就一直运行下去,直到该进程完成工作或者因为等待某事件发生而不能继续运行时才释放处理机。例如,在一个多任务操作系统中,有多个进程等待执行。进程 P1 最先进入就绪队列,那么 P1 就会先获得 CPU 资源开始执行。如果 P1 是一个长时间运行的进程,那么后续进入就绪队列的进程就需要等待 P1 完成后才能获得 CPU。这种方式虽然简单公平,但是对于短进程来说可能不太友好,因为它们可能需要等待很长时间才能得到执行机会。而且如果长进程频繁出现,可能会导致系统的响应时间变长,用户体验下降。
★先来先服务算法的优点
先来先服务算法具有公平性和算法实现简单的优点。公平性体现在它严格按照作业或进程到达的先后顺序进行服务,每个作业或进程都有机会被处理,不会因为其他因素而被不公平地插队。例如在一个任务管理系统中,无论是大型任务还是小型任务,只要先来就会先被处理,不会因为任务的重要性或其他主观因素而改变处理顺序。这种公平性在一些对顺序有严格要求的场景中非常重要,比如一些任务的执行顺序必须按照特定的时间顺序进行。算法实现简单也是其优点之一,因为只需要按照到达的先后顺序进行处理,不需要复杂的计算和判断,降低了算法的复杂度和实现难度。对于一些资源有限、处理能力较弱的系统来说,这种简单的算法可以有效地减少系统的负担,提高系统的稳定性。
★先来先服务算法的缺点
先来先服务算法存在一些明显的缺点。首先,对长作业有利,对短作业不利。当长作业先到达时,短作业可能需要等待很长时间,带权周转时间很大,用户体验不好。例如在一个作业调度系统中,如果一个需要长时间运行的大型数据处理作业先到达,后面的一些小型计算作业可能需要等待很长时间才能被处理。这会导致短作业的响应时间变长,影响系统的整体效率。其次,可能会导致磁头反复移动,增加服务时间,对机械也不利。在磁盘调度中,如果采用先来先服务算法,当请求访问的磁道比较分散时,磁头需要频繁地移动到不同的磁道,这会增加寻道时间,降低磁盘的访问效率。而且频繁的磁头移动也会对磁盘的机械部件造成磨损,降低磁盘的使用寿命。此外,该算法在作业调度和进程调度中都可能导致系统的整体性能下降,难以胜任分时系统和实时系统的主要调度策略。
★如何实现先来先服务算法
实现先来先服务算法可以从以下几个方面入手。首先,需要建立一个数据结构来存储作业或进程的信息,比如可以使用队列来存储等待调度的作业或进程。当一个新的作业或进程到达时,将其加入到队列的末尾。在进行调度时,从队列的头部取出第一个作业或进程进行处理。其次,需要记录每个作业或进程的到达时间、开始时间、完成时间等信息,以便计算周转时间、带权周转时间等指标。在处理作业或进程时,按照先来先服务的原则,依次为每个作业或进程分配资源并执行。如果是在作业调度中,需要为作业创建进程并分配内存等资源;如果是在进程调度中,需要将 CPU 分配给进程并使其运行。在实现过程中,可以使用编程语言提供的数据结构和算法来简化实现过程。例如,在 C++ 中,可以使用标准模板库(STL)中的队列容器来实现作业或进程的队列;在 Java 中,可以使用集合框架中的队列接口和实现类来实现。同时,还需要考虑一些特殊情况,比如作业或进程的阻塞、中断等情况,需要及时处理这些情况,保证算法的正确性和稳定性。
⑵短作业优先调度算法(SJF)
短作业优先调度算法选择执行时间最短的作业优先执行。其优势在于能够最大程度地减少平均等待时间,提高系统的吞吐量。如果系统中有大量短作业,SJF 可以快速处理这些作业,提高系统的响应速度。
但是,SJF 也存在实现难点。首先,它需要准确地知道每个作业的执行时间,但在实际情况下,很难准确地估计作业的执行时间。其次,如果有一个长作业在队列中等待执行,那么其他短作业可能需要等待很长时间才能执行,这可能导致短作业的响应时间较长。此外,长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。
⑶优先级调度算法
优先级调度为每个进程分配一个优先级,优先级高的进程先执行。优先级可以根据进程的重要性、紧急程度等因素来确定。
然而,优先级调度可能出现饥饿现象。低优先级进程可能会因为高优先级进程不断到来而永远无法获得 CPU 资源,从而导致饥饿。为了解决这个问题,可以采用一些方法,如随着时间的推移增加等待进程的优先级,或者采用优先级继承等策略。当一个高优先级进程等待一个低优先级进程持有的资源时,低优先级进程将暂时获得高优先级进程的优先级别,在释放共享资源后,低优先级进程回到原来的优先级别。这样可以避免低优先级进程长时间占用资源而导致高优先级进程饥饿的情况。
2.2抢占式进程调度策略
Linux的进程是抢占式的。如进程进入TASK_RUNNING,内核检查它的动态优先级是否大于当前正运行进程的优先级。如是,current的执行被中断,且调度程序选择另一个进程运行。进程在它的时间片到期时也可被抢占。此时,当前进程thread_info中的TIF_NEED_RESCHED被设置,以便中断处理程序终止时调度程序被调用。
Linux 2,6内核是抢占式的,意味着进程无论是处于内核态还是用户态都可能被抢占。中断返回时处理的调度就是抢占。关闭核心抢占,就是中断返回时,即使有TIF_NEED_RESCHED被设置,也不执行调度。抢占式进程调度策略在操作系统中扮演着重要的角色,它能够极大地提高系统的响应速度和资源利用率。
一个时间片必须持续多长
如果太短,由进程切换引起的额外开销就变得非常高。如平均时间片太长,进程看起来就不再是并发执行。交互式进程相对有较高的优先级,因此,不管时间片多长,它们都会很快地抢占批处理进程。对时间片大小的选择始终是一种折衷。Linux采取单凭经验的方法,即选择尽可能长,同时保持良好响应时间的。
⑴时间片轮转调度算法(RR)
时间片轮转调度算法是一种常见的抢占式调度策略。在这种算法中,系统将所有的就绪进程按先来先服务的规则排成一个队列,将 CPU 分配给队首进程,且规定每个进程最多允许运行一个时间片,通常时间片为 10 - 100 毫秒。如果时间片用完进程还没有结束,则被加入就绪队列队尾,并把 CPU 交给下一个进程。
时间片长度的选择对系统性能有着重要影响。如果时间片长度很小,比如只有几毫秒,那么调度程序剥夺处理机的次数就会频繁,这会加重系统开销。因为频繁的上下文切换会消耗大量的时间和资源。另一方面,如果时间片长度选择过长,比如一个时间片就能保证就绪队列中所有进程都执行完毕,那么时间片轮转调度算法就退化成先来先服务算法,并且会增大进程响应时间。一般来说,时间片的长度需要根据系统响应时间、就绪进程数目和计算机处理能力等因素来确定。通常建议时间片长度在 20ms - 50ms 之间。
⑵其他抢占式调度算法
除了时间片轮转调度算法,还有一些其他的抢占式调度算法。比如最短剩余时间优先算法(SRTN),每当有进程加入就绪队列改变时就需要调度,如果新到达的进程剩余时间比当前运行的进程剩余时间更短,则由新进程抢占处理机,当前运行进程重新回到就绪队列。另外,当一个进程完成时也需要调度。这种算法能够最大程度地减少平均等待时间和平均周转时间,但实现起来相对复杂,需要不断地计算进程的剩余时间。
还有抢占式优先级调度算法,每个进程都被赋予一个优先级,具有较高优先级的进程将被优先选择执行,而具有较低优先级的进程将被推迟执行。如果具有更高优先级的进程进入调度队列,它可以抢占正在执行的进程,即使该进程尚未完成。这种算法能够根据进程的重要性和紧急程度进行调度,但也可能导致低优先级进程饥饿的问题。
三、多处理器系统中的调度
多处理器系统为现代计算带来了强大的计算能力和更高的系统性能,但也带来了独特的调度挑战。
3.1非对称多处理与对称多处理
⑴对称多处理(SMP)
对称多处理系统的特点是系统中的所有处理器单元在功能和结构上完全相同,共享所有的系统资源。例如,绝大多数现代多处理机系统属于此类,如基于 PowerPC 处理器的 IBM RS/6000 Model F50。这种架构简化了系统的设计和管理,所有处理器能够平等地访问内存和执行任务,提高了系统的效率和扩展性。在对称多处理系统中,操作系统负责管理所有资源,确保各个处理器协同工作。例如,当多个进程同时运行时,操作系统可以根据处理器的负载情况动态地将任务分配给不同的处理器,以实现负载均衡。根据搜索结果,在一个双处理器和单处理器系统里,多处理的每个处理器处理速度是单处理的一半。使用 FCFS 调度、轮转法和最短剩余时间法后进行比较,得到进程服务时间的对比,即一个进程的生命周期里使用处理器的时间。轮转法的时间片长度比上下文环境切换的开销大,且比平均服务时间短。对于双处理器,不同调度算法的差距没有单处理表现的大,当处理器增多时该结论更加确定。因此多处理器系统中,采用简单的 FCFS 原则,或在静态优先级方案中使用 FCFS 就已足够。
⑵非对称多处理(AMP)
非对称多处理器系统中包含多种类型的处理单元,功能和结构不同。通常只有一个主处理器负责管理,其余为从处理器,专门执行某些任务。比如在特定的应用场景中,如特殊的硬件加速或专用的处理任务。这种架构下,资源分配通常是由主处理器来管理的。主处理器根据从机处理器的需求和能力来分配任务和资源,从而实现资源的最优利用。在任务分配方面,通常也是由主处理器来决定的。主处理器可以根据从机处理器的负载情况和能力来动态地分配任务,以实现负载均衡和性能优化。
3.2处理器亲和性与负载平衡
⑴处理器亲和性的好处
设置进程的处理器亲和性可以带来多方面的好处。首先,在多核处理器中,每个 CPU 核心通常都有自己的一级缓存(L1 Cache)和二级缓存(L2 Cache),而较大容量的三级缓存(L3 Cache)则是共享的。将一个进程绑定到某个 CPU 核心可以提高缓存命中率,减少缓存缺失,从而加速程序的执行。其次,能够减少资源争用。在多核系统中,如果多个进程或线程同时运行在不同的 CPU 核心上,它们可能会竞争共享的资源(如共享缓存、内存总线等)。再者,控制线程间的干扰。当一个进程内包含多个线程时,这些线程可能会相互干扰,导致性能下降。通过将关键线程绑定到独立的 CPU 核心上,可以减少线程间的竞争和干扰,提升应用程序的响应性能。最后,对于实时性要求高的场景,通过设置 CPU 亲和性,可以确保关键任务在预定的时间内得到处理,避免被其他进程或线程的执行所干扰。
⑵负载平衡的方法及挑战
在多处理器系统中,负载平衡是提高系统性能的关键。常见的负载平衡方法有多种。例如基于轮询的负载均衡算法,顺序地将任务分配给不同的处理器,实现简单,开销小,但可能导致负载不均衡,影响系统性能。加权循环负载均衡则与循环负载均衡类似,但允许为不同的处理器分配不同的权重,可以确保各个处理器的工作负载均衡,但需要更多的配置。最小连接负载均衡将工作负载分配给具有最少连接数的处理器,能确保各个处理器的工作负载均衡,但可能会导致某些处理器空闲,而其他处理器繁忙。最短作业优先负载均衡将工作负载分配给估计完成时间最短的处理器,可以提高系统吞吐量,但需要更多的配置。动态负载均衡根据系统的当前状态动态地调整负载均衡策略,可以提供最佳的性能,但需要更多的配置和管理。
负载平衡也面临着一些挑战。首先是任务分配策略的选择,不同的任务分配策略有不同的优缺点,需要根据具体系统情况进行选择。其次,多处理器系统中,系统负载可能会动态变化,这给负载均衡技术带来了挑战。需要设计能够动态调整任务分配的负载均衡技术,以适应动态变化的系统负载情况。最后,处理器性能异构性也是一个挑战,多处理器系统中,处理器的性能可能不同,这需要设计能够考虑处理器性能差异的负载均衡技术,以实现处理器负载均衡。
四、Linux和Windows的调度策略
4.1Linux 调度策略
Linux 的完全公平调度程序(CFS)是默认的调度算法。Linux 系统的调度基于调度类,每个类都有一个特定的优先级。内核针对不同的调度类,采用不同的调度算法,以便满足系统与进程的需要。
CFS 并不采用严格规则来为一个优先级分配某个长度的时间片,而是为每个任务分配一定比例的 CPU 处理时间。例如,根据搜索结果,CFS 通过红黑树数据结构管理进程,确保调度操作的复杂度为 O (log N),其中 N 是系统中可调度的进程数量。每个可运行的任务放置在红黑树上,按虚拟运行时间(vruntime)排序。较小的 vruntime 意味着进程运行时间较短,需要获得更多的 CPU 时间。当一个任务变得可运行时,它被添加到树上;当一个任务变得不可运行时,它从树上删除。
Linux 标准内核实现了两个调度类:采用 CFS 调度算法的默认调度类和实时调度类。实时调度类适用于对响应时间要求极高的任务,如实时音频和视频处理等。在实时调度类中,有 SCHED_FIFO 和 SCHED_RR 两种调度策略。SCHED_FIFO 是先进先出的调度策略,高优先级的任务会一直运行,直到被更高优先级的任务抢占或主动放弃 CPU。SCHED_RR 是时间片轮转的调度策略,同优先级的任务会按照时间片轮流执行。
在 Linux 中,常用的进程调度算法有完全公平调度算法(CFS)等。
-
案例:假设在一个服务器环境中,运行着多个不同优先级的服务进程。高优先级的进程可能是关键业务服务,需要尽快得到响应;低优先级的进程可能是一些后台任务,如数据备份等。
-
调度特点:CFS 试图确保每个进程都能公平地获得 CPU 时间,同时根据进程的优先级进行适当调整。高优先级进程会获得更多的 CPU 时间份额。例如,当系统负载较高时,高优先级的服务进程会更频繁地被调度执行,以保证关键业务的响应时间。
Linux 环境下伪代码示例(模拟进程调度):
# 假设定义了进程类
class Process:
def __init__(self, name, priority):
self.name = name
self.priority = priority
# 模拟进程队列
process_queue = [
Process("HighPriorityProcess", 2),
Process("LowPriorityProcess", 1)
]
def schedule_linux():
while process_queue:
# 根据优先级排序
process_queue.sort(key=lambda p: p.priority, reverse=True)
current_process = process_queue.pop(0)
print(f"Scheduling {current_process.name}")
# 模拟执行一段时间
#...
4.2Windows 调度策略
Windows 采用基于优先级的、抢占调度算法来调度线程。Windows 调度程序确保具有最高优先级的线程总是在运行的。用于处理调度的 Windows 内核部分称为调度程序。调度程序采用 32 级的优先级方案,以便确定线程执行方案。
在 Windows 系统中,当一个新的线程进入就绪状态时,调度程序会将其优先级与正在运行的线程的优先级进行比较。如果新线程的优先级更高,那么正在运行的线程会被抢占,新线程开始执行。这种抢占式的调度算法能够确保关键任务能够及时得到处理,提高系统的响应速度。
然而,这种调度算法也可能导致一些问题。例如,如果高优先级的线程不断出现,低优先级的线程可能会长期得不到执行,从而出现饥饿现象。为了解决这个问题,Windows 系统引入了一些机制,如优先级提升和老化。优先级提升机制可以在特定情况下提高某些线程的优先级,而老化机制则可以随着时间的推移逐渐提高长时间等待的低优先级线程的优先级。
在 Windows 中,采用基于优先级的抢占式调度。
-
案例:在一个图形设计软件的使用场景中,用户交互进程(如响应鼠标和键盘事件的进程)需要及时响应,而一些长时间运行的渲染进程可以在后台慢慢执行。
-
调度特点:Windows 根据进程的优先级类别(如实时、高、高于正常、正常、低于正常、低等)和线程的优先级级别来决定哪个进程或线程先获得 CPU 时间。用户交互进程通常被赋予较高的优先级,以确保良好的用户体验。
Windows 环境下伪代码示例(模拟进程调度):
class WindowsProcess:
def __init__(self, name, priority_level):
self.name = name
self.priority_level = priority_level
# 模拟进程队列
windows_process_queue = [
WindowsProcess("UserInteractionProcess", "High"),
WindowsProcess("RenderingProcess", "Normal")
]
def schedule_windows():
while windows_process_queue:
# 根据优先级选择进程
if "High" in [p.priority_level for p in windows_process_queue]:
current_process = [p for p in windows_process_queue if p.priority_level == "High"][0]
else:
current_process = windows_process_queue[0]
print(f"Scheduling {current_process.name}")
# 模拟执行一段时间
#...
五、进程调度策略总结
进程调度策略在操作系统中起着至关重要的作用,不同的调度策略各有其优缺点,需要根据实际情况进行选择和优化。
先来先服务调度算法(FCFS)简单公平,但可能导致短作业等待时间过长,对 I/O 密集型进程不利。短作业优先调度算法(SJF)能最大程度减少平均等待时间,但实现困难且可能导致长作业饥饿。优先级调度算法灵活但可能出现饥饿现象。时间片轮转调度算法(RR)公平性高、响应时间快,但时间片长度选择不当会影响系统性能。最短剩余时间优先算法(SRTN)能减少平均等待时间和周转时间,但实现复杂。抢占式优先级调度算法能根据重要性和紧急程度调度,但也可能导致低优先级进程饥饿。
在多处理器系统中,对称多处理(SMP)系统所有处理器功能和结构相同,共享资源,操作系统负责管理资源实现负载均衡;非对称多处理(AMP)系统中主处理器管理从处理器,根据需求和能力分配任务和资源。处理器亲和性可以提高缓存命中率、减少资源争用、控制线程间干扰和满足实时性要求。负载平衡方法有轮询、加权循环、最小连接、最短作业优先和动态负载均衡等,但面临任务分配策略选择、系统负载动态变化和处理器性能异构性等挑战。
Linux 的完全公平调度程序(CFS)通过红黑树管理进程,按虚拟运行时间排序分配 CPU 时间,有默认调度类和实时调度类。Windows 采用基于优先级的抢占调度算法,可能导致低优先级线程饥饿,通过优先级提升和老化机制解决。