1 .. SPDX-License-Identifier: GPL-2.0 1 .. SPDX-License-Identifier: GPL-2.0 2 .. include:: ../disclaimer-zh_CN.rst 2 .. include:: ../disclaimer-zh_CN.rst 3 3 4 :Original: Documentation/scheduler/sched-domai 4 :Original: Documentation/scheduler/sched-domains.rst 5 5 6 :翻译: 6 :翻译: 7 7 8 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> 8 唐艺舟 Tang Yizhou <tangyeechou@gmail.com> 9 9 10 :校译: 10 :校译: 11 11 12 司延腾 Yanteng Si <siyanteng@loongson.cn> 12 司延腾 Yanteng Si <siyanteng@loongson.cn> 13 13 14 ====== 14 ====== 15 调度域 15 调度域 16 ====== 16 ====== 17 17 18 每个CPU有一个“基”调度域(struct 18 每个CPU有一个“基”调度域(struct sched_domain)。调度域层次结构从基调度域构建而来,可 19 通过->parent指针自下而上遍历。->par 19 通过->parent指针自下而上遍历。->parent必须以NULL结尾,调度域结构体必须是per-CPU的, 20 因为它们无锁更新。 20 因为它们无锁更新。 21 21 22 每个调度域管辖数个CPU(存储在->sp 22 每个调度域管辖数个CPU(存储在->span字段中)。一个调度域的span必须是它的子调度域span的 23 超集(如有需求出现,这个限制可 23 超集(如有需求出现,这个限制可以放宽)。CPU i的基调度域必须至少管辖CPU i。每个CPU的 24 顶层调度域通常将会管辖系统中的 24 顶层调度域通常将会管辖系统中的全部CPU,尽管严格来说这不是必须的,假如是这样,会导致某些 25 CPU出现永远不会被指定任务运行的 25 CPU出现永远不会被指定任务运行的情况,直到允许的CPU掩码被显式设定。调度域的span字段意味 26 着“在这些CPU中做进程负载均衡” 26 着“在这些CPU中做进程负载均衡”。 27 27 28 每个调度域必须具有一个或多个CPU 28 每个调度域必须具有一个或多个CPU调度组(struct sched_group),它们以单向循环链表的形式 29 组织,存储在->groups指针中。这些 29 组织,存储在->groups指针中。这些组的CPU掩码的并集必须和调度域span字段一致。->groups 30 指针指向的这些组包含的CPU,必须 30 指针指向的这些组包含的CPU,必须被调度域管辖。组包含的是只读数据,被创建之后,可能被多个 31 CPU共享。任意两个组的CPU掩码的交 31 CPU共享。任意两个组的CPU掩码的交集不一定为空,如果是这种情况,对应调度域的SD_OVERLAP 32 标志位被设置,它管辖的调度组可 32 标志位被设置,它管辖的调度组可能不能在多个CPU中共享。 33 33 34 调度域中的负载均衡发生在调度组 34 调度域中的负载均衡发生在调度组中。也就是说,每个组被视为一个实体。组的负载被定义为它 35 管辖的每个CPU的负载之和。仅当组 35 管辖的每个CPU的负载之和。仅当组的负载不均衡后,任务才在组之间发生迁移。 36 36 37 在kernel/sched/core.c中,sched_balance_trig 37 在kernel/sched/core.c中,sched_balance_trigger()在每个CPU上通过sched_tick() 38 周期执行。在当前运行队列下一个 38 周期执行。在当前运行队列下一个定期调度再平衡事件到达后,它引发一个软中断。负载均衡真正 39 的工作由sched_balance_softirq()->sched_bal 39 的工作由sched_balance_softirq()->sched_balance_domains()完成,在软中断上下文中执行 40 (SCHED_SOFTIRQ)。 40 (SCHED_SOFTIRQ)。 41 41 42 后一个函数有两个入参:当前CPU的 42 后一个函数有两个入参:当前CPU的运行队列、它在sched_tick()调用时是否空闲。函数会从 43 当前CPU所在的基调度域开始迭代执 43 当前CPU所在的基调度域开始迭代执行,并沿着parent指针链向上进入更高层级的调度域。在迭代 44 过程中,函数会检查当前调度域是 44 过程中,函数会检查当前调度域是否已经耗尽了再平衡的时间间隔,如果是,它在该调度域运行 45 sched_balance_rq()。接下来它检查父调 45 sched_balance_rq()。接下来它检查父调度域(如果存在),再后来父调度域的父调度域,以此类推。 46 46 47 起初,sched_balance_rq()查找当前调度 47 起初,sched_balance_rq()查找当前调度域中最繁忙的调度组。如果成功,在该调度组管辖的全部CPU 48 的运行队列中找出最繁忙的运行队 48 的运行队列中找出最繁忙的运行队列。如能找到,对当前的CPU运行队列和新找到的最繁忙运行 49 队列均加锁,并把任务从最繁忙队 49 队列均加锁,并把任务从最繁忙队列中迁移到当前CPU上。被迁移的任务数量等于在先前迭代执行 50 中计算出的该调度域的调度组的不 50 中计算出的该调度域的调度组的不均衡值。 51 51 52 实现调度域 52 实现调度域 53 ========== 53 ========== 54 54 55 基调度域会管辖CPU层次结构中的第 55 基调度域会管辖CPU层次结构中的第一层。对于超线程(SMT)而言,基调度域将会管辖同一个物理 56 CPU的全部虚拟CPU,每个虚拟CPU对应 56 CPU的全部虚拟CPU,每个虚拟CPU对应一个调度组。 57 57 58 在SMP中,基调度域的父调度域将会 58 在SMP中,基调度域的父调度域将会管辖同一个结点中的全部物理CPU,每个调度组对应一个物理CPU。 59 接下来,如果是非统一内存访问(N 59 接下来,如果是非统一内存访问(NUMA)系统,SMP调度域的父调度域将管辖整个机器,一个结点的 60 CPU掩码对应一个调度组。亦或,你 60 CPU掩码对应一个调度组。亦或,你可以使用多级NUMA;举例来说Opteron处理器,可能仅用一个 61 调度域来覆盖它的一个NUMA层级。 61 调度域来覆盖它的一个NUMA层级。 62 62 63 实现者需要阅读include/linux/sched/sd_fl 63 实现者需要阅读include/linux/sched/sd_flags.h的注释:读SD_*来了解具体情况以及调度域的 64 SD标志位调节了哪些东西。 64 SD标志位调节了哪些东西。 65 65 66 体系结构可以把指定的拓扑层级的 66 体系结构可以把指定的拓扑层级的通用调度域构建器和默认的SD标志位覆盖掉,方法是创建一个 67 sched_domain_topology_level数组,并以该 67 sched_domain_topology_level数组,并以该数组作为入参调用set_sched_topology()。 68 68 69 调度域调试基础设施可以通过CONFIG_ 69 调度域调试基础设施可以通过CONFIG_SCHED_DEBUG开启,并在开机启动命令行中增加 70 “sched_verbose”。如果你忘记调整开 70 “sched_verbose”。如果你忘记调整开机启动命令行了,也可以打开 71 /sys/kernel/debug/sched/verbose开关。这将 71 /sys/kernel/debug/sched/verbose开关。这将开启调度域错误检查的解析,它应该能捕获(上文 72 描述过的)绝大多数错误,同时以 72 描述过的)绝大多数错误,同时以可视化格式打印调度域的结构。
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.