调度器68—EEVDF调度器-1-内核文档翻译
一、sched-eevdf.rst
翻译自 Linux-6.13.6 Documentation/scheduler/sched-eevdf.rst
===============
EEVDF调度器
===============
“最早符合条件的虚拟截止日期优先”(EEVDF)于1995年在一份科学出版物中首次提出[1]。Linux内核在版本6.6中开始过渡到EEVDF(作为2024年的新选项),从早期的完全公平调度器(CFS)转向Peter Zijlstra在2023年提出的EEVDF版本[2-4]。有关CFS的更多信息,请参阅Documentation/scheduler/sched-design-CFS.rst。
与CFS类似,EEVDF旨在将CPU时间平均分配给具有相同优先级的所有可运行任务。为此,它为每个任务分配一个虚拟运行时间,创建一个"lag(滞后)"值,可用于确定任务是否已获得其公平的CPU时间份额。这样,具有正滞后的任务就被欠了CPU时间,而负滞后则意味着任务已经超过了它的部分。EEVDF选择延迟大于或等于零的任务,并为每个任务计算虚拟截止日期(VD),选择具有最早VD的任务进行下一步执行。值得注意的是,这允许对时间片较短的延迟敏感任务进行优先级排序,这有助于提高其响应速度。
目前正在讨论如何管理滞后(lag),特别是睡眠任务;但在撰写本文时,EEVDF使用基于虚拟运行时(VRT)的“衰减”机制。这可以防止任务通过短暂休眠来重置其负滞后来利用系统:当任务休眠时,它仍留在运行队列中,但标记为“延迟出列(deferred dequeue)”,从而允许其滞后在VRT期间衰减。因此,长时间睡眠任务最终会重置其延迟。最后,如果任务的VD更早,任务可以抢占其他任务,并且任务可以使用新的 sched_setattr() 系统调用请求特定的时间片,这进一步促进了对延迟敏感的应用程序的工作。
参考:
==========
[1] https://citeseerx.ist.psu.edu/document?repid=rep1&type=pdf&doi=805acf7726282721504c8f00575d91ebfd750564 //论文
[2] https://lore.kernel.org/lkml/a79014e6-ea83-b316-1e12-2ae056bda6fa@linux.vnet.ibm.com/
TODO: 看这里面提到的微基准测试 schbench hackbench stress-ng unixbench netperf STREAM lmbench
[3] https://lwn.net/Articles/969062/ //TODO
[4] https://lwn.net/Articles/925371/ //TODO
二、参考[3]: https://lwn.net/Articles/969062/
最早虚拟截止日期优先(EEVDF)调度程序被合并为6.6内核的一个选项。它代表了Linux系统上CPU调度方式的重大变化,但自那以后,EEVDF方面一直相对平静。不过,现在调度程序开发人员 Peter Zijlstra 已经从长期缺席中回来,发布了一系列旨在完成EEVDF工作的补丁。除了一些修复之外,这项工作还包括一项重大的行为变化和一项旨在帮助延迟敏感任务的新功能。
1. EEVDF快速回顾
EEVDF调度器用于在系统中所有可运行的任务之间平均分配可用CPU时间(假设所有任务具有相同的优先级)。如果四个同等优先级的任务争夺CPU,每个任务将获得25%的份额。每个任务都分配了一个虚拟运行时间,表示其分配的CPU份额;在四任务示例中,虚拟运行时间可以看作是以挂钟速度的25%运行的时钟。当任务运行时,内核计算任务的虚拟运行时间与其实际运行时间之间的差异;结果被称为 "lag(滞后)"。正滞后值表示任务被欠CPU时间,而负值表示任务收到的CPU时间超过其份额。
如果任务的滞后值为零或更大,则该任务被视为“合格”;每当CPU调度程序必须选择要运行的任务时,它都会从一组符合条件的任务中进行选择。对于这些任务中的每一个,虚拟截止日期是通过将其时间片中剩余的时间加上其符合条件的时间来计算的。具有最早虚拟截止日期的任务将被选择运行。由于时间片较长会导致虚拟截止日期较晚,因此时间片较短的任务(通常对延迟敏感)往往会先运行。
一个例子可能有助于可视化滞后是如何工作的。想象一下,三个CPU受限的任务,分别称为A、B和C,它们都是同时启动的。在它们中的任何一个运行之前,它们都会有零的滞后:
A B C
Lag: 0ms 0ms 0ms
由于所有任务都没有负滞后,因此所有任务都符合条件。如果调度器选择A以30ms(选择一个数字)的时间片首先运行,如果A运行到时间片耗尽,则滞后情况将如下:
A B C
Lag: -20ms 10ms 10ms
在这30毫秒内,每个任务有权占用10毫秒(占总数的三分之一)的CPU时间。A实际上得到了30ms,因此累积了-20ms的滞后;另外两个根本没有CPU时间的任务最终出现了10ms的延迟,这反映了它们应该收到的10ms的CPU时间。
任务A不再符合条件,因此调度程序接下来必须选择其他任务之一。如果给B(并使用)30ms的时间片,则情况变为:
A B C
Lag: -10ms -10ms 20ms
再一次,每个任务都获得了与它有权获得的CPU时间相对应的10ms延迟,而B实际运行时消耗了30ms。现在只有C符合条件,所以调度器的下一个决定很容易。
从上表可以看出,EEVDF调度器的一个特性是系统中所有滞后值的总和始终为零。
2. 滞后和睡眠
滞后计算仅适用于可运行的任务;一个休眠一天的任务实际上并没有错过它的虚拟运行时间(因为它没有),因此它不会积累巨大的滞后值。然而,当任务进入睡眠状态时,调度器确实会保留任务的当前滞后值,并在任务唤醒时从该值开始。因此,如果一个任务在睡眠前超出了其分配范围,它将在稍后醒来时为此付出代价。
不过,确实有一点,保持任务的滞后性可能没有意义。刚刚睡了一天的任务真的应该因为昨天被允许超出分配而受到惩罚吗?很明显,任务的滞后迟早会恢复到零。但何时发生尚不完全清楚。正如 Zijlstra 在该系列的这个补丁中指出的那样,在睡眠时立即忘记滞后将使任务有可能在时间片结束时短暂睡眠(当它们的滞后可能为负时),从而获得超过其CPU时间份额的时间。他总结道,随着时间的推移,简单地衰减滞后值也不会很好,因为滞后与虚拟运行时间有关,虚拟运行时间以不同的(和变化的)速率传递。
解决方案是在虚拟运行时间内衰减睡眠任务的延迟(lag)。在补丁集中实现这个想法有点有趣。当任务休眠时,它通常会从运行队列中删除,这样调度程序就不需要考虑它。使用新补丁,一个进入休眠状态的不合格(上次跑超了的)进程将留在队列中,但标记为“延迟出队列(deferred dequeue)”。由于它不符合条件,因此不会选择执行它,但它的延迟将根据经过的虚拟运行时间而增加。一旦延迟变为正值,调度程序将注意到该任务并将其从运行队列中删除。
这种实现的结果是,短暂睡眠的任务将无法逃脱负滞后值,但长时间睡眠的任务最终将免除其滞后债务。有趣的是,正滞后值会无限期保留,直到任务再次运行。
3. 时间片控制
如上所述,时间片较短的任务将具有较早的虚拟截止日期,导致调度器更快地选择它们。但是,在当前的内核中,只有当调度器正在寻找要运行的新任务时,这种隐式优先级才会生效。如果具有短时间片的延迟敏感任务被唤醒,它可能仍需等待当前任务耗尽其时间片(可能很长)才能运行。不过,Zijlstra 的补丁系列改变了这一点,允许一个虚拟截止日期更早的任务抢占另一个任务。这一变化为短时间片任务提供了更一致的时间安排,同时可能会略微减缓长时间运行的任务。
然而,这留下了一个悬而未决的问题:如何指定给定的任务应该被给予一个较短的时间片?在当前的内核中,非实时进程无法告诉内核它的时间片应该是什么,因此这个补丁系列增加了这种功能。具体来说,任务可以使用 sched_setattr() 系统调用,在 sched_attr 结构的 sched_runtime 字段中传递所需的切片时间(以纳秒为单位)。在当前的内核中,此字段仅用于截止日期调度。有了这个添加,任何任务都可以请求更短的时间片,这将导致它运行得更快,甚至可能更频繁。然而,如果请求的时间片太短,任务将发现自己经常被抢占,整体运行速度会变慢。
时间片的允许范围为 100µs 至 100ms。对于好奇的人来说,Zijlstra 在这个补丁的更新日志中用一组令人印象深刻的ASCII艺术图说明了各种时间片选择的结果。
这些变化还处于相对早期的状态,似乎需要在考虑合并之前进行修订。除此之外,与对照组的互动尚未得到调查,很可能无法正常工作。但是,一旦细节得到处理,EEVDF调度器应该已经准备好更广泛地使用。
三、补充
1. 虚拟截止时间是如何计算出来的
在Linux的EEVDF(Earliest Eligible Virtual Deadline First)调度算法中,**虚拟截止日期(Virtual Deadline)**的计算主要基于任务的实际执行时间、权重(优先级)以及调度器的公平性设计。具体计算逻辑如下:
(1) 虚拟运行时间(vruntime)基础
每个任务维护一个虚拟运行时间(vruntime),用于衡量任务在CPU上的“虚拟消耗”。
vruntime的计算公式为:
vruntime = 实际运行时间 * 基准权重/任务权重
其中,权重高的任务(如实时任务)的vruntime 增长更慢,从而获得更高的调度优先级。
(2) 虚拟截止日期的生成
当任务被唤醒或重新加入就绪队列时,调度器会为其生成一个新的虚拟截止日期。
截止日期通过以下公式计算:
Virtual Deadline = vruntime + 调度延迟/任务权重
这里的“调度延迟”是调度器分配给任务的下一个时间片长度,权重高的任务会被分配更早的截止日期。
(3) 动态调整机制
虚拟截止日期会随着任务的执行和休眠动态更新。
高优先级任务(权重高)的截止日期更早,确保其能优先获得CPU;低优先级任务则相反。
(4) 与EDF的区别
传统EDF算法直接基于任务的绝对物理截止时间,而EEVDF通过虚拟化时间实现公平性与实时性的结合。
EEVDF的虚拟截止日期是逻辑值,用于在CFS(完全公平调度器)框架下模拟截止期驱动的行为,而非直接依赖物理时钟。
综上,EEVDF通过权重和虚拟时间的转换,将任务的紧迫性映射到虚拟截止日期上,从而在保证公平性的同时优化实时任务响应。
2. linux EEVDF中的调度延迟是如何计算的?
在EEVDF调度器中,调度延迟的计算主要依赖于以下核心机制:
(1) Lag(滞后值)与 Eligible Time(资格时间)
Lag 反映进程实际获得CPU时间与理想分配的偏差(正值表示欠服务,负值表示超额)
当 lag≥0 时进程获得调度资格,此时 eligible time=当前时间
欠服务进程的 eligible time 会提前,超额进程的 eligible time 会延后。
(2) Virtual Deadline(虚拟截止时间) 计算公式:deadline = eligible_time + slice/weight ==>I:不还是选择虚拟时间小的先运行?
Slice:分配给进程的时间片长度(延迟敏感任务设置更小值)(I: 应该就是CFS原生逻辑分配给任务的时间片)
Weight:由nice值决定的权重(类似CFS优先级)
调度器始终选择eligible任务中最小的deadline执行
(3) 延迟敏感任务优化
低延迟需求的任务设置更小 slice(如0.75ms)
短 slice 导致 deadline 更接近 eligible_time,优先级更高
高延迟容忍任务使用更大 slice,减少调度次数但总CPU时间相同
(4) 调度延迟观测方法
内核态:通过 tracepoint 跟踪 sched_wakeup/sched_switch 事件时间差。
工具示例:
# 使用bcc工具观测调度延迟分布
runqlat.py -m 1
输出示例:
usecs : count distribution
0 -> 1 : 12 |*** |
2 -> 3 : 40 |********** |
4 -> 7 : 35 |********* |
8 -> 15 : 28 |******* |
(5) 与CFS的区别
-------------------------------------------------------------------- 维度 CFS EEVDF -------------------------------------------------------------------- 调度依据 最小 vruntime 最小 eligible deadline 延迟控制 依赖调度周期(sysctl_latency) 直接通过 slice 参数控制 数据结构 按 vruntime 排序的红黑树 按deadline排序的增强红黑树 公平性补偿 仅权重分配 权重+lag补偿双重机制 --------------------------------------------------------------------
EEVDF通过动态计算 eligible_time 和 deadline,使延迟敏感任务无需等待完整调度周期即可快速响应,相比CFS的固定调度周期模型,能实现更精确的延迟控制。
posted on 2025-03-09 19:45 Hello-World3 阅读(211) 评论(0) 收藏 举报