操作系统-进程调度

工作负载

确定工作负载是构建调度策略的关键部分。工作负载了解得越多,你的策略就越优化。
我们对操作系统中运行的进程(有时也叫工作任务)做出如下的假设:
1.每一个工作运行相同的时间。
2.所有的工作同时到达。
3.一旦开始,每个工作保持运行直到完成。
4.所有的工作只是用 CPU(即它们不执行 IO 操作)。
5.每个工作的运行时间是已知的。

调度的指标

调度指标。指标是我们用来衡量某些东西的东西,在进程调度中,有一些不同的指标是有意义的。只用一个指标:周转时间(turnaround time)。任务的周转时间定义为任务完成时间减去任务到达系统的时间。更正式的周转时间定义 T 周转时间是:
T 周转时间= T 完成时间−T 到达时间
因为我们假设所有的任务在同一时间到达,那么 T 到达时间= 0,因此 T 周转时间= T 完成时间。

调度算法

先进先出(FIFO)

先进先出(First In First Out 或 FIFO)调度,有时候也称为先到先服务(First Come First Served 或 FCFS)。
1:看一个简单的例子。想象一下,3 个工作 A、B 和 C 在大致相同的时间(T 到达时间 = 0)到达系统。因为 FIFO 必须将某个工作放在前面,所以我们假设当它们都同时到达时,A 比 B 早一点点,然后 B 比 C 早到达一点点。假设每个工作运行 10s。这些工作的平均周转时间是多少?

A 在 10s 时完成,B 在 20s 时完成,C 在 30s 时完成。因此,这 3个任务的平均周转时间就是(10 + 20 + 30)/ 3 = 20。计算周转时间就这么简单。

2:再次假设 3 个任务(A、B 和 C),但这次 A 运行100s,而 B 和 C 运行 10s。如果A先到达A 先运行 100s,B 或 C 才有机会运行。因此,系统的平均周转时间是比较高的:令人不快的 110s((100 + 110 + 120)/ 3 = 110)。

最短任务优先(SJF)

先运行最短的任务,然后是次短的任务,如此下去。

我们用上面的例子,但以 SJF 作为调度策略。仅通过在 A 之前运行B 和 C,SJF 将平均周转时间从 110s 降低到 50s((10 + 20 + 120)/3 = 50)。考虑到所有工作同时到达的假设,我们可以证明 SJF 确实是一个最优(optimal)调度算法。请添加图片描述
我们可以针对假设 2,现在假设工作可以随时到达,而不是同时到达。这导致了什么问题?
假设 A 在 t = 0 时到达,且需要运行 100s。而 B 和 C 在 t = 10 到达,且各需要运行 10s。用纯 SJF如下图。
请添加图片描述
即使 B 和 C 在 A 之后不久到达,它们仍然被迫等到 A 完成,从而遭遇同样的护航问题。这 3 项工作的平均周转时间为 103.33s,即(100+(110−10)+(120−10))/3。

最短完成时间优先(STCF)

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

向 SJF 添加抢占,称为最短完成时间优先(Shortest Time-to-Completion First,STCF)或抢占式最短作业优先(Preemptive Shortest Job First ,PSJF)调度程序。每当新工作进入系统时,它就会确定剩余工作和新工作中,谁的剩余时间最少,然后调度该工作。

因此,在我们的例子中,STCF 将抢占 A 并运行 B 和 C 以完成。只有在它们完成后,才能调度 A 的剩余时间。请添加图片描述

响应时间

如果我们知道任务长度,而且任务只使用 CPU,而我们唯一的衡量是周转时间,STCF 将是一个很好的策略。。事实上,对于许多早期批处理系统,这些类型的调度算法有一定的意义。然而,引入分时系统改变了这一切。要求系统的交互性好。
新的度量标准诞生了:响应时间(response time)。响应时间定义为从任务到达系统到首次运行的时间。更正式的定义是: T 响应时间= T 首次运行−T 到达时间
例如,如果我们有上面的调度(A 在时间 0 到达,B 和 C 在时间 10 达到),每个作业的响应时间如下:作业 A 为 0,B 为 0,C 为 10(平均:3.33)。
STCF 和相关方法在响应时间上并不是很好。例如,如果 3 个工作同时到达,第三个工作必须等待前两个工作全部运行后才能运行。这种方法虽然有很好的周转时间,但对于响应时间和交互性是相当糟糕的。 例如:你在终端前输入,不得不等待10s才能看到系统的回应。

轮转 RR

基本思想很简单:RR 在一个时间片(time slice,有时称为调度量子,scheduling quantum)内运行一个工作,然后切换到运行队列中的下一个任务,而不是运行一个任务直到结束。它反复执行,直到所有任务完成。
假设 3 个任务 A、B 和 C 在系统中同时到达,并且它们都希望运行 5s。SJF 调度程序必须运行完当前任务才可运行下一个任务相比之下左侧图,1s 时间片的 RR 可以快要地循环工作 右侧图
请添加图片描述
RR 的平均响应时间是:(0 + 1 + 2)/3 = 1; SJF 算法平均响应时间是:(0 + 5 + 10)/ 3 = 5。

