是否可以在调度程序的帮助下或在运行时以某种方式更改硬件计时器的时间?

is it possible to change the hardware timer's time with the help of the scheduler or somehow during runtime?

我正在学习操作系统的入门课程,我正在尝试开发一个调度程序,它可以在进程的周转时间和响应时间之间进行良好的权衡。

我想知道我是否可以更改定时器中断的间隔。如果我能做到这一点,也许我可以根据在给定的更大时间间隔内完成的进程数在不同的计时器间隔之间切换。例如,如果我在最后 10 毫秒内完成了 7 个进程,将计时器间隔从 10 毫秒切换到 15 毫秒,因此,在响应式调度程序与最小化周转时间的调度程序之间切换,从而获得更好的两者平均值。

谢谢。

让我们把它分成多个部分..

计时器

很多现代内核都是"tickless"。这意味着(而不是让定时器以固定频率生成 IRQ)timer/s 在 "one shot" 模式下运行(当请求的时间过去时生成一个 IRQ)并且内核确定下一次何时事件需要发生并设置时间以在下一次事件发生时生成 IRQ。这种方法有利于电源管理(没有不必要的 IRQ 碰撞,否则空闲 CPU/s 超出节能状态)和精度的好处(例如,如果定时器恰好 运行 关闭 1 MHz 时钟,那么内核可以向定时器请求粒度为 1 微秒的 IRQ,而不会产生以固定频率获得相同粒度所需的 100 万个 IRQ 的开销)。当然,计时器有很多用途(网络超时、sleep() 等),因此 "soonest time event" 可能与当前任务耗尽其时间片无关。

注意:现代硬件也可能在 CPU 的频率上有 timer/s 运行ning(例如本地 APIC 计时器中的 "TSC deadline mode"最近的 80x86 芯片)其中 "tickless" 意味着您通常可以获得 "better than 1 nanosecond" 精度。

当然是 "tickless",因为每次 IRQ 发生时,内核都会为定时器设置一个新的计数(对于新的 "soonest" 时间事件),调度程序可以非常容易地使用任何无论出于何种原因,它喜欢的时间片长度。

任务切换原因

对于真正的OS,大多数任务切换是因为当前运行ning任务必须等待一些东西(等待来自文件的数据或交换space,等待对于来自网络的数据包,等待缓冲区变为 "not full" 以便它可以将更多数据放入其中,等待数据通过管道或消息从另一个进程到达,等待释放互斥量等);或者因为任务正在等待的事情发生了。调度程序的计时器仅用于为恶意 CPU 猪和不需要等待任何东西的罕见 CPU 绑定任务提供上限。

请注意(对于使用固定频率计时器的循环调度程序之类的东西)这会导致问题。例如,如果给每个任务一个 10 毫秒的时间片,但由于任务必须等待某事,所以必须在 4.321 毫秒后发生任务切换,那么你不想浪费 5.679 毫秒的 CPU 时间等待下一个定时器 IRQ,但您也不想给下一个任务 5.679 毫秒而不是它应该得到的 10 毫秒。相反,您可以将定时器配置为每 1 毫秒生成一个 IRQ,以便下一个任务的时间可以四舍五入到最接近的时间(例如,给下一个任务 9.679 毫秒而不是 5.679 毫秒而不是 10 毫秒)。然而,这增加了处理不必要的 IRQ 的开销并损害了性能,这也是使用 "tickless" 的原因之一(为了摆脱不必要的 IRQ 并减少 "round to nearest" 错误)。

循环延迟

对于循环调度程序"latency under load"是一个残废的笑话,这与计时器无关。问题是需要低延迟的任务必须等待未知数量的其他任务轮到它们。例如,如果有 200 个任务每个任务最多需要 10 毫秒的 CPU 时间,而这 200 个任务中的每一个实际上每个任务平均消耗 5 毫秒的 CPU 时间(因为大多数 block/wait 在他们的时间片结束之前做某事);那么 "low latency" 任务可能需要等待最多 1000 毫秒才能获得 CPU 时间。将计时器的间隔从 10 毫秒更改为 5 毫秒可能意味着这 200 个任务平均每个消耗 4 毫秒的 CPU 时间,而 "low latency" 任务仍然需要等待 800 毫秒。

