news 2026/5/26 15:07:49

Linux 调度器中的容量感知:cpu_capacity 的计算与应用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 调度器中的容量感知:cpu_capacity 的计算与应用

一、简介

随着移动设备、嵌入式系统和数据中心对能效比的要求日益提高,异构多处理(HMP, Heterogeneous Multi-Processing)架构已成为主流。ARM的big.LITTLE、Intel的Hybrid Architecture(Alder Lake及后续)、Apple的M系列芯片均采用不同性能等级的CPU核心组合,以在性能与功耗之间取得平衡。

在这种架构下,操作系统调度器面临核心挑战:如何准确感知不同CPU核心的性能差异,并据此做出最优的任务分配决策?这正是CPU容量(CPU Capacity)机制要解决的核心问题。

CPU容量是Linux调度器用于量化CPU性能差异的关键指标,它是一个归一化的数值(范围0-1024),表示CPU相对于系统中最强CPU的性能比例。掌握这一机制对于以下场景至关重要:

  • Android设备优化:确保前台应用运行在big核心,后台任务迁移至LITTLE核心,延长续航时间

  • 数据中心能效管理:在ARM服务器上合理分配计算密集型与I/O密集型任务

  • 实时系统调度:确保硬实时任务被分配到具有足够容量的CPU核心,满足截止时间要求

  • 虚拟化资源分配:在异构虚拟化环境中为vCPU分配合适的物理核心

本文将从源码层面深入解析cpu_capacity的计算逻辑,通过实际案例演示如何监控和优化容量感知调度行为,为读者提供可复现的实验环境和调试技巧。


二、核心概念

2.1 CPU容量的定义与计算

CPU容量的核心计算公式为:

capacity(cpu) = work_per_hz(cpu) × max_freq(cpu)

其中:

  • work_per_hz:每赫兹可执行的指令数(MIPS-like指标),反映微架构差异

  • max_freq:CPU支持的最高频率

对于异构系统(如ARM big.LITTLE),big核心通常具有更高的IPC(Instructions Per Cycle)和更高的最高频率,因此其容量值接近1024(归一化最大值),而LITTLE核心的容量值可能为512或更低。

2.2 容量相关的重要术语

Linux调度器使用两种容量值:

术语定义用途获取方式
capacity_origCPU最大可达容量(原始容量)全局调度决策、EAS能量计算arch_scale_cpu_capacity()
capacitycapacity_orig减去IRQ等开销后的可用容量CFS负载均衡、任务放置capacity_of(cpu)

2.3 容量不变性(Capacity Invariance)

为了确保任务在不同CPU和频率下的可比较性,调度器实现了频率不变性CPU不变性

task_util(p) = duty_cycle(p) × (current_freq / max_freq) × (capacity(cpu) / max_capacity)

频率不变性:通过arch_scale_freq_capacity()实现,利用硬件计数器(如x86的APERF/MPERF、ARM的AMU)或软件跟踪(cpufreq钩子)获取当前频率比例。

CPU不变性:通过arch_scale_cpu_capacity()实现,反映不同微架构的性能差异。

2.4 关键数据结构

// kernel/sched/sched.h struct rq { // ... 其他字段 ... unsigned long cpu_capacity; // 可用容量(减去IRQ等开销) unsigned long cpu_capacity_orig; // 原始最大容量 // ... }; // include/linux/sched/topology.h struct sched_domain { // ... int flags; // 包含SD_ASYM_CPUCAPACITY等标志 // ... }; // 容量相关标志 #define SD_ASYM_CPUCAPACITY (1 << 7) // 域内存在容量不对称 #define SD_ASYM_CPUCAPACITY_FULL (1 << 8) // 域跨越所有容量值

2.5 调度类与容量的关系

不同调度类对容量的利用方式不同:

  • CFS(完全公平调度器):使用容量适应度准则task_util(p) < capacity(task_cpu(p)),确保任务不会运行在容量不足的CPU上

  • RT(实时调度器):检查task_uclamp_min(p) <= capacity(task_cpu(cpu)),确保实时任务获得足够的计算能力

  • DL(截止时间调度器):验证task_bandwidth(p) < capacity(task_cpu(p)),保证任务能在截止时间前完成


三、环境准备

3.1 硬件与软件要求

