调度器-高级篇:节能调度、NUMA 亲和性与容器支持
"现代调度器不仅是任务分配者,更是系统资源的智能优化器。
本文将深入调度器的高级特性,
从节能调度到 NUMA 亲和性,
为自制 OS 构建企业级调度能力。"
引言:调度器的企业级需求
在现代数据中心和云环境中,调度器面临前所未有的复杂需求:
- 能耗成本:数据中心 30% 成本来自电力
- NUMA 拓扑:多路服务器的内存访问延迟差异
- 资源隔离:容器和虚拟机的资源争用
- 性能分析:复杂系统的调试和优化
传统的调度器(如 CFS)虽然公平高效,
但缺乏对这些企业级特性的支持。
本文将深入现代调度器的高级特性,
为自制 OS 构建完整的企业级调度能力。
第一章:节能调度
1.1 节能调度的核心挑战
问题本质
现代 CPU 的功耗与频率呈非线性关系:
- 频率 f:CPU 时钟频率
- 电压 V:工作电压
- 功耗 P ∝ C × V² × f
降低频率可显著降低功耗,但影响性能。
调度器的权衡
- 高性能模式:高频率,低延迟,高功耗
- 节能模式:低频率,高延迟,低功耗
- 平衡模式:动态调整,适应负载
1.2 CPU 频率调节
频率调节接口
// arch/x86/kernel/cpu/freq.c
void set_cpu_frequency(int cpu, unsigned long freq)
{
struct cpufreq_policy *policy = cpufreq_cpu_get(cpu);
if (policy) {
/* 设置目标频率 */
__cpufreq_driver_target(policy, freq, CPUFREQ_RELATION_H);
cpufreq_cpu_put(policy);
}
}
调度器集成
// kernel/sched/fair.c
static void energy_aware_task_tick(struct rq *rq, struct task_struct *p)
{
unsigned long util = rq->cfs.avg.util_avg;
unsigned long freq;
/* 根据负载调整频率 */
if (util > HIGH_LOAD_THRESHOLD) {
freq = MAX_FREQUENCY; /* 高负载:高性能 */
} else if (util < LOW_LOAD_THRESHOLD) {
freq = MIN_FREQUENCY; /* 低负载:节能 */
} else {
freq = util * MAX_FREQUENCY / 100; /* 平衡 */
}
set_cpu_frequency(rq->cpu, freq);
}
1.3 核休眠(CPU Idle)
空闲状态管理
// kernel/sched/idle.c
void cpu_idle_loop(void)
{
while (1) {
/* 检查是否有可运行任务 */
if (need_resched()) {
schedule();
continue;
}
/* 选择最优空闲状态 */
int next_state = select_idle_state();
/* 进入空闲状态 */
cpuidle_enter(next_state);
}
}
空闲状态选择
static int select_idle_state(void)
{
u64 predicted_idle_time = predict_idle_duration();
/* 根据预测空闲时间选择状态 */
if (predicted_idle_time > 100000) { /* >100ms */
return CPUIDLE_STATE_DEEP; /* 深度休眠 */
} else if (predicted_idle_time > 10000) { /* >10ms */
return CPUIDLE_STATE_MEDIUM; /* 中度休眠 */
} else {
return CPUIDLE_STATE_LIGHT; /* 轻度休眠 */
}
}
1.4 节能调度策略
调度类扩展
// kernel/sched/energy.c
struct energy_sched_class {
struct sched_class base;
void (*energy_task_tick)(struct rq *rq, struct task_struct *p);
int (*select_energy_cpu)(struct task_struct *p);
};
static struct energy_sched_class energy_sched = {
.base = {
.enqueue_task = enqueue_task_fair,
.pick_next_task = pick_next_task_fair,
.task_tick = energy_aware_task_tick,
},
.energy_task_tick = energy_aware_task_tick,
.select_energy_cpu = select_energy_cpu,
};
能效核调度
static int select_energy_cpu(struct task_struct *p)
{
int best_cpu = -1;
unsigned long min_energy = ULONG_MAX;
int cpu;
/* 遍历所有允许的 CPU */
for_each_cpu(cpu, &p->cpus_allowed) {
if (!cpu_online(cpu))
continue;
/* 计算能耗 */
unsigned long energy = calculate_cpu_energy(cpu, p);
if (energy < min_energy) {
min_energy = energy;
best_cpu = cpu;
}
}
return best_cpu;
}
第二章:NUMA 亲和性
2.1 NUMA 架构挑战
NUMA 拓扑
Node 0: CPU 0-3, Memory 0 (本地延迟 100ns)
Node 1: CPU 4-7, Memory 1 (本地延迟 100ns, 远程延迟 250ns)
性能影响
- 本地内存访问:100ns
- 远程内存访问:250ns(2.5 倍延迟)
- 跨节点带宽:通常为本地带宽的 50-70%
2.2 NUMA 拓扑检测
拓扑信息获取
// arch/x86/kernel/smpboot.c
static void detect_numa_topology(void)
{
int cpu;
for_each_possible_cpu(cpu) {
struct cpuinfo_x86 *c = &cpu_data(cpu);
/* 从 ACPI 或 CPUID 获取 NUMA 信息 */
c->phys_proc_id = topology_physical_package_id(cpu);
c->numa_node = cpu_to_node(cpu);
}
}
NUMA 距离表
// include/linux/numa.h
extern int node_distance(int a, int b);
static inline int node_distance(int a, int b)
{
if (a == b)
return LOCAL_DISTANCE; /* 10 */
else
return REMOTE_DISTANCE; /* 20 */
}
2.3 NUMA 亲和性调度
任务放置策略
// kernel/sched/fair.c
static int numa_select_cpu(struct task_struct *p)
{
int home_node = p->numa_home_node;
int cpu;
/* 1. 优先选择本地节点 CPU */
for_each_cpu_and(cpu, cpumask_of_node(home_node), &p->cpus_allowed) {
if (cpu_online(cpu))
return cpu;
}
/* 2. 回退到最近节点 */
int closest_node = find_closest_node(home_node, &p->cpus_allowed);
for_each_cpu_and(cpu, cpumask_of_node(closest_node), &p->cpus_allowed) {
if (cpu_online(cpu))
return cpu;
}
/* 3. 任意允许的 CPU */
return cpumask_any_and(&p->cpus_allowed, cpu_online_mask);
}
内存分配亲和性
// mm/page_alloc.c
struct page *alloc_pages_node(int nid, gfp_t gfp_mask, unsigned int order)
{
struct page *page;
/* 1. 优先本地节点分配 */
page = __alloc_pages_node(nid, gfp_mask, order);
if (page)
return page;
/* 2. 回退到其他节点 */
return __alloc_pages(gfp_mask, order, nid);
}
2.4 NUMA 负载均衡
NUMA 感知均衡
// kernel/sched/fair.c
static int numa_balance(struct rq *this_rq, struct rq *busiest_rq)
{
/* 1. 检查是否值得跨节点迁移 */
if (node_distance(cpu_to_node(this_rq->cpu),
cpu_to_node(busiest_rq->cpu)) > LOCAL_DISTANCE) {
/* 跨节点迁移成本高,需更严格的条件 */
if (busiest_rq->nr_running - this_rq->nr_running < 2)
return 0;
}
/* 2. 执行迁移 */
return move_one_task(this_rq, busiest_rq);
}
迁移成本评估
static unsigned long migration_cost(struct task_struct *p, int src_cpu, int dst_cpu)
{
int src_node = cpu_to_node(src_cpu);
int dst_node = cpu_to_node(dst_cpu);
if (src_node == dst_node) {
return 10; /* 同节点:成本低 */
} else {
return 100; /* 跨节点:成本高 */
}
}
第三章:容器支持
3.1 cgroups 资源限制
cgroups 架构
/cgroup/cpu/
├── container1/
│ ├── cpu.cfs_quota_us # CPU 配额
│ ├── cpu.cfs_period_us # CPU 周期
│ └── tasks # 任务列表
└── container2/
├── cpu.cfs_quota_us
├── cpu.cfs_period_us
└── tasks
CPU 带宽控制
// kernel/sched/cgroups.c
struct cfs_bandwidth {
raw_spinlock_t lock;
ktime_t period; /* 周期 (100ms) */
u64 quota; /* 配额 (50ms = 50% CPU) */
u64 runtime; /* 剩余运行时间 */
};
static void account_cfs_rq_runtime(struct cfs_rq *cfs_rq, u64 delta_exec)
{
struct cfs_bandwidth *cfs_b = &cfs_rq->tg->cfs_bandwidth;
/* 检查带宽限制 */
if (cfs_b->quota != RUNTIME_INF) {
cfs_rq->runtime_remaining -= delta_exec;
if (cfs_rq->runtime_remaining <= 0) {
/* 带宽用尽,节流任务 */
throttle_cfs_rq(cfs_rq);
}
}
}
3.2 容器调度隔离
任务组调度
// kernel/sched/fair.c
struct task_group {
struct cfs_rq **cfs_rq; /* 每 CPU CFS 队列 */
struct sched_entity **se; /* 每 CPU 调度实体 */
struct cfs_bandwidth cfs_bandwidth;
};
static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int flags)
{
struct sched_entity *se = &p->se;
/* 从叶节点到根节点入队 */
for_each_sched_entity(se) {
struct cfs_rq *cfs_rq = cfs_rq_of(se);
enqueue_entity(cfs_rq, se, flags);
}
}
层次化带宽分配
static int tg_set_cfs_quota(struct task_group *tg, s64 quota)
{
struct cfs_bandwidth *cfs_b = &tg->cfs_bandwidth;
u64 period = ktime_to_ns(cfs_b->period);
if (quota < 0) {
cfs_b->quota = RUNTIME_INF; /* 无限制 */
} else {
cfs_b->quota = quota;
/* 检查层次配额 */
if (tg->parent) {
struct cfs_bandwidth *parent_b = &tg->parent->cfs_bandwidth;
if (quota > parent_b->quota)
return -EINVAL; /* 子组配额不能超过父组 */
}
}
return 0;
}
3.3 容器性能隔离
CPU 亲和性隔离
// 设置容器 CPU 亲和性
echo 0-3 > /cgroup/cpuset/container1/cpuset.cpus
echo 0 > /cgroup/cpuset/container1/cpuset.mems
内存带宽隔离
// Intel RDT (Resource Director Technology)
echo "0=7f;1=7f" > /sys/fs/resctrl/schemata
echo $$ > /sys/fs/resctrl/container1/tasks
第四章:调度器调试与性能分析
4.1 调度器状态监控
/proc/sched_debug
// kernel/sched/debug.c
static int sched_debug_show(struct seq_file *m, void *v)
{
int cpu;
for_each_online_cpu(cpu) {
struct rq *rq = cpu_rq(cpu);
seq_printf(m, "cpu#%d\n", cpu);
seq_printf(m, " .nr_running : %lu\n", rq->nr_running);
seq_printf(m, " .load : %lu\n", rq->load.weight);
seq_printf(m, " .cfs_rq[0]\n");
seq_printf(m, " .exec_clock : %llu\n", rq->cfs.exec_clock);
seq_printf(m, " .min_vruntime : %llu\n", rq->cfs.min_vruntime);
seq_printf(m, " .nr_running : %lu\n", rq->cfs.nr_running);
}
return 0;
}
任务级统计
// /proc/[pid]/sched
se.exec_start : 123456789.123456
se.vruntime : 1000000000.000000
se.sum_exec_runtime : 500000000.000000
nr_switches : 12345
4.2 性能分析工具
perf sched
# 记录调度事件
perf record -e sched:sched_wakeup,sched:sched_switch -a sleep 10
# 分析调度延迟
perf script -i perf.data | perf sched latency
# 输出示例
# task 1: 1024.557 ms
# task 2: 512.234 ms
调度火焰图
# 生成调度火焰图
perf record -g -e sched:sched_switch -a sleep 30
perf script | ./stackcollapse-perf.pl | ./flamegraph.pl > sched.svg
4.3 调度器参数调优
关键参数
| 参数 | 默认值 | 作用 | 调优建议 | |——|——–|——|———-| | sched_migration_cost | 500μs | 迁移成本阈值 | NUMA 系统:增大 | | sched_latency | 6ms | 调度周期 | 交互式系统:减小 | | numa_balancing_scan_size | 256MB | NUMA 扫描大小 | 大内存系统:增大 |
动态调优
// kernel/sched/tune.c
void dynamic_sched_tuning(void)
{
unsigned long load = get_system_load();
int numa_nodes = num_online_nodes();
if (numa_nodes > 1) {
/* NUMA 系统:增大迁移成本 */
sysctl_sched_migration_cost = 2000; /* 2ms */
}
if (load < 0.3) {
/* 低负载:提高响应性 */
sysctl_sched_latency = 3;
} else if (load > 0.8) {
/* 高负载:提高吞吐量 */
sysctl_sched_latency = 12;
}
}
第五章:sched_ext 高级用例
5.1 AI 驱动的调度策略
机器学习调度器架构
+------------------+
| 特征提取器 | // 从任务行为提取特征
+------------------+
| 在线推理引擎 | // 实时预测最优调度
+------------------+
| 调度执行器 | // 应用预测结果
+------------------+
特征提取
// sched_ext 程序
SEC("sched")
int ml_feature_extract(struct task_struct *task)
{
struct task_features features = {};
/* 1. CPU 使用率 */
features.cpu_usage = task->se.sum_exec_runtime / (bpf_ktime_get_ns() - task->start_time);
/* 2. I/O 行为 */
features.io_ratio = task->io_wait_time / task->se.sum_exec_runtime;
/* 3. 内存访问模式 */
features.cache_miss_rate = get_cache_miss_rate(task);
/* 4. 通信模式 */
features.comm_frequency = get_communication_frequency(task);
/* 存储特征 */
bpf_map_update_elem(&task_features_map, &task->pid, &features, BPF_ANY);
return 0;
}
在线推理
SEC("sched")
int ml_inference(struct sched_ext_run_ctx *ctx)
{
struct task_struct *task = ctx->current;
struct task_features *features;
int predicted_priority;
/* 1. 获取特征 */
features = bpf_map_lookup_elem(&task_features_map, &task->pid);
if (!features)
return -1;
/* 2. 简单规则推理 */
if (features->io_ratio > 0.5 && features->cpu_usage < 0.1) {
predicted_priority = INTERACTIVE_PRIORITY; /* 交互式 */
} else if (features->cpu_usage > 0.8) {
predicted_priority = BATCH_PRIORITY; /* 批处理 */
} else {
predicted_priority = NORMAL_PRIORITY; /* 普通 */
}
/* 3. 应用预测结果 */
task->priority = predicted_priority;
return task->pid;
}
5.2 数据库专用调度器
问题场景优化
SEC("sched")
int db_scheduler(struct sched_ext_run_ctx *ctx)
{
struct task_struct *task = ctx->current;
u64 now = bpf_ktime_get_ns();
/* 1. 识别短查询 */
if (task->query_type == QUERY_SHORT &&
task->expected_runtime < SHORT_QUERY_THRESHOLD) {
/* 短查询:最高优先级,立即调度 */
bpf_map_push_elem(&short_query_queue, task, BPF_ANY);
return task->pid;
}
/* 2. 长查询并发控制 */
if (task->query_type == QUERY_LONG) {
if (bpf_map_len(&long_query_queue) < MAX_CONCURRENT_QUERIES) {
bpf_map_push_elem(&long_query_queue, task, BPF_ANY);
return task->pid;
}
/* 超过并发限制:暂时阻塞 */
return -1;
}
return -1;
}
5.3 实时音视频调度器
截止时间保证
SEC("sched")
int av_scheduler(struct sched_ext_run_ctx *ctx)
{
struct task_struct *task = ctx->current;
u64 now = bpf_ktime_get_ns();
/* 1. 音频任务:严格截止时间 */
if (task->task_type == TASK_AUDIO) {
if (now < task->deadline) {
return task->pid; /* 按时完成 */
} else {
/* 错过截止时间:丢弃或降级 */
handle_missed_deadline(task);
return -1;
}
}
/* 2. 视频任务:帧率保证 */
if (task->task_type == TASK_VIDEO) {
if (now - task->last_frame_time > FRAME_INTERVAL) {
return task->pid; /* 需要新帧 */
}
}
return -1;
}
第六章:企业级调度最佳实践
6.1 混合工作负载调度
策略组合
- 实时任务:使用 EDF 或 RMS
- 交互式任务:使用 CFS + 低延迟优化
- 批处理任务:使用 CFS + 高吞吐量优化
- 容器任务:使用 cgroups 隔离
优先级层次
1. 实时任务 (SCHED_FIFO/SCHED_RR)
2. 系统关键任务 (migration, ksoftirqd)
3. 交互式任务 (nice -20 ~ 0)
4. 普通任务 (nice 0)
5. 批处理任务 (nice 1 ~ 19)
6. 容器任务 (cgroups 限制)
6.2 NUMA 感知部署
部署策略
- 数据库:CPU 和内存绑定到同一 NUMA 节点
- Web 服务器:跨 NUMA 节点部署以利用更多资源
- HPC 应用:MPI 进程绑定到特定 NUMA 节点
配置示例
# 数据库 NUMA 绑定
numactl --cpunodebind=0 --membind=0 /usr/bin/mysqld
# Web 服务器跨节点
numactl --interleave=all /usr/bin/nginx
# HPC 进程绑定
mpirun --bind-to core --map-by node ./mpi_app
6.3 节能与性能平衡
动态策略
- 白天:高性能模式,保证用户体验
- 夜间:节能模式,降低运营成本
- 突发负载:临时切换到高性能模式
自动化脚本
#!/bin/bash
# 根据时间自动调整调度策略
HOUR=$(date +%H)
if [ $HOUR -ge 9 ] && [ $HOUR -le 18 ]; then
# 工作时间:高性能
echo 0 > /sys/devices/system/cpu/cpufreq/policy0/energy_performance_preference
else
# 非工作时间:节能
echo power > /sys/devices/system/cpu/cpufreq/policy0/energy_performance_preference
fi
结论:企业级调度的艺术
现代调度器已从简单的任务分配器,
演变为系统资源的智能优化器。
通过本文的深入分析,
我们构建了一个完整的企业级调度框架,
涵盖了:
- 节能调度:动态频率调节和核休眠
- NUMA 亲和性:本地内存优先和拓扑感知
- 容器支持:cgroups 资源限制和隔离
- 性能分析:调试工具和参数调优
- 可编程调度:AI 驱动的高级用例
真正的企业级调度,
不仅需要技术深度,
更需要对业务场景的深刻理解。
调度的艺术,
在于在性能、能耗、成本、可靠性之间找到最优平衡点。