ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux下CPU频率与核心数锁定:性能调优与低延迟场景实战指南

Linux下CPU频率与核心数锁定:性能调优与低延迟场景实战指南 做过Linux性能调优和低延迟场景的朋友应该都有过这种经历同一台服务器跑同样的任务结果一次快一次慢波动大得让人怀疑人生。后来才发现问题不在业务代码而是CPU频率和核心调度在“自作聪明”。默认情况下Linux的CPU频率会跟随负载动态跳变核心也会被调度器随意分配这在普通场景是好事但对性能测试、音视频采集、实时控制这类需要“确定性”的场景就是灾难。所以把CPU频率和核心数锁死让它别乱动就成了一个非常硬核且实用的需求。这篇文章就围绕标题里的“Linux下CPU频率和核心数的锁定设置”把我在服务器和嵌入式环境中常用到的几招全部讲透。适合对Linux有一定基础、想深入做性能优化或刚接触实时系统的朋友。我会从原理讲到实操再讲到踩坑记录尽量让你看完就能在自己的机器上动手。先说清楚所有操作都要root权限操作前务必备份重要数据和配置别拿生产环境直接开刀。1. 为什么要折腾CPU频率和核心数很多新手会问CPU自动调频、自动调度不是挺好吗为什么非要锁定这里有几个非常现实的场景我实际工作中都撞见过。1.1 哪些场景真的需要锁频锁核第一个场景是性能基准测试。不管是给新服务器选型还是验证代码优化效果你都希望数据可复现。可CPU频率一波动同样的代码跑三次可能出三个结果你根本分辨不出那是优化带来的提升还是频率波动造成的假象。锁频之后跑分数据才稳定可靠才有对比价值。第二个场景是低延迟和实时性要求高的任务比如音频采集、视频编码、工业控制。这些场景对“延迟抖动”极其敏感。CPU默认的ondemand或schedutil调速器在负载突增时响应有延迟核心间切换也会引入不可控的上下文切换开销。把核心固定下来把频率拉满能显著降低延迟毛刺。这个我实测过在线处理音频时锁核锁频后中断延迟能稳定很多。第三个场景是功耗和散热控制。某些设备或者服务器机房对整机功耗有严格限制。通过把频率锁在一个较低但够用的值把部分核心下线能压住峰值功耗和发热让风扇安静下来。虽然这不如调TDP那么精细但胜在Linux层直接可控不需要进BIOS。第四个场景有点偏门但很有意思——处理器本身存在体质差异或睿频能力虚标。有些CPU号称能睿频到很高实际一跑高负载就撞温度墙降频性能反而拉胯。手动锁一个保守但稳定的频率往往比让其自由睿频更高效。1.2 锁频锁核之前先搞清楚系统架构动手之前最重要的是搞清楚自己的硬件和内核驱动架构。Linux的CPU调频体系已经发展了很多年不同硬件平台对应的驱动和参数路径都不一样。早期Intel平台用的是acpi-cpufreq后来默认切到了intel_pstateAMD新平台则是amd-pstate。这些驱动决定了你看到哪些调速器governor、哪些参数节点。调速器就是系统用来决定“现在该跑多快”的策略。常见的包括performance维持最高频、powersave维持最低频、ondemand负载高就升频、conservative平滑调频、userspace用户手动指定、schedutil由调度器驱动。每个策略适用的场景不同锁频率最常用的是把governor改成performance或者更极端一点用userspace直接写死频率值。同时要理解“频率”和“核心数”是两个独立但互相影响的维度。锁定频率影响每个核心的算力上限锁定核心数影响可用的算力总量。比如你把频率锁到最高但核心数量少一半总性能可能还是上不去。反过来核心很多但频率被锁得很低单线程性能一样不满意。所以调试时要结合任务特性不能一刀切。2. 准备工作先读懂你的CPU在做什么锁频锁核本质上是改Linux内核暴露出来的接口所以第一步不是直接改而是把系统当前状态看清楚。这一步如果忽略后面往往会陷入“明明改了却没效果”的困境。2.1 查看CPU型号与拓扑架构打开终端最常用的就是lscpu。它能一次性列出CPU架构、核心数、线程数、型号、主频等信息。我一般会关注几项Architecture是x86_64还是aarch64不同架构的调频接口差别很大。CPU(s)逻辑核心总数。Thread(s) per core每个物理核几线程如果为2说明开了超线程。Core(s) per socket每颗CPU的物理核心数。Model name具体型号方便后续查TDP和睿频参数。CPU MHz当前实际运行频率注意这只是一个瞬时快照。如果还想看得更细可以用lscpu -e它会以表格形式列出每个逻辑核心对应的物理核心编号、socket编号等信息。这个在决定到底要下线哪些核心的时候特别有用。比如我要留4个核心我会尽量把同一个物理核心的两个超线程都保留避免出现一个物理核心只留一个线程这种浪费资源的情况。关于超线程要提醒一句很多性能敏感场景下超线程不仅帮不上忙反而会因为共享执行单元导致性能下降。我习惯在BIOS或内核层直接关掉超线程只保留物理核心。如果你不想改BIOS也可以用我们后面讲的isolcpus参数绕过部分逻辑核心。2.2 查看当前频率和可用调速器lscpu看到的频率只是一个结果真正要操作的是/sys/devices/system/cpu/这个虚拟文件系统。先看当前每个核心的调速器cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor正常你会看到一堆重复的字符串比如powersave或者schedutil。这说明所有核心用的都是同一个策略。再看一眼可用的调速器有哪些cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_available_governors不同驱动下这个列表差别很大。intel_pstate默认只给performance和powersave而acpi-cpufreq能看到ondemand、conservative、userspace等一整套。如果想用userspace硬锁频率就得确保scaling_available_governors里有userspace否则还得折腾驱动切换。频率本身的查看也有讲究。我比较推荐用cpupower工具它比直接读文件更直观cpupower frequency-info这个命令会显示硬件限制、系统限制、当前策略、当前频率等一长串信息。重点看“hardware limits”“available frequency steps”和“current policy”这几栏。这能帮你确认CPU到底支持哪些频率档位。有些新CPU是基频睿频的模式scaling_available_frequencies里可能只显示两三个档位这很正常。我见过不少新手看到scaling_cur_freq一直在变就怀疑系统坏了其实这是正常现象只要负载有波动调度器就会调频。别慌我们要做的就是让它停下来。3. 锁定CPU频率从“策略”到“硬锁”锁定频率的操作我个人建议按三个台阶来做。第一步是切到performance模式让系统稳定在最高频第二步是更进一步用userspace写死频率第三步是关掉睿频防止它在锁定的基础上继续“偷跳”。三步按顺序做完频率基本就彻底老实了。3.1 先切换到performance模式performance模式是“最短路径”的锁频方案。它不要求指定具体频率而是让CPU始终运行在可用的最高频率档位。因为不需要固频大多数驱动都支持改起来也最简单。用cpupower一条命令搞定所有核心cpupower frequency-set -g performance如果不想装cpupower也可以手动写文件for cpu in /sys/devices/system/cpu/cpu[0-9]*; do echo performance $cpu/cpufreq/scaling_governor done注意这里用了一个通配循环确保所有核心都被改到。改完重新读一下scaling_governor看看是不是都变过来了。有个细节某些平台下scaling_governor只有powersave一个值可选但你别慌intel_pstate的powersave其实不等于传统意义的降频策略它在负载上来时同样会睿频到接近最高频只是响应逻辑不一样。这种情况下想把频率彻底锁起来就得借助下一节的userspace方式。3.2 用userspace模式写死频率userspace是真正的“硬锁”系统不再根据负载自动调频而是听用户指定的频率。这个模式的实现方式是把governor切换到userspace再手动写入想要的频率值。先确认你的scaling_available_governors里包含userspace。如果有执行cpupower frequency-set -g userspace cpupower frequency-set -f 2.8GHz第一行切换策略第二行把频率固定在2.8GHz。这里的-f参数支持多种写法可以用3000000Hz、3.0G、2900MHz等。为了保险我一般直接写MHz数字比如2800MHz避免单位换算出问题。执行完后查看一下状态cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq正常情况下会稳定输出2800000。如果看到数值一直在2800000左右波动那基本就是成功了。但如果你发现这个频率值压根没生效继续往下看大概率和睿频或驱动模式有关。需要说明的是userspace能选择的频率不是任意的。它只能从scaling_available_frequencies里列出的档位里选。如果你写入一个中间频率驱动可能会自动就近匹配。这也是为什么很多人指定4.0GHz但实际跑出来是3.9或者4.1的原因。3.3 关闭睿频频率稳定性的最后一公里频率锁到了指定值真的就稳了吗不一定。Intel的Turbo Boost和AMD的Precision Boost是一层独立于调频governor的机制。它能在单核负载高时把频率进一步推高出发条件由硬件内部逻辑决定往往不受scaling_governor的控制。这就会导致你明明指定了2.8GHz在某个瞬间却看到频率飙到3.2GHz。关闭睿频最常用的方式是利用intel_pstate驱动的接口。如果你用的是Intel CPU执行echo 1 /sys/devices/system/cpu/intel_pstate/no_turbo如果这个文件不存在说明你的内核没启用intel_pstate或者驱动是acpi-cpufreq。这种情况下可以试试msr-tools但操作比较复杂。更省心的方法是从BIOS层面关闭Turbo Mode一劳永逸。AMD平台略有不同。新内核的amd-pstate驱动可能不直接提供no_turbo接口你需要查一下BIOS里Precision Boost的开关或者在Grub里加参数禁用。说实在的BIOS关睿频是最优先级最高的做法Linux软件层只是控制内核对硬件接口的调用硬件本身如果还在“自作主张”软件层效果会打折扣。关闭睿频之后建议再用turbostat或者cpupower monitor观察一段时间确认所有核心的频率都稳稳钉在目标值才算是真正搞定。3.4 验证锁频是否生效验证频率锁定效果的工具有好几个我分别说一下各自的适用场景。最简单粗暴的是watch配合/proc/cpuinfowatch -n 1 grep MHz /proc/cpuinfo注意有些新内核里/proc/cpuinfo里的MHz字段已经不更新了始终显示初始值那就别依赖它。此时可以用cpupower monitor这个工具会实时刷新各核心的状态和频率非常适合验证。更专业的是turbostat来自linux-tools软件包。它能看到每个核心的实时频率、负载、C-state分布信息量很大。唯一缺点是输出比较乱第一次用的兄弟可能看懵。我的建议是重点关注“MHz”列把所有核心的频率波动情况尽收眼底。我个人的验证流程是先执行turbostat跑5秒钟观察平均频率再跑一个stress-ng之类的压测工具制造负载同时再抓一次turbostat。如果锁频成功空闲和满载是的频率应该几乎一样变化极小。如果压力一上来频率就变了说明锁频失败得检查是不是睿频没关或者驱动不配合。4. 锁定CPU核心数下线核心与绑核频率锁完之后另一个维度就是核心数。核心数锁定有两种理解一是让部分核心从系统里消失下线/隔离二是让某个进程只能跑在指定核心上绑核。实际项目中两种往往配合使用。4.1 通过热插拔方式临时下线核心Linux支持CPU动态热插拔可以通过sysfs接口把一个逻辑核心从系统里摘掉。注意这并不意味着物理核心被关闭而是调度器不再往它上面派任务。操作异常简单echo 0 /sys/devices/system/cpu/cpu3/online把cpu3替换成你想下线的核心编号。执行后lscpu里就看不到这个核心了。想恢复就写回1echo 1 /sys/devices/system/cpu/cpu3/online这里有个非常关键的坑cpu0一般不能下线因为内核和中断处理依赖它。如果你尝试下线cpu0大概率会直接报错或者系统不稳定。另外下线一个超线程核心时它的兄弟线程也可能会受影响建议先看lscpu -e确认逻辑核心和物理核心的对应关系再动手。还有一点要注意下线核心和“锁定频率”是两码事。就算你下线了多余核心留下的核心还是会动态调频。所以我的习惯是先把频率全部锁好然后才去折腾核心数量。顺序反了容易混乱。4.2 在内核启动时隔离核心热插拔下线核心的缺点是它只是临时状态重启就失效。而且某些驱动或者内核模块在核心被下线后会出现异常我遇到过网络驱动绑定的软中断CPU被下线后网络收发性能反而下降的怪事。更稳妥的做法是使用内核启动参数isolcpus。它的作用是把指定核心从通用调度器的候选中排除系统默认不会往这些核心上分配普通进程。你可以用它们做专属计算或者再配合手动绑核。编辑/etc/default/grub找到GRUB_CMDLINE_LINUX把隔离参数加进去。比如我要隔离cpu4到cpu7GRUB_CMDLINE_LINUX... isolcpus4,5,6,7保存后执行update-grubUbuntu/Debian或者grub2-mkconfig -o /boot/grub2/grub.cfgCentOS/RHEL然后重启。isolcpus和热插拔的一个显著区别是被隔离的核心仍然在线系统能看到它但调度器不主动往里面放进程。同时要注意isolcpus并不是绝对隔离某些内核线程比如kworker还是可能会侵入需要配合cpuset和irqaffinity才能做到“绝对隔离”。对多数场景来说isolcpustaskset已经够用了。4.3 用taskset把进程钉在指定核心上如果你不想动grub也不想重启那就直接用taskset给进程设置CPU亲和性。它的本质是告诉内核这个进程只能跑在哪些逻辑核心上。给一个已经在运行的进程设置亲和性taskset -p 0x30 12340x30是十六进制掩码展开成二进制就是110000意思是运行在cpu4和cpu5上。1234是进程PID。如果你希望首次启动进程时就绑核用-c参数更直观taskset -c 4,5 ./my_heavy_process这样my_heavy_process只会在cpu4和cpu5上运行。这种方式的优点是完全免重启、免修改内核参数临时验证特别方便。缺点也很明显它不阻止其他进程使用cpu4和cpu5。如果系统负载很高其他进程照样会把这两个核心抢占你能锁定的只是“这个进程不去别处”不是“别人也不能来”。所以真正要做到核心独占我还是推荐isolcpus和taskset结合用内核启动时隔离出专属核心再用taskset把关键进程塞进去。这样普通进程不会来抢关键进程也出不去双向锁定才算闭环。5. 把配置固化下来重启不失效上面所有的sysfs写操作都是运行时修改一旦重启就会恢复默认。对于服务器和长期运行的环境来说这显然不合适。所以要学会把配置固化到系统启动流程里让每次开机自动执行。5.1 借助systemd服务实现开机自动配置比较现代的做法是写一个systemd服务。先创建服务文件比如/etc/systemd/system/cpu-lock.service内容大致如下[Unit] DescriptionLock CPU frequency and cores Aftermulti-user.target [Service] Typeoneshot ExecStart/usr/bin/cpupower frequency-set -g userspace ExecStart/usr/bin/cpupower frequency-set -f 2800MHz ExecStart/bin/sh -c echo 1 /sys/devices/system/cpu/intel_pstate/no_turbo 2/dev/null || true ExecStart/bin/sh -c echo 0 /sys/devices/system/cpu/cpu3/online 2/dev/null || true RemainAfterExityes [Install] WantedBymulti-user.target启用并启动服务systemctl daemon-reload systemctl enable cpu-lock.service systemctl start cpu-lock.service这个写法的好处是一目了然每条命令独立成行哪一步出错也容易排查。我加的一句“|| true”是为了防止文件不存在时而导致服务启动失败。毕竟不同平台sysfs路径有差异不能因为一个开关不存在就中断后续命令。如果你用的是老版本Linux发行版比如CentOS 6没有systemd那就把同样的命令写进/etc/rc.d/rc.local或者/etc/rc.local并确保该文件有执行权限。原理一样都是开机执行一段脚本。5.2 用脚本管理并设置回滚机制固化配置看起来简单但生产环境里我建议再套一层脚本别裸写在服务文件里。因为裸写在服务文件里调试的时候你没法手动跑一半也无法灵活传参。我习惯把所有关键操作写成一个独立的shell脚本比如/usr/local/sbin/lock-cpu.sh内容按步骤组织每步都有echo日志。脚本大致骨架如下#!/bin/bash set -e # 验证当前用户权限 if [ $EUID -ne 0 ]; then echo 需要root权限执行 exit 1 fi # 锁定频率 cpupower frequency-set -g userspace cpupower frequency-set -f 2800MHz # 关闭睿频如果接口存在 if [ -f /sys/devices/system/cpu/intel_pstate/no_turbo ]; then echo 1 /sys/devices/system/cpu/intel_pstate/no_turbo fi # 下线多余核心 for cpu in 3 5 7; do if [ -f /sys/devices/system/cpu/cpu$cpu/online ]; then echo 0 /sys/devices/system/cpu/cpu$cpu/online fi done # 打印确认信息 cpupower frequency-info | grep current lscpu | grep CPU(s)然后在systemd服务里指向这个脚本后续维护就方便了。想调整频率就去改脚本不用动服务文件。日志输出可以被systemd捕获到journal出错时用journalctl -u cpu-lock.service查看排查效率高很多。再补充一个回滚思路无论你采用哪种方式固化配置都建议同时准备一个“解锁”脚本把睿频打开、把核心重新上线、governor恢复默认。因为有时候临时要跑高性能任务锁定模式反而碍事。有了解锁脚本一台机器可以从“高确定模式”和“高性能模式”之间快速切换。6. 常见问题与排查技巧实录锁频锁核看似简单但我在实际折腾中踩了不少坑。这里挑几个典型问题配上排查思路给各位做个速查参考。问题现象可能原因排查与解决办法改了scaling_governor后没有效果频率还在波动驱动是intel_pstate且只支持performance和powersave或睿频参与干扰先用cat查看scaling_available_governors确认为intel_pstate后可考虑追加内核参数intel_pstatedisable强制切到acpi-cpufreq但小心影响正常睿频或者直接用userspace试。指定频率写入后scaling_cur_freq显示的却是其他值userspace只能从平台支持的频率档位中选值系统自动就近匹配查看scaling_available_frequencies选一个平台原生支持的档位写入别写中间值。cpupower frequency-set -f出现“错误: 无法设置指定频率”当前governor不是userspace或者内核/驱动不支持先切换到userspace再设置频率如果驱动不支持userspace只能退而求其次用performance模式配合关睿频。下线某个核心后系统变卡网络也变慢核心下线导致软中断和内核线程集中到剩余核心出现竞争重新上线核心改用isolcpus配合irqaffinity来隔离减少对内核基础设施的干扰。开机后之前设置的频率/核心下线配置全部失效配置没有固化到systemd或rc.local按第5节内容创建systemd服务或启动脚本并确认服务已被enable。云服务器或者容器环境里根本没有/scsys/devices/system/cpu/cpuX/online虚拟化环境由宿主机管理CPU用户空间无权限不可直接热插拔只能通过cpuset/cgroup限制进程可使用的CPU范围。锁频后CPU温度升高很多、风扇狂转锁定的频率太高散热压不住降低目标频率检查散热或者打开C-state唯独C1/C6不关让CPU在空闲时能进入低功耗状态。某些核心被隔离后依然有进程运行时CPU占用率并非0内核线程kworker或中断处理硬要执行isolcpus不是硬件隔离结合cpuset把整个分区的进程绑定并对中断做irqaffinity设置才能实现更严格的隔离。6.1 为什么锁频成功了但延迟还是很高这个问题很隐蔽经常让人误以为锁频没生效。实际上除了频率和核心数CPU还有C-state空闲状态这个因素在捣乱。当CPU在一段时间内无所事事它会进入C1、C6甚至更深的休眠状态。唤醒是需要时间的哪怕只有几十微秒在实时任务里也可能造成延迟毛刺。排查方法是使用turbostat观察“CPU%c6”等指标。如果你发现哪怕在空闲时也有大量核心长时间停留在C6而你的业务对延迟又极其敏感那可以考虑在BIOS里禁用深C-state或者在内核启动参数中加入processor.max_cstate1配合intel_idle.max_cstate0来限制。这个操作会比锁频锁核更进一步副作用是功耗明显上升得权衡利弊。6.2 如何确认是驱动层面限制了锁频如果所有方法都试过频率就是锁不住别折腾permission了直接看内核日志dmesg | grep -i cpufreq这一条能显示内核加载了哪个cpufreq驱动以及驱动初始化时的信息。再执行ls /sys/devices/system/cpu/cpufreq/如果看到policy0、policy1之类的目录说明驱动是以policy为单位管理频率的某些情况下你需要对整个policy操作而不是单独操作每个核心。举个例子intel_pstate的同一个policy下的多个核心通常共享同一份频率控制你改了cpu0的频率cpu1可能不受影响。理解了这层关系你就知道为什么有些命令只对部分核心生效了。6.3 一个新内核版本带来的坑讲个我近期遇到的例子。一台AMD EPYC的机器内核从5.15升到6.1之后之前用的cpupower frequency-set -g userspace突然就报不支持了。查了一下才知道新版内核默认启用了amd-pstate的EPPEnergy Performance Preference模式这个模式本身不提供userspace governor要切到非EPP模式得在内核启动参数里加amd_pstatepassive。这个问题在发行版论坛上其实讨论很多但第一次遇到的人真的容易卡很久。遇到类似情况我的排查顺序是先确认驱动再确认可用governor最后才去改参数。千万别一上来就手忙脚乱往grub里塞参数那样很容易把启动搞坏。6.4 估一估你的负载需要几个核心锁核心数之前我强烈建议先用perf stat或者mpstat观察一下当前业务真实的并发需求。比如一个Web服务压测发现在4个核心时吞吐量已经能打满网卡那锁8个核心就是浪费。反过来如果是并行计算任务4个核心可能连基础运行都卡顿。锁定不是越少越好而是“刚好够用”。我常用的方法是在压力测试环境下先用systemd service配合taskset把服务限制在不同数量的核心上跑一轮记录吞吐量和延迟画个折线图。找到性能拐点那个拐点对应的核心数量就是最合适的锁定目标。这个做法浪费一点时间但比拍脑袋准确太多。写在最后的几个提醒我把常用配置和排查表整理在这里算是一个简版速查。但实际操作中还有一些细节想再唠叨几句。首先锁频和锁核是“强干预”操作它改变了内核默认的智能调度逻辑。很多新手只盯着性能提升忽略了副作用功耗飙升、发热增加、可用算力下降。如果只是日常用机不建议长时间锁频锁核反而让系统自己调度更划算。其次如果这台机器上有多个业务别把所有核心都隔离或者锁频至少要留一部分核心给系统管理、网络中断、日志写入等基础服务。我做隔离时习惯留出两个核心给内核和中断处理否则一旦主业务流量波动系统会陷入核心之间互相抢占的泥潭性能反而一泻千里。最后分享一个我个人的小技巧所有与CPU相关的配置都建议先写进脚本并备份。因为你可能今天锁频明天调内存参数后天改网络设置出了问题时很难第一时间定位。如果每个优化点都能有独立的、可回滚的脚本排查成本会低很多。我这个习惯救过我不少次有一次生产机器重启后莫名卡死我直接跑了解除锁定脚本系统立刻恢复后来定位到是isolcpus参数叠加了驱动兼容问题。CPU频率和核心数的锁定说到底就是和内核的自动调度机制“抢控制权”。当你真正理解了背后的cpufreq框架和调度器原理这些操作就只是几个命令的事。希望这篇文章能帮你少走一些弯路在需要确定性的时候让你的CPU真正听你的话。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进