
你有没有遇到过这种场景一台服务器的CPU使用率明明只有百分之二三十但业务接口的P99延迟却高得离谱SSH连上去敲个命令都要卡上半天。我之前排查过一个典型的案例最后根因就落在Linux 进程优先级上——某个后台批处理任务在不知不觉中抢走了大量CPU时间片把真正该优先响应请求的进程挤到了调度队列后面。从那以后我意识到光盯着CPU使用率看远远不够还得理解进程在调度器里到底是怎么排队的。这篇学习笔记就是围绕Linux进程优先级整理的一整套认知从nice值、renice操作到实时调度策略再到一次真实排障链路适合正在学Linux系统管理的同学也适合做运维和嵌入式开发的朋友参考。文章里会用实际命令和踩坑经历把优先级这件事讲透你照着操作就能在机器上验证。1. 进程优先级是怎么影响“排队”的调度器的视角1.1 如果没有优先级系统会变成什么样Linux是一个多任务操作系统同一时刻可能跑着几百个进程。CPU资源有限谁先执行、谁后执行、谁多拿时间片、谁少拿时间片这些都是调度器说了算。假设调度器对所有进程一视同仁、完全公平轮转会出现什么情况你一边开着编辑器写代码一边在后台跑一个耗时的数据压缩任务两者各拿一半CPU时间。数据压缩不需要响应慢一点没关系但编辑器需要在你敲下按键的瞬间就有反应如果它只能拿到一半甚至更少的时间片打字都会出现明显的延迟。进程优先级解决的正是这个矛盾它不是让系统变得“不公平”而是让调度器知道“哪些进程需要更快被响应、哪些进程可以让一让”。没有优先级机制交互式应用和批处理任务搅在一起系统的整体体验会被拉到最低水位——这正是很多“CPU不高但机器很卡”问题的根源。1.2 Linux调度器的几类调度策略先明确一个概念优先级不是单一的数字它跟调度策略绑在一起。Linux的调度器按策略把进程分成几类用chrt、ps和top都能看到。调度策略名称使用场景SCHED_OTHERSCHED_NORMAL普通调度策略绝大多数用户进程默认策略SCHED_BATCH批处理策略适合非交互、CPU密集型任务SCHED_IDLE空闲策略极低优先级任务只在CPU完全空闲时运行SCHED_FIFO实时先进先出高优先级实时任务不按时间片轮转SCHED_RR实时时间片轮转高优先级实时任务按时间片轮转普通进程默认都是SCHED_OTHER策略内部使用CFS调度器也就是“完全公平调度器”。CFS的公平不是简单的平均分配而是根据每个进程的权重来分配CPU时间权重又由nice值决定。SCHED_BATCH和SCHED_IDLE是普通策略的变体前者更适合跑批处理任务后者允许你把某个进程降级到“只有系统完全空闲才运行”。这两类策略在日常运维里不常见但理解它们的存在能帮你更准确地读懂进程列表里的sched字段。1.3 优先级在进程内部是怎么存的进程的优先级信息放在内核的task_struct里主要字段包括static_prio静态优先级、normal_prio普通优先级、prio动态优先级和rt_priority实时优先级。对于普通进程static_prio由nice值换算而来对于实时进程rt_priority就是它的实时优先级范围0到99。动态优先级会随着进程的睡眠、唤醒、交互状态变化普通用户一般感知不到也不需要去改它。这一节先建立整体概念后面我会逐个展开能实操的部分。你不需要背这些字段名但看到/proc下的状态输出时能知道它们每个对应什么含义排查问题会顺畅得多。1.4 用生活场景理解进程优先级把CPU比作一条只能一个人通过的窄路进程就是排队的人。普通模式下大家轮流各走一段交互式进程是那种“每次只需要短时间用CPU但频率高”的人批处理任务则是“一次霸占CPU很久”的人。如果没有优先级规则一个压缩任务可能把整条路占住你的编辑器只能在后面干等。Linux的CFS调度器处理这个问题的方式是给每个人分配不同权重权重高的进程在单位时间内能走更多步。优先级数字越小权重越高竞争CPU时越占优势。理解了这个比喻再看后续的nice值和renice操作思路就清晰了。2. nice值和优先级数字之间的换算逻辑动手算清楚2.1 nice值为什么叫“nice”Linux里最常见的优先级控制入口就是nice值范围从-20到19一共40个级别。数字越小优先级越高数字越大优先级越低。这个名字有个反直觉的地方一个进程的nice值越高代表它对其他进程“越友好”自己的优先级越低也就是主动谦让CPU。反过来nice值为负数的进程非常“不nice”它会抢占更多CPU资源。普通用户进程默认的nice值是0。如果你启动一个CPU密集型任务而不做任何设置它的权重就是默认权重和编辑器、数据库这些进程处于同一竞争线上。要改变它的竞争地位就得动手调整nice值。2.2 从nice到实际优先级记住20NI这个公式在用户态看进程优先级最常用到的换算公式是PR 20 NI其中NI就是nice值。举个例子一个进程nice0那么它显示出来的PR就是20把nice调成-5后PR变成15把nice调成10后PR变成30。数字越小优先级越高所以-5比10更“抢跑”。但这里有个容易混淆的点内核内部的优先级编号范围不是这样的。内核把优先级空间分成两段0到99留给实时进程100到139给普通进程。普通进程的静态优先级实际上是120NI对应关系是nice0时static_prio120nice-20时static_prio100nice19时static_prio139。用户态显示的20NI其实就是内核数值减去100以后的“简化展示”。2.3 查看优先级状态ps、top各看哪一列我在排查时习惯用ps -l查看当前进程的PRI和NI列也会用ps -eo pid,comm,ni,pri,rtprio,sched来定制输出。top命令的PR列更直观普通进程直接显示20NI实时进程显示为rt。查看工具普通进程显示实时进程显示top PR列20nice值rtps -o pri20nice值实时优先级值ps -o nicenice值-20~19nice值ps -o rtprio-0~99实时优先级看清楚工具显示的逻辑很重要否则很容易出现“为什么top里PR是20ps里却显示120”的困惑。我见过不止一个同事拿内核数字跟用户态数字直接比较得出排序完全反了的结论。实际排查时统一以ps -o pri和ps -o nice为准就不会乱。2.4 为什么基准是20这个数字从120nice的内核表示可以看出来nice值每变化1优先级数字就变化1。20这个基准数字本身没有特殊的计算意义更多是为了让nice0的进程在用户态显示成一个好记的整数。只要记住“普通进程PR20NI数值越小优先级越高”就够用了。3. 调整进程优先级的实操命令nice、renice与systemd配置3.1 启动进程时指定nice值nice命令在启动一个命令或脚本时可以用nice命令直接设置初始优先级nice -n 5 ./backup.sh nice -n -10 ./data-server第一条命令表示以nice5启动backup.sh让备份任务主动谦让CPU第二条命令表示以nice-10启动data-server给服务更高优先级。如果不加-n参数直接写nice 5 ./backup.sh也是一样的效果这是老版本遗留的写法。还有一个容易踩的坑nice命令不加任何数字时默认是加10而不是设置成10。也就是说如果父进程当前nice0直接运行nice ./sleep.py子进程的nice会变成10如果父进程已经是nice5它会变成15。这是累加不是覆盖。3.2 运行中调整已有进程renice命令如果一个进程已经跑起来了用renice调整它的nice值常用格式renice -n 5 -p 12345 renice -n -5 -p 12345 renice -n 10 -u backupuser第一条把PID 12345的nice调成5第二条调成-5第三条把这个用户下所有进程的nice都调成10适合批量处理跑在同一账号下的任务。调整之后可以用ps -o pid,comm,ni,pri -p 12345确认结果。renice修改的是进程的static_prio会影响CFS给它的CPU权重分配不需要重启进程效果是即时的。我自己在运维里经常用它压住备份、日志清理、镜像构建这类批处理任务。需要在深夜跑全量备份直接把backup脚本的nice设成10或者15这样即使它占满CPU业务进程的响应也不会被明显影响。3.3 权限边界普通用户和root能改的范围完全不一样修改nice值不是无限制的。普通用户只能调高自己进程的nice值也就是让进程更谦让不能调低不能越过默认的0往下调。如果你用非root用户执行renice -n -5 -p 你的进程会看到Permission denied。root用户不受这个限制可以在-20到19之间任意调整。除此之外拥有CAP_SYS_NICE权限的进程也可以调整其他进程的nice值这在容器场景里比较重要有些容器默认没有授予这个capability所以容器内执行renice会失败即使你是容器里的root也一样。遇到这种情况先检查容器的capabilities配置别一直怀疑命令写错。3.4 用top交互式调整适合临时验证如果你正在top界面里观察按r键就可以调整优先级。top会先提示输入PID然后让你输入nice值输入负数就是提高优先级。这个操作的好处是快缺点是只适合临时调整一旦进程重启配置就丢了。3.5 通过systemd统一管理服务优先级我更推荐在部署阶段就把优先级写进systemd服务文件比事后用命令调整可靠得多。在service文件的[Service]段加两行[Service] Nice10 CPUSchedulingPolicyotherNice10表示服务启动时以nice10运行对CPU密集型后台任务非常合适。如果服务实时性要求高可以配合CPUSchedulingPolicyrr或fifo以及CPUSchedulingPriority指定实时优先级但这类配置要非常谨慎后面第4节细说。用systemd的好处是服务重启后优先级自动恢复不会因为忘记执行renice导致优先级漂移。3.6 别忽略“nice值累加”的连锁反应前面提过子进程会继承父进程的当前nice值所以你会遇到类似情况启动脚本本身被设置成nice10脚本里再执行nice -n 5 sub_task最终子任务的nice是15。如果你在脚本里还调用了其他脚本叠几次之后优先级会变得很低看进程列表时甚至会觉得莫名其妙。排查这类问题时用ps -o pid,ppid,ni,comm看一下父子进程关系就能理清楚。4. 实时调度策略和实时优先级别随便动这类进程4.1 SCHED_FIFO和SCHED_RR到底有什么区别前面提到普通进程用的是SCHED_OTHER对应CFS。而Linux还提供两类实时调度策略SCHED_FIFO和SCHED_RR。它们的优先级范围是0到99数字越大优先级越高这一点和nice值刚好相反第一次接触的人特别容易搞混。SCHED_FIFO是“先进先出”一个FIFO实时进程进入运行状态后只要它自己不主动让出CPU同优先级的其他进程就没有机会运行即使来了更高优先级的FIFO进程也只能排队等它让出CPU。SCHED_RR给同一优先级的实时进程分配时间片轮流使用CPU但依然优先于所有普通进程。换句话说一个实时进程只要处于可运行状态就会打断所有普通进程的执行。这对延迟敏感的实时任务很友好但对普通系统服务来说是灾难性的。4.2 chrt命令查看和设置调度策略用chrt查看进程当前的调度策略和实时优先级chrt -p 12345 chrt -p 5678如果输出里显示policy: SCHED_FIFOpriority: 50说明这个进程用的是FIFO策略优先级50。设置的方法chrt -f -p 50 12345 chrt -r -p 30 5678-f表示SCHED_FIFO-r表示SCHED_RR后面跟实时优先级数字。也可以在启动进程时给命令加上调度策略chrt -f 50 ./realtime_app4.3 我在生产环境踩过的一个教训有一段时间我们尝试把某个业务线程调成SCHED_FIFO以为能提升响应速度。结果启动后那个线程里有个循环在没有锁竞争的情况下疯狂消耗CPU因为它优先级太高SSH进程、监控agent、日志进程全都被抢占系统几乎失去响应最后只能硬重启。这个经历给我的教训是实时优先级是个“特权”不是优化工具。如果只是希望普通服务更流畅用nice值就够了要动实时优先级必须确认该进程是真正的周期性、确定性任务且不会出现无限循环占用CPU的情况。设置实时优先级之前先想想这个进程异常时会不会把系统拖死。4.4 什么场景下才真正需要实时优先级需要实时优先级的典型场景是嵌入式或工业控制领域电机控制、数据采集、音频处理这类对延迟有硬性要求的任务。配合isolcpus把CPU核隔离出来、把中断绑到特定核、甚至打上PREEMPT_RT补丁才能保证实时进程的确定性。普通服务器上的业务进程绝大多数用不到也不该用实时优先级。如果你确实需要设置建议把实时优先级控制在较低范围同时用超时监控兜底一旦进程长时间占着CPU不释放监控系统能及时告警甚至有人工介入通道避免整个系统失去响应。调度策略和配套工具非常强大但强大不等于安全。5. 一次“CPU没满但服务卡顿”的排障记录优先级视角的排查链路5.1 现象回顾所有常规指标都正常接口就是慢有个周末我值班收到告警说一台API网关P99延迟从20毫秒涨到3秒。登上去一看CPU使用率只有30%内存充足网络正常所有常规指标都“正常得不能再正常”但接口就是慢。这种反直觉的情况往往就是调度层面的问题。5.2 第一步用ps列出进程的优先级字段我先用了一条命令看整个系统的进程排序ps -eo pid,ppid,comm,ni,pri,rtprio,sched --sort-pri | head -30输出里马上发现了问题有两个PID非常小的进程说明是系统启动早期就拉起来的它们的NI列是-5和-10PRI列远低于其他进程数字小意味着优先级高CPU占用一直在跳。再一看原来是某个初始化脚本把这些任务设置成了低nice值而它们内部又有重试循环导致CPU时间被大量占用。这条命令里的sched列也很有用0表示SCHED_OTHER1表示SCHED_FIFO2表示SCHED_RR3表示SCHED_BATCH5表示SCHED_IDLE。看到1和2要格外小心那意味着有进程在用实时策略。5.3 第二步用pidstat观察CPU分时占用单看优先级还不够我接着用pidstat确认高优先级进程到底在干什么pidstat -p PID 1 10 pidstat -u 1 10第一条监控指定进程每秒一次共10次第二条看全系统进程的CPU占用。结果发现那个低nice值进程在反复做重试每次重试间隙让出CPU但很快又拿回来形成了一个高频抢占。业务进程的调度延迟就是这么被拉高的。5.4 第三步调整并验证确认根因后我执行了renice把这两个进程的nice值从-5、-10调回10一瞬间P99就回落了。随后我把初始化脚本里那段优先级设置删掉重新部署再也没出现同类问题。这次的真正收获是CPU使用率不高不代表调度没问题。优先级高、低权重策略、高频唤醒这些因素都可能让低使用率机器上的核心服务出现延迟。排查的时候不能只盯CPU%还得看进程的nice和sched字段。5.5 一个容易误伤的脏活内核线程和软中断在进程列表里你还会看到ksoftirqd、migration、kworker这类内核线程。它们的优先级字段和普通进程不一样调优起来特别容易误伤。举个例子网卡软中断集中到某个CPU核时对应的ksoftirqd会占据大量CPU这时候正确的处理方式是优化中断亲和性、检查网卡驱动或者看网络流量而不是renice ksoftirqd。强行调整内核线程的优先级轻则没有效果重则会让整个网络栈出现异常。我现在的习惯是任何可能出现资源争抢的服务在部署阶段就在systemd或者启动脚本里把Nice值明确写出来而不是等问题出现再临时调整。排查优先级问题的时候建议先顺着ps -eo pid,comm,ni,pri,rtprio,sched --sort-pri | head -20这条命令往下看把调度策略和nice值一起评估很多“CPU不高却卡顿”的怪问题都会豁然开朗。附带一个小技巧用watch持续盯着优先级排名异常进程基本藏不住watch -n 1 ps -eo pid,comm,ni,pri,rtprio,sched --sort-pri | head -20能把这个命令的输出看明白Linux进程优先级这块就算真正入门了。