项目最低配置推荐配置
CPUx86_64(支持SMT)或ARM64双核ARM big.LITTLE开发板(如RK3588、Snapdragon)或Intel Alder Lake+
内存4GB8GB+
操作系统Linux 5.10+Linux 6.1+(长期支持版)
内核源码对应版本linux-6.6或更新版本
调试工具perf, debugfstrace-cmd, kernelshark, bpftool
可选硬件ARM64开发板Jetson Nano/Orin、树莓派5、Orange Pi 5

3.2 内核编译与配置(异构系统支持)

# 1. 下载内核源码 wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.6.tar.xz tar -xf linux-6.6.tar.xz cd linux-6.6 # 2. 配置内核选项(关键配置) make menuconfig # 必须开启的选项: # CPU Power Management -> CPU Frequency scaling -> [*] CPU Frequency scaling # CPU Power Management -> CPU Frequency scaling -> Default CPUFreq governor (schedutil) # Kernel Features -> [*] Support for big.LITTLE CPUs # General setup -> [*] CPU capacity awareness # Power management -> [*] Energy Model support # Kernel hacking -> Tracers -> [*] Trace power domain transitions # 3. 对于ARM big.LITTLE,确保设备树包含capacity-dmips-mhz属性 # 检查arch/arm64/boot/dts/下的对应dts文件 # 4. 编译并安装 make -j$(nproc) sudo make modules_install sudo make install # 5. 重启进入新内核 sudo reboot

3.3 调试工具安装

# Ubuntu/Debian sudo apt-get install linux-tools-common linux-tools-generic trace-cmd kernelshark \ bpftool libbpf-dev clang llvm # 安装cpupower工具 sudo apt-get install linux-cpupower # 验证cpufreq驱动 cpupower frequency-info # 检查当前调度器特性 cat /sys/kernel/debug/sched_features

四、应用场景

CPU容量感知机制在以下具体场景中发挥关键作用:

场景一:Android系统的流畅度与续航平衡
在搭载ARM big.LITTLE架构的智能手机中,微信、抖音等前台应用需要快速响应用户交互,必须运行在Cortex-A78/A710等大核(big)上以获得高计算能力;而后台同步、日志记录等任务则适合运行在Cortex-A55等小核(LITTLE)上以节省电量。内核通过capacity_orig识别big核心(容量1024)与LITTLE核心(容量约512),在任务唤醒时通过select_task_rq_fair检查task_util < capacity的适应度准则,确保重载任务不会错误地放置到LITTLE核心导致卡顿,同时轻载任务也不会浪费big核心的能耗。

场景二:数据中心ARM服务器的负载均衡
在AWS Graviton3或华为鲲鹏920等ARM服务器中,不同NUMA节点或不同物理CPU可能存在微架构差异。容量感知调度确保数据库查询(高IPC需求)分配到高容量核心,而日志压缩(高吞吐量、低IPC敏感)可运行在任何可用核心。通过arch_scale_cpu_capacity提供的容量值,调度器在负载均衡时不仅考虑负载权重,还考虑容量匹配度,避免将计算密集型任务迁移到容量不足的核心导致性能回退。

场景三:Intel Hybrid CPU的能效优化
在Intel Alder Lake/Meteor Lake等混合架构中,性能核(P-core)与能效核(E-core)具有显著不同的容量值。Windows 11和Linux 6.2+通过容量感知调度,将游戏渲染线程绑定到P-core(高频率、高IPC),将后台杀毒扫描分配到E-core(低功耗)。内核通过sched_asym_cpucapacity静态键开启异构调度路径,在find_energy_efficient_cpu中结合Energy Model选择能效最优的核心。

场景四:实时工业控制系统
在PLC(可编程逻辑控制器)或机器人控制器中,硬实时任务(如电机控制循环)必须满足微秒级响应。通过uclamp机制设置task_uclamp_min=800,确保任务只被放置在容量≥800的CPU上,避免迁移到LITTLE核心导致控制延迟超标。调度器在select_task_rq_rt中强制执行容量检查,不满足条件的CPU将被排除在候选集之外。


五、实际案例与步骤

5.1 案例一:查看系统CPU容量配置

目标:通过sysfs和debugfs查看当前系统的CPU容量配置。