时间片长度对于 RR 是至关重要的。越短,RR 在响应时间上表现越好。然而时间片太短是有问题的:突然上下文切换的成本将影响整体性能。因此需要失去足够长,上下文切换的成本,而又不会使系统不及时响应。
因为周转时间只关心作业何时完成,RR 几乎是最差的,在很多情况下甚至比简单的 FIFO 更差。任何公平(fair)的政策(如 RR),即在小规模的时间内将 CPU 均匀分配到活动进程之间,在周转时间这类指标上表现不佳。事实上,这是固有的权衡:如果你愿意不公平,你可以运行较短的工作直到完成,但是要以响应时间为代价。如果你重视公平性,则响应时间会较短,但会以周转时间为代价。

第一种类型(SJF、STCF)优化周转时间,但对响应时间不利。第二种类型(RR)优化响应时间,但对周转时间不利。


假设 4(作业没有 I/O)和假设 5(每个作业的运行时间是已知的)。
结合IO:
假设 4:当然所有程序都执行 I/O。没有任何输入的程序:每次都会产生相同的输出。设想一个没有输出的程序:没有人看到它。它的运行并不重要。

调度程序显然要在工作发起 I/O 请求时做出决定,因为当前正在运行的作业在 I/O 期间不会使用 CPU,它被阻塞等待 I/O 完成。如果将 I/O 发送到硬盘驱动器,则进程可能会被阻塞几毫秒或更长时间,具体取决于驱动器当前的 I/O 负载。调度程序还必须在 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。通过将每个 CPU 突发作为一项工作,调度程序确保“交互”的进程经常运行。

简单总结:第一类是运行最短的工作,从而优化周转时间。第二类是交替运行所有工作,从而优化响应时间。如何将 I/O 结合到场景中,但仍未解决操作系统根本无法看到未来的问题。

多级反馈队列

多级反馈队列需要解决两方面的问题。首先,它要优化周转时间。通过先执行短工作来实现。然而,操作系统通常不知道工作要运行多久,而这又是SJF(或 STCF)等算法所必需的。MLFQ 希望给交互用户很好的交互体验,因此需要降低响应时间。然而,像轮转这样的算法虽然降低了响应时间,周转时间却很差。

MLFQ:基本规则

  • 规则 1:如果 A 的优先级 > B 的优先级,运行 A(不运行 B)。
  • 规则 2:如果 A 的优先级 = B 的优先级,轮转运行 A 和 B。
  • 规则 3:工作进入系统时,放在最高优先级(最上层队列)。
  • 规则 4:一旦工作用完了其在某一层中的时间配额(无论中间主动放弃了多少次CPU),就降低其优先级(移入低一级队列)。
  • 规则 5:经过一段时间 S,就将系统中所有工作重新加入最高优先级队列。

MLFQ 中有许多独立的队列(queue),每个队列有不同的优先级(priority level)。任何时刻,一个工作只能存在于一个队列中。MLFQ 总是优先执行较高优先级的工作。每个队列中可能会有多个工作,因此具有同样的优先级。

MLFQ 没有为每个工作指定不变的优先而已根据观察到的行为调整它的优先级。如果一个工作不断放弃CPU 去等待键盘输入,这是交互型进程的可能行为,MLFQ 因此会让它保持高优先级。如果一个工作长时间地占用 CPU,MLFQ 会降低其优先级。

如何改变优先级:就是上述的规则3 4. 假设一个工作200ms,3级队列:该工作首先进入最高优先级(Q2)。执行一个 10ms 的时间片后,调度程序将工作的优先级减 1,因此进入 Q1。在 Q1 执行一个时间片后,最终降低优先级进入系统的最低优先级(Q0),一直留在那里。

如何提升优先级:简单的思路是周期性地提升所有工作的优先级。将所有工作扔到最高优先级队列。(规则5),那么S 怎么设置?如果 S 设置得太高,长工作会饥饿;如果设置得太低,交互型工作又得不到合适的 CPU 时间比例。

MLFQ的其他问题

如何配置一个调度程序,例如,配置多少队列?每一层队列的时间片配置多大?为了避免饥饿问题以及进程行为改变,应该多久提升一次进程的优先级?
大多数的 MLFQ 变体都支持不同队列可变的时间片长度。高优先级队列通常只有较短的时间片(比如 10ms 或者更少),因而这一层的交互工作可以更快地切换。相反,低优先级队列中更多的是 CPU 密集型工作,配置更长的时间片会取得更好的效果。

总结:多级反馈队列(MLFQ)。—它有多级队列,并利用反馈信息决定某个工作的优先级。关注进程
的一贯表现,然后区别对待。
它不需要对工作的运行方式有先验知识,而是通过观察工作的运行来给出对应的优先级。通过这种方式,MLFQ 可以同时满足各种工作的需求:对于短时间运行的交互型工作,获得类似于 SJF/STCF 的很好的全局性能,同时对长时间运行的CPU 密集型负载也可以公平地、不断地稳步向前。

