zoukankan      html  css  js  c++  java
  • 《操作系统导论》第7章 | 进程调度

    探讨可能的调度策略之前,我们先做一些简化假设。这些假设与系统中运行的进程有关,有时候统称为工作负载(workload)。确定工作负载是构建调度策略的关键部分。这里做的工作负载假设是不切实际的,但我们会逐渐放宽这些假定,并最终开发出一个完全可操作的调度准则。

    我们对操作系统中运行的进程(有时也叫工作任务)做出如下的假设:

    1. 每一个工作运行相同的时间
    2. 所有的工作同时到达
    3. 一旦开始,每个工作保持运行直到完成
    4. 所有的工作只使用CPU(即没有I/O操作)
    5. 每个工作的运行时间是已知的

    评价指标

    除了做出工作负载假设之外,还需要一个东西能让我们比较不同的调度策略:调度指标。任务的周转时间(T_{turnaround})定义为任务完成时间(T_{completion})减去任务到达系统的时间(T_{arrival})。更正式的周转时间定义为:

    [T_{turnaround} = T_{completion} - T_{arrival} ]

    注意到,周转时间是一个性能指标,而另一个有趣的指标是公平。性能和公平在调度系统中往往是矛盾的。例如,调度程序可以优化性能,但代价是以阻止一些任务运行,这就降低了公平。

    先进先出(FIFO)

    最基本的算法被称为先进先出(First In First Out)调度,有时候也称为先到先服务(First Come First Served)。它很简单,易于实现,而且对于目前的假设效果不错。

    假设3个工作A、B和C在大致相同的时间到达系统。因为FIFO必须将某个工作放在前面,所以我们假设当它们都同时到达时,A比B早一点点,然后B比C早到达一点点。假设每个工作运行10s,从图上可以看出,A在10s时完成,B在20s时完成,C在30s时完成。因此,这3个任务的平均周转时间就是((10 + 20 + 30)/ 3 = 20s)

    现在,我们放宽假设1,每个任务的运行时间不再相同。这种情况下FIFO的表现如何?具体来说,我们再次假设3个任务(A、B和C),但这次A运行100s,而B和C运行10s。

    如图所示,A先运行100s,B或C才有机会运行。因此,系统的平均周转时间是比较高的((100 + 110 + 120)/ 3 = 110s)。这个问题通常被称为护航效应(convoy effect),一些耗时较少的潜在资源消费者被排在重量级的资源消费者之后。

    最短任务优先(SJF)

    事实上,我们一个非常简单的方法来解决护航问题。这个新的调度准则被称为最短任务优先(Shortest Job First,SJF),即先运行最短的任务,然后是次短的任务,以此类推。

    还是上面的例子,但这次以SJF作为调度策略。上图展示的是运行A、B和C的结果。通过在A之前运行B和C,SJF将平均周转时间从110s降低到((10 + 20 + 120)/3 = 50s)。在所有工作同时到达的情况下,可以证明SJF是一个最有调度算法。

    现在,让我们放宽假设2,即工作可以随时到达,而不是同时到达。假设A在t=0时到达,且需要运行100s。而B和C在t=10时到达,且各需要运行10s。使用SJF,可以得到如下所示的调度。

    从图中可以看出,即使B和C在A之后不久到达,它们仍然被迫等到A完成,从而遭遇同样的护航问题。这3项工作的平均周转时间为((100+(110−10)+(120−10))/3=103.33s)

    最短完成时间优先(STCF)

    为了解决这个问题,需要放宽假设3(工作必须保持运行直到完成)。也就是说,当B和C到达时,调度程序可以做其他事情:它可以抢占(preempt)工作A,并决定运行另一个工作,或许稍后继续工作A。根据我们的定义,SJF是一种非抢占式(non-preemptive)调度程序,因此还是存在护航问题。

    通过向SJF添加抢占,我们可以得到最短完成时间优先(Shortest Time-to-Completion First,STCF)调度程序。每当新工作进入系统时,它就会确定剩余工作和新工作中,谁的剩余时间最少,然后调度该工作。因此,在我们的例子中,STCF将抢占A并运行B和C以完成。只有在它们完成后,才能调度A的剩余时间。

    根据上图,3项工作的平均周转时间是(((20-10)+(30-10)+120) / 3 = 50s)。基于新的假设,可证明STCF是最优的。

    响应时间

    如果我们知道任务长度,任务只使用CPU,且唯一的衡量是周转时间,那么STCF将是一个很好的策略。然而,引入分时系统改变了这一切。现在,用户将会坐在终端前面,同时也要求系统的交互性好。因此,一个新的度量标准诞生了:响应时间(response time),它定义为任务到达系统到首次运行该任务的时间:

    [T_{response} = T_{firsturn} - T_{arrival} ]

    显然,STCF和相关方法在响应时间上并不是很好。如果3个工作同时到达,第三个工作必须等待前两个工作全部运行后才能运行。这种方法虽然有很好的周转时间,但对于响应时间和交互性是相当糟糕的。

    轮转(RR)

    为了提高响应时间,我们将引入轮转(Round-Robin,RR)调度。其基本思想很简单:在一个时间片内运行一个工作,然后切换到运行队列中的下一个任务,而不是运行一个任务直到结束,它反复执行,直到所有任务完成。注意,时间片长度必须是时钟中断周期的倍数。因此,如果时钟中断是每10ms中断一次,则时间片可以是10ms、20ms或10ms的任何其他倍数。

    假设3个任务A、B和C在系统中同时到达,并且它们都希望运行5s。如下所示,SJF调度程序必须运行完当前任务才可运行下一个任务,其平均响应时间是((0+5+10)/3 = 5s)

    相比之下,1s时间片的RR可以快速地循环工作,其平均响应时间为((0+1+2)/3=1s)

    显然,时间片长度对于RR是至关重要的。时间片越短,RR在响应时间上表现越好。然而,时间片太短是有问题的:突然上下文切换的成本将影响整体性能。因此,系统设计者需要权衡时间片的长度,使其足够长,以便摊销上下文切换成本,而又不会使系统不及时响应。

    结合I/O

    接下来我们放宽假设4,现在所有的程序都会执行I/O。调度程序显然要在工作发起I/O请求时做出决定,因为当前正在运行的作业在I/O期间不会使用CPU,它被阻塞等待I/O完成。如果将I/O发送到硬盘驱动器,则进程可能会被阻塞几毫秒或更长时间。因此,这时调度程序应该在CPU上安排另一项工作。调度程序还必须在I/O完成时做出决定。发生这种情况时,会产生中断,操作系统运行并将发出I/O的进程从阻塞状态移回就绪状态。当然,它甚至可以决定在那个时候运行该项工作。操作系统应该如何处理每项工作?

    假设有两项工作A和B,每项工作需要50ms的CPU时间。但是A先运行10ms,然后发出I/O请求(假设I/O每个都需要10ms),而B只是使用CPU 50ms,不执行I/O。调度程序先运行A,然后运行B。

    毫无疑问,上图所示的调度是非常糟糕的。常见的方法是将A的每个10ms的子工作视为一项独立的工作。因此,当系统启动时,它的选择是调度10ms的A,还是50ms的B。STCF会选择较短的A。然后,A的工作已完成,只剩下B,并开始运行。然后提交A的一个新子工作,它抢占B并运行10ms。这样做可以实现重叠,一个进程在等待另一个进程的I/O完成时使用CPU,系统因此得到更好的利用。

    有了应对I/O的基本方法,我们来到最后的假设5:调度程序知道每个工作的长度。如前所述,这可能是可以做出的最糟糕的假设。事实上,操作系统通常不知道每个作业的长度。因此,我们将在第8章利用最近的历史预测未来,从而解决这个问题。

  • 相关阅读:
    LeetCode 769. Max Chunks To Make Sorted
    LeetCode 845. Longest Mountain in Array
    LeetCode 1059. All Paths from Source Lead to Destination
    1129. Shortest Path with Alternating Colors
    LeetCode 785. Is Graph Bipartite?
    LeetCode 802. Find Eventual Safe States
    LeetCode 1043. Partition Array for Maximum Sum
    LeetCode 841. Keys and Rooms
    LeetCode 1061. Lexicographically Smallest Equivalent String
    LeetCode 1102. Path With Maximum Minimum Value
  • 原文地址:https://www.cnblogs.com/littleorange/p/12740241.html
Copyright © 2011-2022 走看看