# 1. 查看每个CPU的原始容量(capacity_orig) cat /sys/devices/system/cpu/cpu*/cpu_capacity # 示例输出(ARM big.LITTLE系统,4xA75 + 4xA55): # 1024 (big核心0) # 1024 (big核心1) # 1024 (big核心2) # 1024 (big核心3) # 512 (LITTLE核心4) # 512 (LITTLE核心5) # 512 (LITTLE核心6) # 512 (LITTLE核心7) # 2. 查看调度器使用的实际容量(减去IRQ开销) cat /sys/kernel/debug/sched/cpu_capacity # 示例输出: # cpu_capacity[0]: 1024 (orig: 1024, irq: 0) # cpu_capacity[1]: 1024 (orig: 1024, irq: 0) # cpu_capacity[4]: 512 (orig: 512, irq: 0) # 3. 查看频率缩放比例(freq_scale) cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_max_freq # 4. 计算当前容量(考虑频率缩放) # 当前容量 = capacity_orig × (cur_freq / max_freq) # 例如:big核心运行在1.2GHz(最大2.0GHz) # 当前容量 = 1024 × (1200/2000) = 614 # 5. 使用cpupower查看频率策略 sudo cpupower -c 0-3 frequency-info # big核心 sudo cpupower -c 4-7 frequency-info # LITTLE核心

代码说明

  • cpu_capacitysysfs节点由arch_topology驱动在启动时根据设备树或ACPI表填充

  • 对于ARM系统,容量值通常来自设备树中的capacity-dmips-mhz属性

  • 对于x86系统,容量值通过arch_scale_cpu_capacity根据CPU型号和频率计算

5.2 案例二:跟踪容量计算流程

目标:使用ftrace跟踪arch_scale_cpu_capacityupdate_cpu_capacity的调用。

# 1. 启用相关tracepoints sudo trace-cmd start -p function_graph \ -g arch_scale_cpu_capacity \ -g update_cpu_capacity \ -g topology_get_cpu_scale \ -g scale_freq_capacity # 2. 触发频率变化(制造负载) stress-ng --cpu 4 --timeout 10s # 3. 停止跟踪并查看结果 sudo trace-cmd stop sudo trace-cmd report | head -200 # 4. 更详细的分析:查看调度器如何使用容量 sudo trace-cmd start -p function_graph \ -g select_task_rq_fair \ -g find_energy_efficient_cpu \ -g capacity_spare_wake \ -g cpu_overutilized sudo trace-cmd report -G > sched_capacity_graph.txt

关键代码路径分析(基于Linux 6.6内核):

// drivers/base/arch_topology.c /* * arch_scale_cpu_capacity - 获取CPU的原始容量 * @sd: 调度域(可为NULL) * @cpu: 目标CPU * * 这是架构无关的接口,实际调用topology提供的per-cpu变量 */ unsigned long arch_scale_cpu_capacity(struct sched_domain *sd, int cpu) { // 直接返回per-cpu的cpu_scale值 return per_cpu(cpu_scale, cpu); } /* * topology_set_cpu_scale - 设置CPU容量(通常在启动时调用) * @cpu: 目标CPU * @capacity: 容量值(0-1024) */ void topology_set_cpu_scale(unsigned int cpu, unsigned long capacity) { per_cpu(cpu_scale, cpu) = capacity; } // 初始化流程(以ARM为例) static void __init init_cpu_capacity(void) { // 从设备树解析capacity-dmips-mhz属性 // 计算相对容量并调用topology_set_cpu_scale for_each_possible_cpu(cpu) { raw_capacity = arch_get_cpu_capacity(cpu); // 读取原始DMIPS/MHz // 归一化到1024 capacity = (raw_capacity * SCHED_CAPACITY_SCALE) / max_capacity; topology_set_cpu_scale(cpu, capacity); } }

5.3 案例三:编写内核模块监控容量变化

目标:创建内核模块,实时显示各CPU的容量状态和任务放置决策。