多级处理器调度

必须先知道它与单 CPU 之间的基本区别。区别的核心在于对硬件缓存(cache)的使用以及多处理器之间共享数据的方式。
请添加图片描述
在单 CPU 系统中,存在多级的硬件缓存,一般来说会让处理器更快地执行程序。缓存是很小但很快的存储设备,通常拥有内存中最热的数据的备份。相比之下,内存很大且拥有所有的数据,但访问速度较慢。通过将频繁访问的数据放在缓存中,系统似乎拥有又大又快的内存。

假设一个程序需要从内存中加载指令并读取一个值,CPU1个,较小的缓存和较大的内存。
程序第一次读取数据时,数据在内存中,因此需要花费较长的时间处理器判断该数据很可能会被再次使用,因此将其放入 CPU 缓存中。如果之后程序再次需要使用同样的数据,CPU 会先查找缓存。因为在缓存中找到了数据,所以取数据快得多(比如几纳秒),程序也就运行更快。

缓存是基于局部性的概念,局部性有两种,即时间局部性和空间局部性。时间局部性是指当一个数据被访问后,它很有可能会在不久的将来被再次访问,比如循环代码中的数据或指令本身。而空间局部性指的是,当程序访问地址为 x 的数据时,很有可能会紧接着访问 x 周围的数据,比如遍历数组或指令的顺序执行。
如果系统有多个处理器,并共享同一个内存如上图右侧。

多 CPU 的情况下缓存要复杂得多。假设一个运行在 CPU 1 上的程序从内存地址 A 读取数据。由于不在 CPU 1 的缓存中,所以系统直接访问内存,得到值 D。程序然后修改了地址 A 处的值,只是将它的缓存更新为新值 D’。将数据写回内存比较慢,因此系统(通常)会稍后再做。假设这时操作系统中断了该程序的运行,并将其交给 CPU 2,重新读取地址 A 的数据,由于 CPU 2 的缓存中并没有该数据,所以会直接从内存中读取,得到了旧值 D,而不是正确的值 D’。

这一普遍的问题称为缓存一致性问题。
硬件提供了这个问题的基本解决方案:通过监控内存访问,硬件可以保证获得正确的数据,并保证共享内存的唯一性。在基于总线的系统中,一种方式是使用总线窥探每个缓存都通过监听链接所有缓存和内存的总线,来发现内存访问。如果 CPU 发现对它放在缓存中的数据的更新,会作废本地副本(从缓存中移除),或更新它(修改为新值)。

跨 CPU 访问共享数据或数据结构时,需要使用互斥原语才能保证正确性其他方法偶尔才使用。

单队列调度

单队列多处理器调度 SQMS 它不需要太多修改,就可以将原有的策略用于多个 CPU,选择最适合的工作来运行.
短板:第一个是缺乏可扩展性。为了保证在多CPU 上正常运行,调度程序的开发者需要在代码中通过加锁(locking)来保证原子性。在 SQMS 访问单个队列时(如寻找下一个运行的工作),锁确保得到正确的结果。锁可能带来巨大的性能损失,尤其是随着系统中的 CPU 数增加时.随着这种单个锁的争用增加,系统花费了越来越多的时间在锁的开销上,较少的时间用于系统应该完成的工作。
SQMS 的第二个主要问题是缓存亲和性。尽可能让进程在同一个 CPU 上运行。保持一些工作的亲和度的同时,可能需要牺牲其他工作的亲和度来实现负载均衡。

多队列调度

每个 CPU一个队列。我们称之为多队列多处理器调度 MQMS
MQMS 中,基本调度框架包含多个调度队列,每个队列可以使用不同的调度规则,比如轮转或其他任何可能的算法。

假设系统中有两个 CPU(CPU 0 和 CPU 1)。这时一些工作进入系统:A、B、C 和 D。由于每个 CPU 都有自己的调度队列,操作系统需要决定每个工作放入哪个队列。根据不同队列的调度策略,每个 CPU 从两个工作中选择,决定谁将运行。

MQMS 比 SQMS 有明显的优势,它天生更具有可扩展性。队列的数量会随着 CPU 的增加而增加,因此锁和缓存争用的开销不是大问题。此外,MQMS 天生具有良好的缓存亲和度。所有工作都保持在固定的 CPU 上,因而可以很好地利用缓存数据。

负载不均 :假设4个任务2个CPU,ABCD,假设C指向完了,然后 Q0->A Q1-> BD 那么就是CPU0 ->A CPU1->B D A也完成的话 那么 CPU0 -> CPU1->BD

解决是:工作窃取! 工作量较少的(源)队列不定期地“偷看”其他(目标)队列是不是比自己的工作多。如果目标队列比源队列(显著地)更满,就从目标队列“窃取”一个或多个工作,实现负载均衡。

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值