〇、全景:SMT 的两个后果,两个进阶解法
上一篇(#11)讲清了 SMT 的"共享/不共享"边界。这个边界带来两个后果,各有各的进阶解法:
- 安全后果 :兄弟逻辑核共享微架构状态 → 侧信道(MDS/L1TF)→ 最彻底的解法是关 SMT(
nosmt),但那太激进。core scheduling 给出折中:不关 SMT,而是只让"互信"的任务同核共存。 - 性能后果 :兄弟逻辑核共享算力 → 不能把它们当独立核做负载平衡 → 调度器用
SD_SHARE_CPUCAPACITY在负载平衡时把"共享算力"考虑进去。
#mermaid-svg-V4vdFSxEkPWWbdXb{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-V4vdFSxEkPWWbdXb .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-V4vdFSxEkPWWbdXb .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-V4vdFSxEkPWWbdXb .error-icon{fill:#552222;}#mermaid-svg-V4vdFSxEkPWWbdXb .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-V4vdFSxEkPWWbdXb .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-V4vdFSxEkPWWbdXb .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-V4vdFSxEkPWWbdXb .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-V4vdFSxEkPWWbdXb .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-V4vdFSxEkPWWbdXb .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-V4vdFSxEkPWWbdXb .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-V4vdFSxEkPWWbdXb .marker{fill:#333333;stroke:#333333;}#mermaid-svg-V4vdFSxEkPWWbdXb .marker.cross{stroke:#333333;}#mermaid-svg-V4vdFSxEkPWWbdXb svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-V4vdFSxEkPWWbdXb p{margin:0;}#mermaid-svg-V4vdFSxEkPWWbdXb .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-V4vdFSxEkPWWbdXb .cluster-label text{fill:#333;}#mermaid-svg-V4vdFSxEkPWWbdXb .cluster-label span{color:#333;}#mermaid-svg-V4vdFSxEkPWWbdXb .cluster-label span p{background-color:transparent;}#mermaid-svg-V4vdFSxEkPWWbdXb .label text,#mermaid-svg-V4vdFSxEkPWWbdXb span{fill:#333;color:#333;}#mermaid-svg-V4vdFSxEkPWWbdXb .node rect,#mermaid-svg-V4vdFSxEkPWWbdXb .node circle,#mermaid-svg-V4vdFSxEkPWWbdXb .node ellipse,#mermaid-svg-V4vdFSxEkPWWbdXb .node polygon,#mermaid-svg-V4vdFSxEkPWWbdXb .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-V4vdFSxEkPWWbdXb .rough-node .label text,#mermaid-svg-V4vdFSxEkPWWbdXb .node .label text,#mermaid-svg-V4vdFSxEkPWWbdXb .image-shape .label,#mermaid-svg-V4vdFSxEkPWWbdXb .icon-shape .label{text-anchor:middle;}#mermaid-svg-V4vdFSxEkPWWbdXb .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-V4vdFSxEkPWWbdXb .rough-node .label,#mermaid-svg-V4vdFSxEkPWWbdXb .node .label,#mermaid-svg-V4vdFSxEkPWWbdXb .image-shape .label,#mermaid-svg-V4vdFSxEkPWWbdXb .icon-shape .label{text-align:center;}#mermaid-svg-V4vdFSxEkPWWbdXb .node.clickable{cursor:pointer;}#mermaid-svg-V4vdFSxEkPWWbdXb .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-V4vdFSxEkPWWbdXb .arrowheadPath{fill:#333333;}#mermaid-svg-V4vdFSxEkPWWbdXb .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-V4vdFSxEkPWWbdXb .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-V4vdFSxEkPWWbdXb .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V4vdFSxEkPWWbdXb .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-V4vdFSxEkPWWbdXb .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V4vdFSxEkPWWbdXb .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-V4vdFSxEkPWWbdXb .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-V4vdFSxEkPWWbdXb .cluster text{fill:#333;}#mermaid-svg-V4vdFSxEkPWWbdXb .cluster span{color:#333;}#mermaid-svg-V4vdFSxEkPWWbdXb div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-V4vdFSxEkPWWbdXb .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-V4vdFSxEkPWWbdXb rect.text{fill:none;stroke-width:0;}#mermaid-svg-V4vdFSxEkPWWbdXb .icon-shape,#mermaid-svg-V4vdFSxEkPWWbdXb .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-V4vdFSxEkPWWbdXb .icon-shape p,#mermaid-svg-V4vdFSxEkPWWbdXb .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-V4vdFSxEkPWWbdXb .icon-shape .label rect,#mermaid-svg-V4vdFSxEkPWWbdXb .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-V4vdFSxEkPWWbdXb .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-V4vdFSxEkPWWbdXb .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-V4vdFSxEkPWWbdXb :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SMT 共享
(#11 的边界)
安全后果
侧信道 MDS/L1TF
性能后果
共享算力
core scheduling
core_cookie 信任域
整核原子调度
负载平衡
SD_SHARE_CPUCAPACITY
sibling_imbalance
一句话主线:SMT 的共享带来两个后果------安全上可被侧信道攻击,core scheduling 用
core_cookie(信任域)把任务分组、整核原子调度,只让同 cookie 的任务同核共存;性能上共享算力不能当独立核,负载平衡用SD_SHARE_CPUCAPACITY避免把任务堆到同一物理核的忙兄弟上。
一、core scheduling:不关 SMT,只隔离
1.1 动机:关 SMT 太激进,需要"运行时隔离"
MDS/L1TF 这类侧信道的本质是"兄弟逻辑核通过共享的微架构状态互相偷看"。最彻底的办法是把 SMT 关掉(nosmt,见 #11 §四),物理核上只剩一个逻辑核,自然没有兄弟可嗅探------但代价是把多线程吞吐砍半。
**core scheduling(CONFIG_SCHED_CORE)**是折中:SMT 照开,但调度器保证同一个物理核的两个兄弟逻辑核上,同时运行的只能是"互相信任"的任务。互不信任的任务(比如两个不同容器/租户的任务)永远不会成为兄弟同时运行,侧信道就无从谈起;而同一信任域里的任务照常共享算力。
1.2 机制:core_cookie 信任域 + 整核原子调度
core scheduling 给每个任务加一个信任域标记 core_cookie (struct sched_core_cookie,core_sched.c:7):
c
// kernel/sched/core_sched.c (v6.6, line 7)
struct sched_core_cookie {
refcount_t refcnt;
};
规则很简单:两个任务要成为兄弟同时运行,core_cookie 必须相等(cookie_match,core.c:6040;idle 永远兼容) 。cookie 相同 = 互信,可以同核;cookie 不同 = 不互信,不能同核。注意 cookie=0 是默认值、表示"不设防"------全核都没有非零 cookie 时,core scheduling 走快路径、压根不介入隔离(core.c:6155),只有出现非零 cookie 才强制"同核必须同 cookie"。
关键在执行:当 core scheduling 启用时,pick_next_task(core.c:6067)不再逐 CPU 独立选任务,而是把整个物理核的所有兄弟当整体、原子地选:
c
// kernel/sched/core.c (v6.6, line 6067)
static struct task_struct *
pick_next_task(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
{
// ...
if (!sched_core_enabled(rq))
return __pick_next_task(rq, prev, rf); // 没开 core scheduling,走老路
// ... 整核原子选择:遍历 smt_mask 里的每个兄弟,按 core_cookie 排序挑任务
// 选中的任务记在 rq->core_pick 里
}
选完还要校验"兄弟之间 cookie 必须匹配",这是防止破坏 L1TF 缓解的硬检查:
c
// kernel/sched/core.c (v6.6, line 6274)
/* Did we break L1TF mitigation requirements? */
WARN_ON_ONCE(!cookie_match(next, rq_i->core_pick));
如果物理核上跑着不同 cookie 的任务怎么办?答案是 force idle :只有"胜出"的那组 cookie 的任务真正占 CPU,其他不互信的任务虽然 runnable,却被强制置空 (force idle)------它们的 vruntime 照常推进(task_vruntime_update,core.c:6264),只是分不到执行单元。这样"不互信任务不同时运行"的承诺就被硬性维持住了:
c
// kernel/sched/core.c (v6.6, line 6288)
if (rq->core->core_forceidle_count && next == rq->idle)
queue_core_balance(rq);
1.3 接口:PR_SCHED_CORE
用户态通过 prctl(PR_SCHED_CORE, ...) 给任务分配 cookie,入口是 sched_core_share_pid(core_sched.c:129):
c
// kernel/sched/core_sched.c (v6.6, line 129)
int sched_core_share_pid(unsigned int cmd, pid_t pid, enum pid_type type,
unsigned long uaddr)
{
// ...
if (!static_branch_likely(&sched_smt_present))
return -ENODEV; // 没有 SMT,core scheduling 没意义
// ...
// 权限检查:ptrace_may_access(能 ptrace 对方才能改对方的 cookie)
四种命令(core_sched.c:170 起的 switch):
| 命令 | 作用 |
|---|---|
PR_SCHED_CORE_CREATE |
新建一个 cookie,分配给指定任务 |
PR_SCHED_CORE_SHARE_TO |
让指定任务共享"当前任务"的 cookie |
PR_SCHED_CORE_SHARE_FROM |
让"当前任务"共享指定任务的 cookie |
PR_SCHED_CORE_GET |
读某个任务的 cookie |
type 决定作用范围:PIDTYPE_PID(单线程)/ PIDTYPE_TGID(整个线程组)/ PIDTYPE_PGID(整个进程组)。于是管理员可以给每个容器/租户分配一个独立 cookie,让它们互不成为 SMT 兄弟。
二、SMT 负载平衡细节:SD_SHARE_CPUCAPACITY
#11 讲过 SD_SHARE_CPUCAPACITY 是 SMT 的"身份证"(共享算力)。这里展开它在负载平衡里具体怎么起作用------两个关键点。
2.1 sibling_imbalance:兄弟核"多任务共享算力"就是不平衡
负载平衡的一个判断是"源组到底是不是真的负载不均"。sibling_imbalance(fair.c:9611)专门处理 SMT 源组:
c
// kernel/sched/fair.c (v6.6, line 9598)
/*
* For SMT source group, it is better to move a task
* to a CPU that doesn't have multiple tasks sharing its CPU capacity.
* Note that if a group has a single SMT, SD_SHARE_CPUCAPACITY
* will not be on.
*/
if (group->flags & SD_SHARE_CPUCAPACITY &&
sgs->sum_h_nr_running > 1)
return true; // 判定为"不平衡",值得迁移
sum_h_nr_running 是组内正在跑的 CFS 任务数。含义:如果一个 SMT 组里有多个任务在跑(说明兄弟之间在共享/抢算力),就认为它"不平衡"、值得把任务迁到不共享算力的核上。这和"把任务摊开、别堆在同一物理核"的原则一致。
2.2 group_has_spare:不把"只剩一个任务的 SMT 核"当纯核来抢
在找"有空闲容量"的组(update_sg_lb_stats,fair.c:9667)时,SD_SHARE_CPUCAPACITY 影响对 spare 的判定:
c
// kernel/sched/fair.c (v6.6, line 9863)
case group_has_spare:
/*
* Do not pick sg with SMT CPUs over sg with pure CPUs,
* as we do not want to pull task off SMT core with one task
* and make the core idle.
*/
if (smt_vs_nonsmt_groups(sds->busiest, sg)) {
if (sg->flags & SD_SHARE_CPUCAPACITY && sgs->sum_h_nr_running <= 1)
return false; // 别选这个 SMT 组
else
return true;
}
含义:一个 SMT 物理核上只剩 1 个任务 时,它已经"半空闲"了(另一个兄弟 idle)。如果负载平衡器还要从它上面把最后那个任务也拉走,这个物理核就彻底 idle 了------而保持"1 个任务 + 1 个 idle 兄弟"反而更好(任务独占算力、核不空闲)。所以 sum_h_nr_running <= 1 时不把它当可抢的 spare 组。
这两个点合起来就是 SD_SHARE_CPUCAPACITY 的完整含义:让负载平衡器"看见"兄弟核共享算力这个事实,从而(1)积极把堆在忙兄弟上的任务摊开,(2)不把半空闲的 SMT 核掏空。
三、为什么这样设计(Why 层)
3.1 为什么 core scheduling 选"cookie 分组 + 整核原子调度",而不是别的
侧信道的根子是"不互信的任务同时运行在同一物理核上"。要堵住它,只有两条路:关 SMT (彻底但浪费),或限制"谁和谁做兄弟"。后者需要两件事:
- 一个"信任域"的表示 ------
core_cookie,简单、可比、可继承(fork 时 clone)。 - 把"选任务"从 per-CPU 提升到 per-core ------因为"两个兄弟能不能共存"是跨 CPU 的约束,单个 CPU 的调度器自己决定不了,必须把整个物理核当整体、原子地选。
这就是为什么 core scheduling 要动 pick_next_task 这个核心路径:只有整核原子调度,才能保证"不互信任务永不同核"这个跨核不变式。
3.2 为什么 force idle 而不是"把不互信任务迁走"
迁移有代价(cache 冷、要重新选核),而且可能迁过去还是找不到"全是同 cookie"的核。force idle 的取舍是:让不互信任务原地等,等胜出任务用完时间片再轮换 。代价是部分时间片被浪费(force-idle 的兄弟在空转),但换来"绝对不共享"的安全保证 + 无需迁移。安全优先于吞吐,这是 core scheduling 的核心权衡。
3.3 为什么负载平衡要"看见"共享算力,而不是把兄弟当普通核
如果把 SMT 兄弟当独立核,负载平衡器会高估并行度 :两个任务放到同一物理核的兄弟上,实际是分时复用一套执行单元。SD_SHARE_CPUCAPACITY 把这个事实显式告诉平衡器,让它:
- 摊开(§2.1):忙兄弟上的任务优先迁走;
- 不掏空(§2.2):半空闲的 SMT 核不当作可抢的 spare。
"看见共享"是 SMT 上正确负载平衡的前提 ------和 #11 里 select_idle_sibling 优先找空闲兄弟是同一原则的两面。
四、与已有博客的交叉引用
| 已有博客 | 本篇覆盖的关系 |
|---|---|
| CPU #11 SMT | 本篇是 #11 的进阶:core scheduling 对应 #11 §四的"安全"那条线(从"关 SMT"升级到"运行时隔离");§二对应 #11 §三的 SD_SHARE_CPUCAPACITY(从"是什么"到"怎么影响负载平衡") |
| CPU #7 调度器启动 | pick_next_task 是调度器核心路径,core scheduling 在 #7 讲的 sched_class 选任务之上加了一层"整核原子选择" |
| CPU #3 hotplug 状态机 | core scheduling 依赖 sched_smt_present(有 SMT 才有意义),与 #3 的 CPU 上线拓扑识别相关 |
附:本篇关键源码索引
| 符号 | 位置 |
|---|---|
pick_next_task(core scheduling 入口) |
kernel/sched/core.c:6067 |
sched_core_enabled(rq)(是否走整核选择) |
kernel/sched/core.c:6078 |
cookie_match 校验("break L1TF mitigation") |
kernel/sched/core.c:6274 |
queue_core_balance(force idle 后平衡) |
kernel/sched/core.c:6288 |
struct sched_core_cookie / sched_core_alloc_cookie |
kernel/sched/core_sched.c:7 / :11 |
sched_core_share_pid(PR_SCHED_CORE 接口) |
kernel/sched/core_sched.c:129 |
sched_smt_present 检查(无 SMT 返回 ENODEV) |
kernel/sched/core_sched.c:137 |
sibling_imbalance(SMT 源组不平衡判定) |
kernel/sched/fair.c:9611 |
SD_SHARE_CPUCAPACITY && sum_h_nr_running > 1 |
kernel/sched/fair.c:9604 |
update_sg_lb_stats(组统计) |
kernel/sched/fair.c:9667 |
group_has_spare 的 SMT 特判 |
kernel/sched/fair.c:9870 |
find_busiest_group |
kernel/sched/fair.c:10629 |