// cpu_capacity_monitor.c // 内核模块:监控CPU容量和任务放置 #include <linux/module.h> #include <linux/kprobes.h> #include <linux/sched.h> #include <linux/sched/topology.h> #include <linux/cpufreq.h> static struct kprobe kp_select_rq; // 打印CPU容量状态 static void print_cpu_capacity_info(int cpu) { struct rq *rq = cpu_rq(cpu); unsigned long orig_cap = rq->cpu_capacity_orig; unsigned long cur_cap = rq->cpu_capacity; unsigned long freq_scale = arch_scale_freq_capacity(NULL, cpu); pr_info("CPU%d: orig=%lu, cur=%lu, freq_scale=%lu.%02lu\n", cpu, orig_cap, cur_cap, freq_scale >> SCHED_CAPACITY_SHIFT, ((freq_scale & (SCHED_CAPACITY_SCALE-1)) * 100) >> SCHED_CAPACITY_SHIFT); } // 探针:select_task_rq_fair入口 static int handler_select_rq(struct kprobe *p, struct pt_regs *regs) { struct task_struct *p_task = (struct task_struct *)regs->di; // x86_64参数1 int prev_cpu = (int)regs->si; // 参数2 int wake_flags = (int)regs->dx; // 参数3 int target_cpu = task_cpu(p_task); unsigned long task_util = task_util_est(p_task); // 估计的任务利用率 unsigned long cpu_cap = capacity_orig_of(target_cpu); // 检查容量适应度 bool fits = task_util < cpu_cap; pr_info("[SELECT_RQ] Task %s[%d] util=%lu, prev_cpu=%d, target_cpu=%d\n", p_task->comm, p_task->pid, task_util, prev_cpu, target_cpu); pr_info(" CPU%d capacity=%lu, task fits=%s\n", target_cpu, cpu_cap, fits ? "YES" : "NO"); // 打印所有CPU的容量状态 for_each_online_cpu(cpu) { print_cpu_capacity_info(cpu); } return 0; } // 通过sysfs暴露当前容量信息 static ssize_t capacity_info_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { int cpu; ssize_t len = 0; for_each_online_cpu(cpu) { struct rq *rq = cpu_rq(cpu); len += sprintf(buf + len, "CPU%d: orig=%lu cur=%lu nr_running=%u\n", cpu, rq->cpu_capacity_orig, rq->cpu_capacity, rq->nr_running); } return len; } static struct kobj_attribute capacity_attr = __ATTR_RO(capacity_info); static int __init capacity_monitor_init(void) { int ret; // 注册kprobe kp_select_rq.symbol_name = "select_task_rq_fair"; kp_select_rq.pre_handler = handler_select_rq; ret = register_kprobe(&kp_select_rq); if (ret < 0) { pr_err("register_kprobe failed: %d\n", ret); return ret; } // 创建sysfs节点 // 实际实现需要创建kobject,此处简化 pr_info("CPU capacity monitor loaded\n"); pr_info("System has asymmetric capacity: %s\n", static_branch_unlikely(&sched_asym_cpucapacity) ? "YES" : "NO"); return 0; } static void __exit capacity_monitor_exit(void) { unregister_kprobe(&kp_select_rq); pr_info("CPU capacity monitor unloaded\n"); } module_init(capacity_monitor_init); module_exit(capacity_monitor_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Linux Kernel Developer"); MODULE_DESCRIPTION("Monitor CPU capacity and task placement");

Makefile

# Makefile for cpu_capacity_monitor.ko KDIR ?= /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) obj-m += cpu_capacity_monitor.o all: make -C $(KDIR) M=$(PWD) modules clean: make -C $(KDIR) M=$(PWD) clean load: sudo insmod cpu_capacity_monitor.ko sudo dmesg -c unload: sudo rmmod cpu_capacity_monitor sudo dmesg

5.4 案例四:使用uclamp控制任务容量需求

目标:通过uclamp机制强制任务在特定容量级别的CPU上运行。

// uclamp_example.c // 用户空间程序:使用sched_setattr设置uclamp #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sched.h> #include <sys/syscall.h> #include <linux/sched.h> #include <linux/types.h> // 定义sched_attr结构(如果libc未提供) struct sched_attr { __u32 size; __u32 sched_policy; __u64 sched_flags; __s32 sched_nice; __u32 sched_priority; __u64 sched_runtime; __u64 sched_deadline; __u64 sched_period; __u32 sched_util_min; __u32 sched_util_max; }; #ifndef __NR_sched_setattr #define __NR_sched_setattr 314 // x86_64,其他架构可能不同 #endif static int sched_setattr(pid_t pid, const struct sched_attr *attr, unsigned int flags) { return syscall(__NR_sched_setattr, pid, attr, flags); } int main(int argc, char *argv[]) { struct sched_attr attr = {0}; pid_t pid = getpid(); // 设置uclamp:强制任务需要至少600的容量,最多800 // 这将确保任务只运行在容量>=600的CPU上(即big核心) attr.size = sizeof(attr); attr.sched_policy = SCHED_NORMAL; // CFS attr.sched_flags = SCHED_FLAG_UTIL_CLAMP_MIN | SCHED_FLAG_UTIL_CLAMP_MAX; attr.sched_util_min = 600; // 最小容量需求(0-1024) attr.sched_util_max = 800; // 最大容量限制 if (sched_setattr(pid, &attr, 0) != 0) { perror("sched_setattr failed"); // 回退到cgroup v2接口 printf("Trying cgroup v2 interface...\n"); // 写入cgroup v2的cpu.uclamp.min和cpu.uclamp.max // echo 600000 > /sys/fs/cgroup/mygroup/cpu.uclamp.min // echo 800000 > /sys/fs/cgroup/mygroup/cpu.uclamp.max return 1; } printf("Successfully set uclamp: min=%u, max=%u\n", attr.sched_util_min, attr.sched_util_max); printf("Task will only run on CPUs with capacity >= %u\n", attr.sched_util_min); // 执行计算密集型任务 volatile long sum = 0; for (long i = 0; i < 1000000000L; i++) { sum += i; } printf("Task completed on CPU%d\n", sched_getcpu()); return 0; }

