暗色模式

Linux 进程调度优先级:从 nice/renice 到 ionice 与 chrt

技术教程
2026-09-11
3
0
本文要点
  • 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
-208876186.7 倍
-1095489.3 倍
010241 倍
53350.33 倍
101100.11 倍
15360.035 倍
19150.015 倍

这张表解释了两件事:

  1. nice 的差距是乘法关系,不是加法。nice 0 和 nice 19 的权重比是 1024 : 15 ≈ 68 : 1——不是「慢一点」,而是在争抢时几乎拿不到 CPU。反过来,nice -20 能拿到 nice 19 进程的约 5900 倍 CPU 时间。
  2. 权重是相对分配的。如果机器上只有你一个进程,权重多少都是 100% CPU——这也是下一节实测要验证的重点。

查看当前 shell 的 nice 值,直接跑不带参数的 nice

nice; nice -n 5 nice
0
5

nice -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

nice 0 与 nice 19 两个死循环争抢单核 CPU 的实测对比

输出读法:

  • NI 列是 nice 值,两行分别是 190,确认两个进程按预期启动。
  • %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 s

4.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

renice 调整运行中进程的 nice 值

93803 (process ID) old priority 0, new priority 10
    PID  NI     TIME COMMAND
  93803  10 00:00:02 bash

renice 会明确回显旧值和新值(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 -5

chrt:换一种调度策略

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

查看调度策略、切换 SCHED_BATCH、设置 ionice 与查看 I/O 调度器

输出分三组:

  1. chrt -p 1:PID 1 的策略是 SCHED_OTHER,优先级 0。普通进程的 rtprio 恒为 0。
  2. chrt -b 0 bash -c 'chrt -p $$':在 SCHED_BATCH 策略下启动的新进程,chrt -p 确认已经是 SCHED_BATCH-b 后面的 0 是实时优先级参数(对非实时策略无意义,填 0 即可)。
  3. 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      - systemd

TS = 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 0idlebest-effort: prio 7

但这里有个关键前提ionice 是否真正生效,取决于块设备的 I/O 调度器。演示机最后一行输出:

[mq-deadline] none

方括号表示当前启用的是 mq-deadline——它会尊重 ionice 设置的 best-effort / idle 优先级。可用的另一个选择是 none(不做调度、直接下发),noneionice 设置会被完全忽略。所以排查「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 /etc

CPU 上谦让(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= 写进单元文件,比在命令行包一层更可靠。

参考资料:

发表评论

暂无评论,快来抢沙发吧!