[内核学习]SMP调度

/ 0评 / 0赞

前面所有调度话题都假装只有一个 CPU.真机是 4 个 A53 加 2 个 A72,六个核,两种脾气,调度器的活立刻多了两件:任务放哪个核,核之间的负载怎么摆平.这篇把 SMP 的三层机制 — 亲和性,负载均衡,异构感知 — 全部在板上实测,顺手把频率联动也抓个现行.

这篇回答几件事:

先看摊任务.起六个不绑核的死循环,两秒后查它们各自跑在哪个核 — /proc/PID/stat 的 psr 字段(第四十来列)就是任务最后所在 CPU,ps -eo comm,psr 直接看:

spin  0 / spin  1 / spin  2 / spin  3 / spin  4 / spin  5

六个任务恰好六个核,一人一个.这不是巧合,是负载均衡器在干活:每个 CPU 有自己的运行队列(所以叫 per-CPU rq),闲的队列会从忙的队列"拉"任务,周期性均衡加空闲时紧急均衡,搬的时机和数量由调度域的参数决定 — debugfs 里能看到几个旋钮,migration_cost_ns(搬任务的"运费",低于这个收益差的搬运不做)和 nr_migrate(一次最多搬几个).搬运不是免费的:任务换了核,它的 cache 全部作废,上一章说的切换成本之外又叠加一层,所以均衡器天生保守.

亲和性是用户态干预"放哪"的把手.taskset -c 4 ./prog 把任务钉在 cpu4,/proc/PID/status 的 Cpus_allowed_list 如实反映(板上默认 0-5 全开).什么时候该绑:性能抖动敏感的线程绑核避免迁移(cache 稳定);中断亲和和线程亲和配对绑同核(cache 命中);故意把测试负载和系统服务隔离.什么时候别绑:绑死妨碍均衡器救场,六个忙核的世界里把任务钉死等于自愿排队.

然后是这块板的重头戏,大小核.RK3399 是典型的 big.LITTLE:cpu0-3 是 A53 小核,最高 1.4GHz,cpu4-5 是 A72 大核,1.8GHz.同一个计算负载,钉在不同核上跑:

cpu0 (A53): 217 ms
cpu4 (A72):  86 ms    -> 大核快约 2.5 倍

这不是频率差能解释的(1.8/1.4 只有 1.29),是微架构的差距 — A72 更宽的乱序窗口,更强的分支预测.1.5 篇提过设备树里每个 CPU 节点有 capacity-dmips-mhz,A53 是 485,A72 是 1024,启动时这些数字进了内核的拓扑表,归一化后就是调度器眼里的"每毫秒能干多少活".微基准测出 2.5 倍,DT 声明 2.1 倍,同方向不同值 — capacity 是个模型不是定律,具体负载的比值会飘,但它给调度器提供了统一的决策货币.

有了 capacity,SMP 调度的选核逻辑(select_task_rq)就从"挑最闲的"升级成"挑最划算的":唤醒一个任务,预估它的 util,能量感知(EAS)路径会比较把它放小核(省电但可能跑不完)和大核(快但费电)的代价,选满足性能前提下最省的;系统负载重到大核也挤,EAS 让路退回纯负载均衡.这套逻辑只有调度域按真实拓扑组织时才工作:每个 cluster 一个 MC 级域(共享 L2),往上一个 DIE 级域(整个 SoC),均衡在域内积极,跨域保守 — 谁都不愿意任务在两个簇之间来回横跳,跨簇的 cache 作废比簇内贵得多.

频率联动最后抓个现行.idle 时 cpu4 蹲在 600MHz(3.5 篇看的),六个 spin 压上去之后再看:

scaling_cur_freq = 1800000

满血.链路是调度器把每个任务的 util 信号喂给 cpufreq 的调度器调速器(schedutil),util 高说明忙,频率就该拉上去 — 调度和调频在 6.x 里已经是一对连体婴.这套反馈环也解释了 capacity 的另一个用途:util 是按"当前频率下的负载"算的,要换算成"满频需要多少"才能和 capacity 比,内核里这套换算贯穿 schedutil 和 EAS.

最后留几个容易忽略的细节:

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注