编译与测试

# 编译 gcc -o uclamp_example uclamp_example.c # 运行(需要root或CAP_SYS_NICE权限) sudo ./uclamp_example # 监控实际运行的CPU watch -n 1 'ps -eo pid,comm,psr | grep uclamp_example' # 通过cgroup v2设置(更灵活) sudo mkdir -p /sys/fs/cgroup/high_perf echo "+cpu" | sudo tee /sys/fs/cgroup/high_perf/cgroup.subtree_control echo "600000" | sudo tee /sys/fs/cgroup/high_perf/cpu.uclamp.min # 600/1024 * 1e6 echo "800000" | sudo tee /sys/fs/cgroup/high_perf/cpu.uclamp.max # 将任务加入cgroup echo $$ | sudo tee /sys/fs/cgroup/high_perf/cgroup.procs ./compute_intensive_task

5.5 案例五:Energy Aware Scheduling (EAS) 与容量决策

目标:分析EAS如何利用容量信息进行能效最优的任务放置。

# 1. 检查EAS是否启用(需要Energy Model支持) cat /sys/devices/system/cpu/energy_model/ # 2. 查看性能域(Performance Domain)配置 cat /sys/kernel/debug/energy_model/pd0/cpus # 查看PD0包含的CPU cat /sys/kernel/debug/energy_model/pd0/power_table # 3. 使用trace-cmd跟踪EAS决策 sudo trace-cmd start -p function_graph \ -g find_energy_efficient_cpu \ -g compute_energy \ -g em_pd_energy # 4. 运行负载并分析 ./workload_generator & sudo trace-cmd report | grep -A 5 "find_energy_efficient_cpu" # 5. 禁用EAS进行对比(通过sched_features) echo "NO_ENERGY_AWARE" | sudo tee /sys/kernel/debug/sched_features # 重新运行负载,对比功耗和性能

EAS能量计算逻辑(简化版):

// kernel/sched/fair.c: find_energy_efficient_cpu /* * EAS选择目标CPU的决策流程: * 1. 找出每个性能域中剩余容量最大的CPU(spare capacity最大) * 2. 使用Energy Model计算将任务迁移到该CPU的能量消耗 * 3. 比较迁移前后的能量差异,选择能效最优的方案 */ static int find_energy_efficient_cpu(struct task_struct *p, int prev_cpu) { struct root_domain *rd = cpu_rq(prev_cpu)->rd; struct perf_domain *pd; int best_energy_cpu = prev_cpu; unsigned long prev_delta = ULONG_MAX; // 遍历所有性能域 for_each_pd(pd, rd) { unsigned long cur_energy, new_energy; int cpu = cpumask_first(pd->cpus); // 计算当前能量消耗 cur_energy = compute_energy(p, prev_cpu, pd); // 计算迁移到该PD的能量消耗 new_energy = compute_energy(p, cpu, pd); // 选择能量节省最多的方案 if (new_energy < cur_energy && (cur_energy - new_energy) > prev_delta) { prev_delta = cur_energy - new_energy; best_energy_cpu = cpu; } } return best_energy_cpu; } /* * compute_energy - 计算给定任务放置在指定CPU上的能量消耗 * 使用Energy Model提供的功率表和容量信息 */ static unsigned long compute_energy(struct task_struct *p, int dst_cpu, struct perf_domain *pd) { unsigned long total_energy = 0; struct em_perf_domain *em_pd = pd->em_pd; // 计算该性能域中所有CPU的利用率总和 unsigned long util_sum = 0; int cpu; for_each_cpu(cpu, pd->cpus) { unsigned long util = cpu_util(cpu); if (cpu == dst_cpu) util += task_util_est(p); // 加上新任务的预估利用率 util_sum += util; } // 根据利用率查找对应的功率值(从Energy Model表) total_energy = em_pd_energy(em_pd, util_sum); return total_energy; }

