
如果你管理过一台跑着多个任务的Linux服务器大概率遇到过这样的场景一个后台备份任务突然把CPU吃满前端业务的响应时间跟着飙上去kill掉它不合适重启更不现实。这时候你可能会想到一个词——进程优先级。没错Linux很早就提供了nice、renice、chrt这样一套工具专门用来调整进程在CPU调度中的权重。这篇笔记想把这些年关于Linux进程优先级的实战经验整理出来从内核调度的基本逻辑讲到命令行实操再讲几个真实环境中的调优案例和踩坑记录。内容不算高深但足够帮你把优先级这个概念从看过变成会用尤其是刚接触Linux运维和嵌入式开发的朋友应该能少走不少弯路。1. 进程优先级到底在解决什么问题1.1 从一次CPU满载事故想起的先说个真实经历。有一年我维护的一台8核服务器白天跑着Java网关凌晨两点有个定时任务起来做数据归档。这个归档任务本身涉及大量压缩和排序直接把所有核都占满了。结果就是凌晨的报表接口时不时超时监控一拉发现CPU的user态冲到95%以上Java进程的线程大量处于R状态但拿不到CPU时间片。当时第一反应是降低归档任务的优先级一条renice -n 10 -p 12345下去几分钟内业务接口的RT就回落了归档任务也从一个半小时延长到两个小时多一点但完全不影响核心业务。这个案例就是进程优先级最典型的应用场景多个进程同时争抢CPU时操作系统要决定谁先用、谁多用。Linux把决策权交给了调度器而调度器依据的核心指标之一就是进程优先级。可以简单理解为它不是能不能运行的开关而是多快能轮到、能占多久的权重。1.2 优先级不是一个数值那么简单很多人第一次接触这个主题以为就是nice -n后面跟一个数字。实际上Linux进程的优先级体系比这复杂它至少包含三套并行的概念普通进程的nice值、内核里的静态优先级/动态优先级以及实时进程的实时优先级。这几套东西分别对应不同的调度策略也对应不同的修改工具。用大白话解释普通进程之间用nice值分轻重nice值越低越有礼貌反而越容易被优先调度实时进程则走另一套逻辑优先级数值从1到99数值越大越优先它能直接抢占普通进程的CPU时间。所以后面你会看到用renice调整普通进程用chrt才能动实时进程的属性。这两套机制彼此独立混着用会出大问题。2. Linux调度器视角下的优先级体系2.1 从nice值到内核优先级调度器是怎么算的要理解优先级不能只看表面数值。Linux内核里每个非实时进程都有一个静态优先级static_prio范围是100到139。这个数值和nice值有一个明确的换算关系static_prio 100 nice。也就是说nice范围是-20到19对应静态优先级100到139。数值越小优先级越高所以nice-20的进程几乎是最横的普通进程。但是在CFS完全公平调度器里真正参与调度计算的是权重不是直接的优先级数值。内核维护了一张从nice值到权重的映射表nice每差1权重约差1.25倍nice差5权重差约3倍。这意味着一个nice0的CPU密集型进程和一个nice5的同类进程同时跑在单核上前者获得的CPU时间大约是后者的3倍而不是简单的5%差别。很多新手改了nice之后觉得没效果就是因为他们没搞懂这个非线性关系也没有制造CPU争抢的背景。2.2 实时优先级和普通优先级为什么是两套体系实时进程走的是SCHED_FIFO和SCHED_RR两种调度策略内核里的rt_priority范围是1到99。注意这里的1到99和普通进程的100到139不是同一个尺度实时优先级数值越大越优先这和nice的方向完全相反。当一个实时进程处于可运行状态时它会优先于所有普通进程被调度哪怕普通进程的nice值已经是-20。我用一个比喻来帮理解普通优先级像排队买奶茶可以加钱插队但终归是按顺序来实时优先级则像急诊室抢救医生到了就必须立刻处理其他排队的人全得暂停。举个例子一个SCHED_FIFO优先级50的音频采集进程和一个nice-20的数据分析任务抢CPU时调度器会先满足音频进程的运行需求数据分析任务只能捡剩余时间。这也是为什么我后面会反复强调实时优先级是给特定场景准备的不能随手乱设。2.3 动态优先级和饥饿机制是怎么运作的还有一个容易被忽略的概念动态优先级。为了不让某个进程长期霸占CPU内核会在运行时对优先级做微调。对普通进程CFS通过虚拟运行时间确保公平性对实时进程则通过rt_priority加上一个可配置的rt_runtime限制避免实时任务把系统完全锁死。RHEL系内核里/proc/sys/kernel/sched_rt_period_us和sched_rt_runtime_us这两个文件控制实时进程在一个周期内最多占多少CPU默认情况下即使有实时进程也会给普通内核线程留出大约95%周期里的5%时间。这个设计非常关键。我曾经见过有人把某个死循环进程设成SCHED_FIFO优先级99结果整个系统几乎失去响应SSH都敲不动命令就是因为实时进程把CPU全吃光内核自身的维护线程连喘息的机会都没有。理解了动态优先级和实时限制机制再遇到这种假死才有思路去恢复。3. 查看进程优先级的实用命令全解3.1 ps、top里那几个字段30秒看懂优先级想在命令行里看进程优先级最常用的是ps和top但很多人分不清输出里的PRI、NI、PR到底代表什么。用ps -l看当前shell的进程输出里有两个关键字段ps -l F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 0 S 1000 1234 1200 0 80 0 - 3456 do_wai pts/0 00:00:00 bash这里的NI就是nice值普通进程是0。PRI是内核动态优先级对普通进程通常是static_prio加上一些动态调整大致可以看作100到139之间的数值。注意top命令里的PR和ps里的PRI含义不同top里为了显示直观普通进程的PR一般等于20 nice范围是1到39实时进程则显示为RT不会看到一个具体数字。如果用top建议按f键把P和NI列都调出来再按ShiftP按CPU排序这样谁在抢CPU、谁优先级低被饿着一眼就能扫出来。对运维排查来说这个技巧比装什么监控工具都直接。3.2 再往底层看procfs和系统调用获取完整调度属性命令行工具封装了底层数据但真要深究还是得看内核暴露的接口。/proc/pid/stat的第18个字段是nice值第19个字段是进程的优先级数值对普通进程是nice对应内核封装后的值对实时进程则是rt_priority加1000不过直接数stat字段太容易错位我更推荐看/proc/pid/schedcat /proc/1234/sched输出里有prio、policy、se.nr_migrations等信息。其中policy字段的数值和调度策略对应关系是0表示SCHED_NORMAL普通CFS1表示SCHED_FIFO2表示SCHED_RR3表示SCHED_BATCH5表示SCHED_IDLE。这个文件还包含进程实际运行了多少时间、被调度了多少次排查为什么这个进程这么卡时非常有价值。如果想要更完整的调度参数可以用chrt -p pid查看也可以用sched_getattr系统调用在代码里获取。对绝大多数场景来说ps、top、cat /proc/pid/sched三件套已经够用了。4. 修改进程优先级的三种部署手段4.1 nice命令启动进程时指定优先级nice命令用于在启动程序时设置优先级语法很简单nice -n 15 ./backup.sh这条命令表示以nice15启动backup.sh。另一种老式写法是nice -15但这里有个容易踩的坑nice -15究竟表示-15还是15不同版本和不同shell的解析并不一致。为了不出歧义强烈建议统一用nice -n加参数的写法。需要注意的是普通用户用nice只能把进程的优先级调低也就是让nice值变大正向调整不能调低nice值来提升优先级。这个限制来自Linux的权限模型目的是防止普通用户恶意抢占资源。只有root用户或具备CAP_SYS_NICE能力的进程才能把nice值往负数方向调。# root可以把进程优先级调高 nice -n -5 ./important_server4.2 renice命令调整已经在运行的进程启动时忘了设置优先级或者运行中情况变化就需要用renice。最典型的用法是给指定PID调整nice值renice -n 10 -p 12345也支持按用户名调整该用户启动的所有进程renice -n 8 -u deployrenice调整的是运行中进程的nice值效果立刻生效。实际操作中我一般会先用pgrep -f或者top找出进程PID再执行renice比如pgrep -f backup_script.py | xargs -I{} renice -n 15 -p {}这里再强调一次权限问题普通用户只能调整自己拥有的进程而且只能往positive方向调降低优先级。root用户或者具备相应capability的进程可以往negative方向调把进程优先级提高。如果看到Operation not permitted先检查是不是权限问题别盲目以为命令写错了。4.3 chrt命令设置实时调度策略和优先级chrt是调整实时调度策略和实时优先级的工具但它修改的是另一个维度的优先级和nice是平行的体系。查看进程当前调度策略chrt -p 12345把进程设置为SCHED_RR抢占式轮转优先级50chrt -r -p 50 12345设置为SCHED_FIFO先进先出优先级60chrt -f -p 60 12345注意chrt对实时优先级的设置范围是1到99数值越大优先级越高。普通用户能设置的实时优先级上限受ulimit -r限制root用户不受限。但root也不建议乱设尤其是计算密集型任务把它设成高优先级实时进程极容易让其他进程甚至系统本身失去响应。还有一个高频组合用法taskset配合chrt让实时进程绑定到特定CPU核心上运行taskset -c 0 chrt -f -p 50 12345这样既避免实时进程在所有核之间频繁迁移又能减少缓存抖动。具体在下一节实战场景里展开。4.4 代码层面C和Python如何控制进程优先级命令行工具管的是外部调整有些业务场景需要在代码启动时主动设置优先级。C语言里最常用的是getpriority/setpriority和sched_setscheduler两套接口。setpriority用于设置普通进程nice值#include sys/resource.h #include stdio.h int main(int argc, char **argv) { if (setpriority(PRIO_PROCESS, 0, 10) -1) { perror(setpriority); return 1; } printf(current nice: %d\n, getpriority(PRIO_PROCESS, 0)); return 0; }PRIO_PROCESS表示按进程维度设置第二个参数0表示当前进程。如果想按进程组或者用户维度设置分别用PRIO_PGRP和PRIO_USER。设置实时调度策略则要调用sched_setscheduler#include sched.h struct sched_param param; param.sched_priority 50; if (sched_setscheduler(0, SCHED_FIFO, param) -1) { perror(sched_setscheduler); return 1; }Python的话更简单import os os.setpriority(os.PRIO_PROCESS, 0, 10)不过我的建议是业务代码里能不动优先级就不动尤其是实时调度策略。因为代码一旦写死你在不同的部署环境里很难灵活调整出了问题还不好排查。把调整交给运维侧的启动参数或者systemd配置比写在代码里可控得多。5. 真实场景里的优先级调优实战5.1 场景一备份任务和在线服务抢CPU再回到开头那个场景。凌晨跑数据归档压缩任务用gzip或者自研脚本打满CPU网关响应变慢。这个场景的解法不是调高网关的优先级而是调低归档任务的优先级让调度器在两者竞争时偏向网关。实际操作我会分两步。第一步调整CPU优先级renice -n 15 -p $(pgrep -f backup_archive)第二步千万别忽略I/O优先级。归档任务除了吃CPU还会产生大量磁盘读写磁盘I/O争抢同样会影响业务。用ionice把I/O优先级降到最低等级idleionice -c 3 -p $(pgrep -f backup_archive)ionice -c 3表示只使用空闲I/O时间系统有其他I/O请求时归档任务会被强制让路。两条命令配合归档任务变佛系对业务的冲击降到最低。5.2 场景二音频采集进程不准有卡顿另一个经典场景是嵌入式设备或者桌面机上跑音频采集进程需要每几十毫秒就处理一次数据稍微被调度延迟就会产生爆音。这种场景靠renice -n -5往往不够因为普通进程的优先级再高也还是会被其他普通进程抢占。正确思路是把采集进程转成实时调度。先看采集进程PID比如1892然后执行chrt -f -p 50 1892如果这个进程是多线程的建议结合taskset绑定到一个不跑其他中断密集型任务的核上taskset -pc 2 1892这里有个细节chrt -f -p设置的SCHED_FIFO优先级对内核而言是很硬的保证但前提是该进程不能长时间不释放CPU。音频采集进程通常是短促执行然后让出CPU等数据非常适合FIFO。反过来如果给一个纯计算的渲染进程设FIFO它就有可能长期霸占CPU把系统拖死下一节我会专门讲这个坑。5.3 场景三systemd服务里固化优先级设置如果希望某个服务启动后自动带上优先级与其在脚本里写renice不如直接写在systemd的Unit文件里[Service] ExecStart/usr/bin/my_server Nice10 IOSchedulingClassidle CPUSchedulingPolicyfifo CPUSchedulingPriority45这样服务启动时内核就直接把调度属性和优先级设置好比启动后再renice更可靠也不会出现脚本顺序问题。注意CPUSchedulingPolicy的可选值有other、batch、idle、fifo、rr几种如果设置成fifo或rr必须配合CPUSchedulingPriority指定1到99之间的值。6. 常见问题与排查技巧实录6.1 renice提示Operation not permitted是怎么回事这个问题在论坛里被问了无数遍基本都是权限问题。普通用户执行renice -n -5时内核会检查是否拥有目标进程的PRIO权限由于降低nice值等于提高优先级属于特殊能力普通用户直接拒绝。解决办法要么切root要么给用户加CAP_SYS_NICE能力。如果目标是别人的进程即使调低优先级也没权限。另外实时优先级还受RLIMIT_RTPRIO资源限制管束。在/etc/security/limits.conf里配置myuser hard rtprio 50这是allow用户把实时优先级最多设到50如果配成0普通用户就连chrt -f -p 1都无法执行。6.2 改了nice值为什么进程还是抢CPU原因之一是CFS调度只在CPU满负载的时候才体现差异。如果系统有大量空闲核nice值差10的任务照样跑得飞快优先级不会主动限制进程使用空闲CPU。所以在做优先级测试时必须先人为制造CPU负载否则你会觉得renice根本没有用。原因之二是权重差异是非线性的。nice每差1权重差约1.25倍看起来不多但nice0和nice19的权重比接近10倍。调整之后观察实际效果最好用top按CPU时间看比例不要只看心理预期。原因之三可能就是你的进程本身不在CPU队列里而是卡在I/O或锁上。这时候调CPU优先级毫无帮助得用iostat、pidstat这类工具定位瓶颈到底在哪一层。6.3 把进程设为SCHED_FIFO后系统突然卡死了这是我见过最高危的操作没有之一。有人为了优化一个自研服务把它的调度策略设成了SCHED_FIFO且优先级99结果服务CPU占用一高整个Linux失去响应鼠标不动、SSH连不上、网络监控也挂了。原因是实时进程可以无限抢占普通进程包括内核的一些关键线程。虽然内核有rt_runtime机制保护但如果实时进程始终可运行它会把周期里绝大部分时间都吃掉。进程越多、优先级越高的实时任务系统越容易假死。如果已经卡成这样怎么自救我的经验是尽可能通过硬件外的方式重启系统如果还在可操作窗口立刻把该进程的调度策略改回SCHED_NORMALchrt -o -p 0 pid或者直接kill掉。但这属于事后的补救所以我在实际项目中定了两条规矩第一不为计算密集型任务设SCHED_FIFO第二实时进程必须限制CPU占用配合taskset绑定单独核并做好监控告警。6.4 容器和cgroup让优先级变得不那么直接最后提醒一个容易被忽略的问题现在很多服务跑在Docker容器里或者受systemd cgroup v2限制。这时即使你手动调了进程的nice值cgroup的cpu.weight和cpu.max设置也会影响实际分配比例。如果容器的cpu.weight很低你在容器内把进程的nice调到-20能分到的CPU配额仍然有限。docker里可以这样设置CPU相对权重docker run --cpu-shares1024 --cpu-quota50000 ...--cpu-shares对应CFS的权重值默认1024--cpu-quota限制这个容器在每100ms周期内最多用多少CPU时间。在排查优先级问题时先确认自己修改的是调度层还是cgroup层否则你会陷入明明改了优先级却毫无变化的困惑。我自己这些年调过不少进程优先级最大的体会是这玩意不是调完就万事大吉的银弹它只是调度体系里的一个维度后面还站着CFS、实时调度、cgroup、I/O优先级、CPU亲和性这些相互缠绕的机制。遇到CPU争抢问题先画清楚是谁在抢、抢的是CPU还是磁盘、谁必须优先再去决定用nice、renice还是chrt。把整套逻辑理清了比记住多少条命令都管用。实际工作中我也一直按这个顺序来排查出错的概率会小很多。