本文要点
- nice 值范围
-20(最高)到19(最低),内核按权重而非「优先级数字」分配 CPU:nice 0 权重 1024,nice 19 权重仅 15,理论差距约 68 倍;只有 root 能设负值 - 实测(1 核机器、两个死循环抢 8 秒):nice 0 的进程拿到
98.5%CPU、累计00:00:07,nice 19 的只拿到1.5%、累计00:00:00——实测比值 65.7,与理论 68 吻合 - 没有竞争就没有差别:CPU 空闲时单独跑同一段循环,nice 19 耗时 4.10s、nice 0 耗时 4.01s,差距在误差内——nice 只在 CPU 抢不过来时才生效
renice可随时改运行中进程的 nice 值(实测 0 → 10 立即生效);chrt换调度策略(SCHED_BATCH / SCHED_IDLE / SCHED_FIFO);ps -o cls,rtprio可核对ionice管磁盘 I/O 优先级,但取决于 I/O 调度器:演示机/sys/block/vda/queue/scheduler为[mq-deadline],会尊重 ionice 设置;若为none则完全无效
Linux 进程调度优先级:从 nice/renice 到 ionice 与 chrt
一台 1 核小机器上跑着备份任务,压缩一启动,SSH 打字就开始一顿一顿;或者你提交了一个长时计算的作业,又不想让它影响同机上的 Web 服务响应。加机器是最直接的解法,但在那之前,Linux 自带了更便宜的手段:告诉内核「这个进程不重要」。
本文在 Ubuntu 22.04(KVM,内核 5.15.0-30-generic,1 核)上实测进程调度优先级的三个层次:CPU 优先级(nice/renice)、调度策略(chrt)、磁盘 I/O 优先级(ionice)。所有数字都是真实跑出来的,截图即真实终端输出。
nice 值:-20 到 19 的权重游戏
Linux 用 nice 值表示普通进程的 CPU 优先级,范围 -20(最不客气,抢得最凶)到 19(最谦让),默认 0。名字来自「谁更 nice(友善)谁就让步」——值越大越友善,越容易把 CPU 让给别人。
关键是要理解:nice 值不是「优先级数字」,内核会把它换算成权重,再按权重比例分配 CPU 时间。当前内核(CFS 调度器)的换算表是这样:
| nice 值 | 权重 | 相对 nice 0 |
|---|---|---|
| -20 | 88761 | 86.7 倍 |
| -10 | 9548 | 9.3 倍 |
| 0 | 1024 | 1 倍 |
| 5 | 335 | 0.33 倍 |
| 10 | 110 | 0.11 倍 |
| 15 | 36 | 0.035 倍 |
| 19 | 15 | 0.015 倍 |
这张表解释了两件事:
- nice 的差距是乘法关系,不是加法。nice 0 和 nice 19 的权重比是
1024 : 15 ≈ 68 : 1——不是「慢一点」,而是在争抢时几乎拿不到 CPU。反过来,nice -20能拿到 nice 19 进程的约 5900 倍 CPU 时间。 - 权重是相对分配的。如果机器上只有你一个进程,权重多少都是 100% CPU——这也是下一节实测要验证的重点。
查看当前 shell 的 nice 值,直接跑不带参数的 nice:
nice; nice -n 5 nice0
5nice -n 5 nice 的意思是「以 nice 5 运行 nice 命令」,内层 nice 打印出的就是它继承到的 5。
另外记住:只有 root 能把 nice 设为负值(提高优先级)。普通用户只能在自己进程上调高 nice 值(降低优先级),这是防止普通用户抢占系统资源的设计。
实测:两个死循环抢一个核
光看表不够直观,直接在 1 核机器上让两个死循环对撞:一个以 nice 19 启动,一个以默认 nice 0 启动,同时跑 8 秒,然后看 ps 统计:
nice -n 19 bash -c 'while :; do :; done' & P19=$!; bash -c 'while :; do :; done' & P0=$!; sleep 8; ps -o pid,ni,pcpu,time,comm -p $P0,$P19; kill $P0 $P19
输出读法:
NI列是 nice 值,两行分别是19和0,确认两个进程按预期启动。%CPU列:nice 0 的进程 98.5%,nice 19 的进程 1.5%。TIME列是累计消耗的 CPU 时间:nice 0 的拿到 7 秒,nice 19 的是 0 秒。
把两个 %CPU 相除:98.5 / 1.5 ≈ 65.7,与上一节权重表算出来的 1024 / 15 ≈ 68 基本吻合。理论值和实测值对上了,说明内核确实是按权重比例在切分这唯一的 CPU 核。
需要注意 nice 19 的进程并不是「完全饿死」:它依然拿到了 1.5% 的 CPU。CFS 保证每个进程都有最小执行份额(sched_min_granularity),不会出现绝对的饥饿——只是慢到几乎看不出来。
前提:没有竞争,nice 几乎不起作用
这是最容易被误解的一点。如果 CPU 空闲,nice 19 和 nice 0 跑得一样快。同样一段 300 万次迭代的循环,单独运行(系统没有其他负载)分别计时:
/usr/bin/time -f "nice19 无争抢耗时: %e s" nice -n 19 bash -c 'for i in $(seq 1 3000000); do :; done'
/usr/bin/time -f "nice0 无争抢耗时: %e s" bash -c 'for i in $(seq 1 3000000); do :; done'nice19 无争抢耗时: 4.10 s
nice0 无争抢耗时: 4.01 s4.10s 与 4.01s,2% 的差距完全在测量误差范围内,等于没有区别。
结论很实用:nice 只在 CPU 成为瓶颈时才有效。如果你给备份任务加了 nice 19,却发现它照样把机器拖慢,先确认瓶颈到底是不是 CPU——如果瓶颈在磁盘 I/O 或网络,nice 一点忙都帮不上(磁盘要交给后面的 ionice)。同理,在多核机器上跑单线程任务,只要还有空闲核,nice 也几乎不产生影响。
renice:调整正在运行的进程
nice 只能在启动时指定,进程跑起来之后要改,用 renice——它最大的价值是不用重启进程。实测把一个正在死循环的进程从 nice 0 改成 10:
bash -c 'while :; do :; done' & P=$!; sleep 2; renice -n 10 -p $P; ps -o pid,ni,time,comm -p $P; kill $P
93803 (process ID) old priority 0, new priority 10
PID NI TIME COMMAND
93803 10 00:00:02 bashrenice 会明确回显旧值和新值(old priority 0, new priority 10),随后的 ps 也确认 NI 列已经是 10,进程没有中断。
几个常用变体:
renice -n 19 -p 12345 # 指定单个进程
renice -n 19 -g 12345 # 指定进程组(-g 后接 PGID)
renice -n 19 -u backup # 该用户的全部进程找到「谁是 CPU 大户」可以用按 CPU 排序的 ps:
ps -eo pid,ni,pcpu,comm --sort=-pcpu | head -5chrt:换一种调度策略
nice/renice 调的是同一个调度策略(SCHED_OTHER)内部的权重。如果想换一类调度策略,要用 chrt。Linux 的主要调度策略:
| 策略 | chrt 选项 | 说明 |
|---|---|---|
SCHED_OTHER | -o | 默认,CFS 公平调度,受 nice 影响 |
SCHED_BATCH | -b | 批处理,暗示「吞吐优先、不在乎延迟」,不抢占交互任务 |
SCHED_IDLE | -i | 最低优先级,只在 CPU 完全空闲时才跑 |
SCHED_FIFO | -f | 实时,先进先出,优先级 1-99,一旦就绪就抢占所有普通进程 |
SCHED_RR | -r | 实时,时间片轮转 |
查看和设置策略都用 chrt -p:
chrt -p 1; chrt -b 0 bash -c 'chrt -p $$'; ionice -p 1; ionice -c 3 -p $$; ionice -p $$; cat /sys/block/vda/queue/scheduler
输出分三组:
chrt -p 1:PID 1 的策略是SCHED_OTHER,优先级0。普通进程的rtprio恒为 0。chrt -b 0 bash -c 'chrt -p $$':在SCHED_BATCH策略下启动的新进程,chrt -p确认已经是SCHED_BATCH。-b后面的0是实时优先级参数(对非实时策略无意义,填 0 即可)。ionice与调度器(下一节展开)。
实测切换 SCHED_IDLE 和实时策略:
chrt -i 0 bash -c 'chrt -p $$'
chrt -f 50 sleep 3 & sleep 1; chrt -p $!pid 94273's current scheduling policy: SCHED_IDLE
pid 94273's current scheduling priority: 0
pid 94274's current scheduling policy: SCHED_FIFO
pid 94274's current scheduling priority: 50配 ps 一起看更直观——CLS 列是策略缩写、RTPRIO 是实时优先级:
ps -o pid,ni,cls,rtprio,comm -p 1 PID NI CLS RTPRIO COMMAND
1 0 TS - systemdTS = SCHED_OTHER(Time Sharing),FF = SCHED_FIFO,B = SCHED_BATCH,IDL = SCHED_IDLE。
⚠️ 实时策略是双刃剑:SCHED_FIFO优先级高于所有普通进程,一个死循环的 FIFO 进程在单核机器上足以让 SSH 都连不上。上面演示只把它用在sleep(会主动让出 CPU)上。内核留了一道保险:/proc/sys/kernel/sched_rt_runtime_us默认为950000,即实时任务每秒最多占用 95% 的 CPU,剩下 5% 留给普通进程,避免整机彻底卡死——但不要依赖这道保险去做危险实验。
ionice:磁盘 I/O 优先级
CPU 让出来了,磁盘还在被抢——nice 管不到 I/O,那是 ionice 的领域。它把进程分成三类:
| 类别 | 选项 | 说明 |
|---|---|---|
none | -c 0 | 不指定,使用默认(内核按 nice 值推导) |
realtime | -c 1 | 最高,立即访问磁盘,滥用会饿死其他进程 |
best-effort | -c 2 | 默认类,可用 -n 0~7 指定优先级 |
idle | -c 3 | 只在没有其他进程请求磁盘时才访问 |
查看和设置:
ionice -p 1 # 查看 PID 1 的 I/O 优先级
ionice -c 3 -p $$ # 把当前 shell 设为 idle 类
ionice -p $$ # 确认
ionice -c 2 -n 7 -p $$ # best-effort 类、优先级 7(最低)实测结果依次是 none: prio 0、idle、best-effort: prio 7。
但这里有个关键前提:ionice 是否真正生效,取决于块设备的 I/O 调度器。演示机最后一行输出:
[mq-deadline] none方括号表示当前启用的是 mq-deadline——它会尊重 ionice 设置的 best-effort / idle 优先级。可用的另一个选择是 none(不做调度、直接下发),在 none 下 ionice 设置会被完全忽略。所以排查「ionice 好像没用」时,第一件事就是看这个文件:
cat /sys/block/vda/queue/scheduler(把 vda 换成你的实际设备名,用 lsblk 查。)如果输出里方括号套在 none 上,那 ionice 怎么调都不会有区别;临时切换可以 echo mq-deadline > /sys/block/vda/queue/scheduler,持久化则要写 udev 规则。
组合使用与持久化
把三者叠起来,就是一条「尽量别影响别人」的备份命令:
nice -n 19 ionice -c 3 tar czf /tmp/doc.tar.gz /etcCPU 上谦让(nice 19),磁盘上让路(ionice idle)。实测这条命令正常完成打包(527K 的 /etc 归档),期间用 ps 核对,进程确实是 NI=19:
nice -n 19 ionice -c 3 bash -c 'ps -o pid,ni,cls,comm -p $$' PID NI CLS COMMAND
94199 19 TS ps如果要让某个服务长期以低优先级运行,别在启动脚本里包一层 nice——直接写进 systemd 单元最干净:
[Service]
Nice=19
IOSchedulingClass=idle
CPUSchedulingPolicy=batch这三个指令 systemd 原生支持(可用 systemctl show -p Nice -p IOSchedulingClass -p CPUSchedulingPolicy <服务名> 核对当前值,默认分别是 0 / 2(best-effort) / 0(SCHED_OTHER))。改完 systemctl daemon-reload && systemctl restart <服务名> 即可。
小结
按「管什么」把三个工具分清楚,就不会用错:
nice/renice:管 CPU 时间份额,只在 CPU 争抢时生效,renice能改运行中的进程。chrt:换调度策略,-b批处理、-i空闲时再跑,实时策略慎用。ionice:管磁盘 I/O 优先级,生效与否先看/sys/block/<设备>/queue/scheduler。
最实用的组合是 nice -n 19 ionice -c 3 加在备份、压缩、日志归档这类「不急但要跑完」的任务前面;而 systemd 服务则把 Nice=、IOSchedulingClass= 写进单元文件,比在命令行包一层更可靠。
参考资料:
评论 (0)
暂无评论,快来抢沙发吧!