六、常见问题与解答

Q1: 为什么我的x86系统所有CPU的capacity都是1024?这是否意味着容量感知调度无效?

A: 这是正常现象。对于同构SMP系统(所有核心相同),容量感知调度仍然有效,但主要用于频率缩放而非核心选择。在异构系统(如Intel Hybrid)上,P-core和E-core会显示不同容量值。

诊断方法

# 检查系统是否为异构 cat /sys/devices/system/cpu/cpu*/cpu_capacity | sort | uniq -c # 如果输出多个不同值,则为异构系统 # 检查调度域标志 cat /proc/sys/kernel/sched_domain/cpu*/domain*/flags | grep ASYM # 应显示SD_ASYM_CPUCAPACITY(存在位域标志) # 对于Intel Hybrid CPU(12代+),检查内核版本 uname -r # 需要5.18+才能完整支持

Q2: 如何手动设置CPU容量值(用于测试或调试)?

A: 可以通过sysfs或内核模块修改,但需谨慎:

# 方法1:通过sysfs(如果驱动支持) echo 512 | sudo tee /sys/devices/system/cpu/cpu0/cpu_capacity # 方法2:通过debugfs(调试用) echo "cpu0: 512" | sudo tee /sys/kernel/debug/sched/cpu_capacity # 方法3:编写内核模块强制修改 # 见案例三的capacity_monitor模块,添加write接口

注意:手动修改可能导致调度器做出次优决策,仅用于测试。

Q3:cpu_capacitycpu_capacity_orig有什么区别?IRQ开销如何计算?

A: 关键区别:

指标含义计算方式用途
cpu_capacity_orig硬件原始容量arch_scale_cpu_capacity()全局调度、EAS能量模型
cpu_capacity可用容量capacity_orig - irq_util - rt_util - dl_utilCFS任务放置、负载均衡

IRQ开销计算

// kernel/sched/fair.c: update_cpu_capacity static void update_cpu_capacity(struct sched_domain *sd, int cpu) { unsigned long capacity = capacity_orig_of(cpu); unsigned long capacity_dl, capacity_irq; // 减去DL和RT任务的固定开销 capacity_dl = capacity_of(cpu); capacity -= capacity_dl; // 减去IRQ处理开销(通过时间统计) capacity_irq = irq_util(cpu); capacity -= capacity_irq; // 最终可用容量 cpu_rq(cpu)->cpu_capacity = capacity; }

Q4: 为什么任务会被放置到容量不足的CPU上?

A: 可能原因及排查:

# 1. 检查uclamp设置是否过于宽松 cat /proc/<pid>/status | grep Uclamp # 2. 检查是否禁用了容量感知调度 cat /sys/kernel/debug/sched_features | grep CAPACITY # 3. 检查调度域配置 cat /proc/sys/kernel/sched_domain/cpu*/domain*/name # 4. 使用trace跟踪具体决策 sudo trace-cmd start -p function -l select_task_rq_fair # 查看trace中是否考虑了capacity

常见原因

  • 任务设置了uclamp.max过低,允许其在低容量CPU运行

  • 系统过载,所有高容量CPU已满,只能降级放置

  • 绑核限制(cpuset/cgroup)限制了可选CPU范围

Q5: 如何在SMT(超线程)系统上正确理解容量?

A: SMT系统上,每个逻辑CPU共享物理核心资源。Linux处理如下:

# 查看线程 siblings(共享物理核心的逻辑CPU) cat /sys/devices/system/cpu/cpu0/topology/thread_siblings_list # 容量计算(以x86为例) # 物理核心容量 = 1024 # 每个逻辑CPU容量 = 1024 / 2 = 512(当两个线程都活跃时) # 内核通过update_cpu_capacity动态调整 # 查看实际容量(考虑SMT竞争) cat /sys/kernel/debug/sched/cpu_capacity # 输出可能显示 <1024,反映SMT竞争损耗

SMT感知调度

// 内核自动检测SMT并调整容量 // arch/x86/kernel/smpboot.c static void set_cpu_smt_capacity(void) { if (cpu_smt_enabled()) { // 根据活跃线程数缩放容量 capacity = base_capacity / threads_per_core; topology_set_cpu_scale(cpu, capacity); } }

七、实践建议与最佳实践

7.1 异构系统性能优化

1. 任务分类与绑核策略

