英语

调度器域

每个 CPU 都有一个“基本”调度域(struct sched_domain)。域层次结构通过 ->parent 指针从这些基本域构建。 ->parent 必须以 NULL 结尾,并且域结构应该是每个 CPU 的,因为它们是无锁更新的。

每个调度域跨越多个 CPU(存储在 ->span 字段中)。域的 span 必须是其子域 span 的超集(如果需要,可以放宽此限制),并且 CPU i 的基本域必须至少跨越 i。每个 CPU 的顶级域通常跨越系统中的所有 CPU,尽管严格来说不必如此,但这可能导致某些 CPU 永远不会被赋予运行任务,除非显式设置了允许的 CPU 掩码。调度域的 span 表示“在这些 CPU 之间平衡进程负载”。

每个调度域必须有一个或多个 CPU 组(struct sched_group),这些组从 ->groups 指针组织为循环单向链表。这些组的 cpumask 的并集必须与域的 span 相同。 ->groups 指针指向的组必须包含该域所属的 CPU。组可以在 CPU 之间共享,因为它们包含设置后只读数据。来自任意两个组的 cpumask 的交集可以是非空的。 如果是这种情况,则在相应的调度域上设置 SD_OVERLAP 标志,并且其组可能不会在 CPU 之间共享。

调度域内的负载均衡发生在组之间。也就是说,每个组都被视为一个实体。一个组的负载被定义为其每个成员 CPU 的负载之和,只有当一个组的负载变得不平衡时,任务才会在组之间移动。

在 kernel/sched/core.c 中,sched_balance_trigger() 通过 sched_tick() 定期在每个 CPU 上运行。 它会在当前运行队列的下一个定期调度的重新平衡事件到达后引发一个 softirq。 实际的负载均衡主力,sched_balance_softirq()->sched_balance_domains(),然后在 softirq 上下文 (SCHED_SOFTIRQ) 中运行。

后一个函数接受两个参数:当前 CPU 的运行队列以及 sched_tick() 发生时 CPU 是否处于空闲状态,并迭代我们的 CPU 所在的所有调度域,从其基本域开始,然后向上遍历 ->parent 链。 在执行此操作时,它会检查当前域是否已耗尽其重新平衡间隔。 如果是这样,它会在该域上运行 sched_balance_rq()。 然后它检查父 sched_domain(如果存在),以及父的父等等。

最初,sched_balance_rq() 找到当前调度域中最繁忙的组。 如果成功,它会查找该组中所有 CPU 的运行队列中最繁忙的运行队列。 如果它设法找到这样的运行队列,它会锁定我们的初始 CPU 的运行队列和新找到的最繁忙的运行队列,并开始将任务从它移动到我们的运行队列。 任务的确切数量相当于先前在迭代此调度域的组时计算出的不平衡。

实现调度器域

“基本”域将“跨越”层次结构的第一个级别。 在 SMT 的情况下,您将跨越物理 CPU 的所有兄弟,每个组都是一个虚拟 CPU。

在 SMP 中,基本域的父域将跨越节点中的所有物理 CPU。 每个组都是一个物理 CPU。 然后使用 NUMA,SMP 域的父域将跨越整个机器,每个组都具有一个节点的 cpumask。 或者,例如,您可以执行多级 NUMA 或 Opteron,可能只有一个域覆盖其一个 NUMA 级别。

实现者应阅读 include/linux/sched/sd_flags.h 中的注释:SD_* 以了解具体细节以及如何针对 sched_domain 的 SD 标志进行调整。

架构可以通过创建一个 sched_domain_topology_level 数组并使用此数组作为参数调用 set_sched_topology() 来覆盖通用域构建器和给定拓扑级别的默认 SD 标志。

可以通过将 'sched_verbose' 添加到您的 cmdline 来启用 sched-domains 调试基础设施。 如果您忘记调整您的 cmdline,您也可以翻转 /sys/kernel/debug/sched/verbose knob。 这会启用调度域的错误检查解析,该解析应该会捕获大多数可能的错误(如上所述)。 它还会以可视格式打印出域结构。