Linux 内核 Core Scheduling:SMT 安全域与侧信道深度防御

一、为什么 SMT 变成了安全炸弹

Intel Hyper-Threading(同步多线程,SMT)自 2002 年诞生以来,一直以「免费性能」的逻辑存在:一个物理核上并行的两个逻辑处理器共享执行单元、缓存、TLB 和微架构状态。操作系统将其视为两个独立 CPU,可以在一个逻辑核空闲时让另一个抢占前端流水线和 ALU,通常带来 15%~30% 的吞吐提升。

然而从 2018 年开始,Spectre、Meltdown 及其衍生变体的爆发彻底颠覆了这个假设。SMT 不再是一种透明的资源共享机制,它变成了侧信道攻击的高速公路:

- L1TF(L1 Terminal Fault):利用同一个物理核上并发执行的跨逻辑核访问,从 L1 数据缓存中抽取宿主机或其他租户的明文数据。 - MDS(Microarchitectural Data Sampling):包括 RIDL、Fallout、ZombieLoad,跨 SMT 线程的 Store Buffer、Line Fill Buffer、Load Port 可以被并发线程采样窃取。 - TAA(TSX Asynchronous Abort):利用 TSX 事务内存的乱序窗口在 SMT 邻居间进行数据窃取。 - SRBDS(Special Register Buffer Data Sampling):跨核的 RDRAND/RDSEED 结果可以被邻居推测窃取。

这些攻击的共同模式在于:攻击者并不需要与受害者共享内存页面或进程边界,只需共享同一物理核心上的逻辑兄弟即可。这让传统的进程隔离、KASLR、页表隔离(KPTI)都变成了从内核层面防御,但对跨 SMT 共享资源的微架构层面却力不从心。

缓解方案出现了两极分化:

- 关闭 SMT:最彻底,但性能损失巨大(某些工作负载下降 30% )。 - STIBP / IBPB / L1D flush:软件缓解,细粒度但引入每次上下文切换或 VM entry/exit 的性能开销。

Core Scheduling 走中间路线:允许 SMT 保持开启,但在调度时强制"信任域"隔离,防止不可信任务进入同一物理核心。

二、核心设计:Cookie 信任模型

Core Scheduling 的核心概念极其简洁:每个任务(task)持有一个 cookie(调度标识),代表它的信任级别。只有拥有相同 cookie 的任务才允许在同一个物理核的逻辑兄弟上并发执行。

2.1 Cookie 数据结构

struct task_struct {

/* ... */ #ifdef CONFIG_SCHED_CORE unsigned int core_cookie; // 当前任务的信任标识 struct rb_node core_node; // 红黑树节点(用于核心级调度队列) struct list_head core_task_list; // 同一核心上的任务链表 unsigned int core_index; // 核心级调度顺序 unsigned int core_sched_seq; // 调度序列号 #endif };

struct sched_core { struct rb_root tree; // 按 core_index 排序的红黑树 struct list_head queue; // 同 cookie 任务队列 unsigned int core_id; // 物理核心 ID int nr_running; // 当前核心上的运行任务数 unsigned long cookie; // 核心当前的信任标识 bool is_idle; // 核心是否空闲 raw_spinlock_t lock; // 核心级自旋锁 };

2.2 信任模型的三个层级

1. 同一进程内线程(启用 SCHED_CORE 时):同一进程内的所有线程共享同一个 cookie,允许并发在 SMT 兄弟上运行。这是因为同一进程的信任边界相同。

2. 不同进程,同 UID:默认不同进程的 cookie 不同(随机生成),因此在严格模式下不能在同一物理核的 SMT 兄弟上并发。可以通过 PR_SCHED_CORE 的 SCORE 操作让同 UID 的进程共享 cookie。

3. 不同进程,不同 UID:严格隔离,不允许在 SMT 兄弟上并发。

2.3 sysctl 接口

# 查看当前 Core Scheduling 模式

cat /proc/sys/kernel/sched_core

0 = 关闭

1 = 仅捐赠(SMT 空闲时不强制驱逐,宽松模式)

2 = 严格模式(强制 SMT 隔离,违反时触发 IPI 驱逐)

生产环境中通常设置为 2(严格模式),配合 nmi_watchdog=0 和 skew_tick=1 使用。

三、调度器决策:pick_next_task_core

Core Scheduling 的关键实现位于 kernel/sched/core.c 的 pick_next_task_core() 函数。该函数在 CFS(完全公平调度器)之前被调用,确保核心级调度优先于进程级调度。

static struct task_struct *

pick_next_task_core(struct rq *rq, struct task_struct *prev) { struct sched_core *core = cpu_core(rq); struct task_struct *next, *leader;

// 如果核心上已有非空任务队列,从核心队列中选择 if (core-

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部