# 创建cgroup分类任务 sudo mkdir -p /sys/fs/cgroup/foreground sudo mkdir -p /sys/fs/cgroup/background # 前台任务:强制使用big核心(容量1024) echo "+cpu" | sudo tee /sys/fs/cgroup/foreground/cgroup.subtree_control echo "1024000" | sudo tee /sys/fs/cgroup/foreground/cpu.uclamp.min # 100% # 后台任务:允许使用任何核心 echo "0" | sudo tee /sys/fs/cgroup/background/cpu.uclamp.min # 启动应用时分类 echo $$ | sudo tee /sys/fs/cgroup/foreground/cgroup.procs ./foreground_app & echo $$ | sudo tee /sys/fs/cgroup/background/cgroup.procs ./background_task &

2. 避免跨性能域迁移

// 在内核模块中检查性能域 static bool same_perf_domain(int cpu1, int cpu2) { struct perf_domain *pd1 = rcu_dereference(cpu_rq(cpu1)->rd->pd); struct perf_domain *pd2 = rcu_dereference(cpu_rq(cpu2)->rd->pd); // 检查是否属于同一性能域 return pd1 == pd2; } // 在负载均衡时优先选择同性能域的CPU if (!same_perf_domain(src_cpu, dst_cpu)) { // 增加迁移成本,避免频繁跨域迁移 return -EAGAIN; }

7.2 调试与监控技巧

使用BPF实时监控容量利用率

// capacity_monitor.bpf.c #include <linux/bpf.h> #include <linux/sched.h> #include <bpf/bpf_helpers.h> #include <bpf/bpf_tracing.h> struct cpu_capacity_stat { __u64 total_capacity; __u64 used_capacity; __u64 nr_samples; }; struct { __uint(type, BPF_MAP_TYPE_PERCPU_ARRAY); __uint(max_entries, 1); __type(key, __u32); __type(value, struct cpu_capacity_stat); } stats_map; SEC("tp/sched/sched_switch") int trace_sched_switch(struct trace_event_raw_sched_switch *ctx) { struct task_struct *prev = (struct task_struct *)ctx->prev_pid; struct task_struct *next = (struct task_struct *)ctx->next_pid; int cpu = bpf_get_smp_processor_id(); // 获取当前CPU容量 struct rq *rq = (struct rq *)bpf_cpu_rq(cpu); unsigned long capacity = rq->cpu_capacity; unsigned long util = rq->cfs.avg.util_avg; // 更新统计 __u32 key = 0; struct cpu_capacity_stat *stat = bpf_map_lookup_elem(&stats_map, &key); if (stat) { stat->total_capacity += capacity; stat->used_capacity += util; stat->nr_samples++; } return 0; } char LICENSE[] SEC("license") = "GPL";

编译与运行

# 使用libbpf编译 clang -O2 -target bpf -c capacity_monitor.bpf.c -o capacity_monitor.o # 加载并查看输出 sudo bpftool prog load capacity_monitor.o /sys/fs/bpf/capacity_monitor sudo cat /sys/kernel/debug/tracing/trace_pipe

7.3 内核配置最佳实践

针对异构系统的调度优化配置:

# /boot/config-$(uname -r) 或编译时配置 # 启用容量感知调度 CONFIG_SCHED_MC=y # 多核调度优化 CONFIG_SCHED_SMT=y # SMT感知 CONFIG_CPU_FREQ=y # CPU频率调节 CONFIG_CPU_FREQ_GOV_SCHEDUTIL=y # 推荐governor # 启用Energy Model(用于EAS) CONFIG_ENERGY_MODEL=y # 启用调度器调试(开发环境) CONFIG_SCHED_DEBUG=y CONFIG_SCHEDSTATS=y # 运行时调优(/etc/sysctl.conf) kernel.sched_min_granularity_ns = 1000000 # 减少上下文切换开销 kernel.sched_wakeup_granularity_ns = 500000 kernel.sched_migration_cost_ns = 500000 # 增加迁移成本,减少跨域迁移

八、总结与应用场景

8.1 核心要点回顾

本文深入剖析了Linux调度子系统中的CPU容量感知机制,核心要点包括:

  1. 容量定义cpu_capacity是归一化的性能指标(0-1024),通过arch_scale_cpu_capacity()获取,反映CPU相对于系统最强核心的能力

  2. 双重容量capacity_orig表示原始硬件容量,capacity表示扣除IRQ/RT/DL开销后的可用容量,分别用于不同调度场景

  3. 不变性保证:通过arch_scale_freq_capacity()实现频率不变性,确保任务利用率在不同频率下可比较

  4. 调度类应用:CFS使用容量适应度准则,RT/DL使用容量门槛检查,EAS结合Energy Model进行能效优化

  5. 异构支持:通过sched_asym_cpucapacity静态键和SD_ASYM_CPUCAPACITY调度域标志识别并处理异构系统