注:理论上;避免此问题的一种方法是将最近停止等待的任务放在 "which task gets CPU time next" 列表的开头,以便在当前 运行ning 任务完成后立即获得 CPU 时间使用 CPU。实际上,这允许任务通过在每个时间片结束时极短地等待以快速获得一个全新的时间片来故意占用 CPU 时间,并且通过使用一对协作任务,恶意软件可以确保没有其他任务永远得到任何 CPU 时间。因此,您永远不会将任务放在 "which task gets CPU time next" 列表的开头。

任务优先级

如果您为每个任务分配优先级,以便调度程序知道任务的重要性,会怎么样?

在这种情况下,如果任务需要低延迟,您可以告诉调度程序它具有高优先级,并且当任务停止等待某事时(例如等待用户按键,等待网络数据包到达) , etc) 调度器可以将任务的优先级与当前 运行ning 任务的优先级进行比较,发现 "low latency" 任务具有更高的优先级,并立即进行任务切换(无需等待 200 个其他任务轮到他们了)。在这种情况下,延迟可以是 "almost zero",即使 OS 负载很重。

这确实会产生一个潜在的问题(高优先级 CPU 猪),但是这个问题用其他方式解决是微不足道的(例如,如果一个高优先级任务花费太多 CPU 时间而不做任何可能导致其 block/wait 的情况,要么降低其优先级,要么将其终止为 "unresponsive")。

如果您查看现代操作系统中的调度程序,您会发现 none 其中 none 本身使用循环法。通常 "round robin" 仅在 2 个或更多任务具有相同优先级时使用(但这并不是一个好主意,我将在下一节中告诉您原因)。此外,大多数操作系统都提供多种调度策略(例如,OS 可能会提供使用 "earliest deadline first" 的软实时调度策略,以及使用 "highest priority wins" 的 "low latency" 调度策略],加上可能使用 "priority determines how many time slices" 的 "general use" 策略,再加上可能使用 "priority determines size of time slice" 的 "idle task" 策略);调度程序为具有最佳策略的任务提供 CPU 时间(例如,如果有一个实时任务需要 CPU,那么甚至不会考虑空闲任务)并且每个策略都有不同的规则.

平均作业完成时间

假设只有一个 CPU;一个任务想要处理一个网络数据包并发送一个响应,这个处理将花费 50 毫秒的 CPU 时间;同时,另一个进程想要处理用户的按键,所涉及的处理也将花费 50 毫秒。如果调度程序每 1 毫秒在任务之间切换一次,那么第一个任务将在 99 毫秒后完成,第二个任务将在 100 毫秒后完成,并且任务完成其工作所需的平均时间将为 99.5 毫秒。但是,如果调度程序在任何时间后都没有在任务之间切换(例如,只让当前 运行ning 任务 运行 直到它完成它的工作并等待下一个工作来做)然后第一个任务将在 50 毫秒后完成,第二个任务将在 100 毫秒后完成,任务完成其工作所需的平均时间为 75 毫秒(而不是 99.5 毫秒)。

换句话说,对于事件驱动的任务(例如发生某事并且任务 运行s 在处理它时,然后等待下一个事件),循环调度程序(以固定频率切换任务) 可以使平均完成时间比大多数其他调度算法(例如 "earliest start time first"、"highest priority wins"、...)差近 50%。

结论

a) 是的,完全可以调整时间片长度,可以使用无滴答设计,也可以对固定频率定时器设计的 "IRQs per time slice" 使用不同的值。

b) 对于循环调度程序,调整时间片长度对延迟没有多大帮助(尤其是在负载下)。

c) 入门课程是一种介绍 - 它们旨在提供(在未来的某个时候)将帮助您学习更高级概念的信息(而不是旨在提供使用的信息 "as is" 在实践中)。循环调度是经常放在介绍中的内容之一,部分原因是它们很简单,但部分原因是它们自然会导致 discussion/s 为什么它们很糟糕以及如何避免这些问题(这是开始最先进的概念)。