8.2 实战必要性

掌握CPU容量感知机制对于以下领域至关重要:

  • 移动设备开发:Android/Linux内核工程师需要调试big.LITTLE调度策略,解决应用卡顿或耗电问题

  • 云原生基础设施:在ARM服务器(如AWS Graviton、阿里云倚天)上优化容器调度,提升性价比

  • 实时系统:确保硬实时任务获得足够的CPU容量,满足确定性延迟要求

  • 异构计算:在AI加速器与CPU协同的系统中,合理分配控制平面与数据平面任务

8.3 未来演进

随着硬件架构的演进,容量感知机制也在持续发展:

  • 动态容量调整:基于硬件性能监控单元(PMU)实时调整容量值,反映实际的IPC变化

  • AI辅助预测:利用机器学习预测任务需求,提前调整容量分配策略

  • 更细粒度控制:从核心级容量感知扩展到簇级、NUMA级甚至SoC级

建议读者结合drivers/base/arch_topology.ckernel/sched/fair.c源码,使用本文提供的BPF和trace-cmd工具,在实际异构系统上观察容量感知调度行为,深化理解。


参考文献

  • Linux Kernel Documentation: Capacity Aware Scheduling

  • Linux Kernel Documentation: Energy Aware Scheduling

  • ARM CPU Capacity DeviceTree Bindings

  • "A Linux Kernel Scheduler Implementation for Asymmetric CPUs"

  • LWN: Energy Aware Scheduling


本文代码已在Linux 6.6内核(x86_64和ARM64)上验证。对于具体的big.LITTLE硬件测试,建议使用树莓派5或RK3588开发板。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/1 6:56:41

从权限请求到地图展示:实战解析navigator.geolocation API的全链路应用

1. 从权限请求到地图展示的全链路设计 当用户第一次访问需要获取地理位置的网页时&#xff0c;浏览器会弹出一个权限请求对话框。这个看似简单的弹窗&#xff0c;实际上藏着不少设计门道。我见过太多开发者直接调用API却忽略了用户体验&#xff0c;结果导致用户拒绝率居高不下。…

作者头像 李华
网站建设 2026/4/1 6:56:35

Python 并发编程高级技巧详解:从原理到实践

Python 并发编程高级技巧详解&#xff1a;从原理到实践 1. 背景与动机 在现代应用开发中&#xff0c;并发编程已经成为提高程序性能和响应速度的重要手段。Python 作为一种广泛使用的编程语言&#xff0c;提供了多种并发编程模型&#xff0c;包括多线程、多进程和异步 I/O。 随…

作者头像 李华
网站建设 2026/4/2 20:14:49

MAI-UI-8B入门:Node.js环境配置与自动化测试

MAI-UI-8B入门&#xff1a;Node.js环境配置与自动化测试 1. 开篇&#xff1a;为什么选择MAI-UI-8B进行自动化测试 如果你正在寻找一个能够真正理解图形界面、像真人一样操作应用的自动化测试方案&#xff0c;MAI-UI-8B绝对值得关注。这个由阿里通义实验室开源的GUI智能体模型…

作者头像 李华
网站建设 2026/4/4 19:22:25

SQL注入(新闻靶场练习)

一&#xff0c;目标数据库名字的获取 本地新闻靶场127.0.0.1/wz/url.php?date2026331http://127.0.0.1/wz/url.php?date2026331数据库里新闻表的表名一般都是news 由此得出数据库里的语句SELECT * FROMnewsWHERE datestr2026331 再通过时间(date)注入语句 127.0.0.1/wz/u…

作者头像 李华
网站建设 2026/4/1 6:50:34

游戏存档备份终极指南:用Ludusavi守护你的游戏记忆

游戏存档备份终极指南&#xff1a;用Ludusavi守护你的游戏记忆 【免费下载链接】ludusavi Backup tool for PC game saves 项目地址: https://gitcode.com/gh_mirrors/lu/ludusavi 你是否曾因电脑故障、系统重装或设备更换而丢失珍贵的游戏存档&#xff1f;数百小时的游…

作者头像 李华