第 35 章 进程优先级与调度 (Process Priorities and Scheduling)

      +

      核心结论

      • *nice 值范围 –20 (高优先级) 到 19 (低优先级)*,默认 0;用于「间接」影响 CFS 调度器权重;2.6.23 CFS 调度器对 nice 差异更敏感;特权进程可设负值,非特权只能调高(降低优先级)。

      • getpriority/setpriority (PRIO_PROCESS/PRIO_PGRP/PRIO_USER):取/设 nice 值;返回 unice = 20 – knice(避免负返回值);调用前必须 errno = 0(成功可能返回 -1)。

      • POSIX 实时调度策略 SCHED_RR (round-robin) 与 SCHED_FIFO:实时进程严格优先于 SCHED_OTHER;优先级 1-99(Linux);同优先级 SCHED_RR/SCHED_FIFO 等价;高优先级可独占 CPU。

      • Linux 非实时策略 SCHED_BATCH (2.6.16+) 与 SCHED_IDLE (2.6.23+):BATCH 减少频繁唤醒进程的调度频率;IDLE 优先级低于 nice +19。

      • sched_setscheduler/sched_setparam/sched_getscheduler/sched_getparam:改/查调度策略与优先级;非特权受 RLIMIT_RTPRIO 限制;成功调用后进程移到队列尾部。

      • sched_yield() 让出 CPU:同优先级有其他进程 → 移到队尾;无其他 → 不动;非实时进程行为未定义。

      • RLIMIT_RTTIME (2.6.25+) 控制实时进程连续 CPU 时间:单位微秒;超过 → SIGXCPU;SCHED_RESET_ON_FORK (2.6.32+) 防止子继承实时策略。

      • sched_setaffinity/sched_getaffinity (Linux-specific):设置/获取 CPU 亲和性掩码(per-thread);CPU_* 宏操作 cpu_set_t;CPU_SETSIZE = 1024;改善 cache locality。

      本章主旨

      本章深入 Linux 进程调度——从传统的 nice 值到 POSIX 实时调度 API,再到 Linux 特有的 CPU 亲和性。读者应掌握:nice 值的范围与含义、CFS 调度器对 nice 的处理、POSIX 实时调度策略(SCHED_RR/SCHED_FIFO/SCHED_OTHER/SCHED_BATCH/SCHED_IDLE)的差异、sched_setscheduler 等 API、RLIMIT_RTPRIO/RLIMIT_RTTIME 资源限制、SCHED_RESET_ON_FORK 标志、sched_yield 让出 CPU、sched_setaffinity 控制 CPU 亲和性。理解「实时调度可能 lock up 系统」是写实时应用的关键——runaway 实时进程独占 CPU,需要 RLIMIT_CPU/alarm/watchdog/RLIMIT_RTTIME 多重防护。这是写音视频处理、嵌入式控制、工业自动化等实时应用的基础。

      一、核心概念

      本章围绕 6 个核心概念展开:从 nice 值入手,到 POSIX 实时调度策略、sched_setscheduler API、资源限制、sched_yield、CPU 亲和性。

      概念 定义 + 重要性 实现提示

      nice 值与 CFS 调度

      nice 值范围 –20 (高) 到 19 (低);默认 0;CFS 用 nice 作调度权重(2.6.23 强敏感);传统只有特权可设负 nice;Linux 2.6.12+ RLIMIT_NICE 允许非特权设负值(上限 20 - rlim_cur)。

      §35.1;getpriority/setpriority 取/设(PRIO_PROCESS/PRIO_PGRP/PRIO_USER);getpriority 返回 unice = 20 – knice;调用前必须 errno = 0;nice 命令行 nice(1)/renice(8)。

      POSIX 实时调度策略

      SCHED_FIFO(无时间片,独占 CPU 到放弃/被抢占)、SCHED_RR(round-robin,同优先级共享 CPU);Linux 优先级 1-99;SCHED_BATCH(批处理)+ SCHED_IDLE(极低优先级);都优于 SCHED_OTHER。

      §35.2;sched_get_priority_min/max 查范围;Linux 1-99;同优先级 SCHED_FIFO/SCHED_RR 等价;高优先级进程可独占 CPU——runaway 风险。

      sched_setscheduler API

      改策略 + 优先级;pid=0 → 调用者;policy 接受 SCHED_RR/FIFO/OTHER/BATCH/IDLE;param.sched_priority;成功移到队列尾;非特权受 RLIMIT_RTPRIO 限制。

      §35.3.2;sched_setparam 只改优先级;sched_getscheduler/getparam 查;fork 继承、exec 保留;SCHED_RESET_ON_FORK 防子继承实时。

      实时进程资源限制

      RLIMIT_CPU(SIGXCPU 杀死)+ alarm(SIGALRM)+ watchdog 进程 + RLIMIT_RTTIME(2.6.25+,单位 μs,连续 CPU 时间不阻塞系统调用则计数);防止 runaway 实时进程 lock 系统。

      §35.3.2 末;RLIMIT_RTPRIO(非特权设实时优先级上限);realtime scheduling groups (CONFIG_RT_GROUP_SCHED, 2.6.25+)。

      sched_yield 让出 CPU

      同优先级有其他进程 → 移到队尾;无其他 → 不动;Linux 总成功(SUSv3 允许失败);非实时进程行为未定义。

      §35.3.3;典型用途——实时进程间协作;无需锁但要让同优先级进程运行。

      CPU 亲和性 (sched_setaffinity)

      Linux-specific;cpu_set_t 表示 CPU 掩码;CPU_ZERO/SET/CLR/ISSET 宏操作;CPU_SETSIZE = 1024;per-thread 属性;gettid() 改特定线程;改善 cache locality、隔离关键进程。

      §35.4;sched_getaffinity 查掩码;fork 继承、exec 保留;isolcpus 启动参数隔离 CPU;cpuset 更精细控制(多 NUMA)。

      二、详细笔记

      35.1 nice 值与 CFS 调度

      What:nice 值是每个进程的调度权重指标,范围 –20(高优先级)到 +19(低优先级),默认 0;CFS 用作权重。

      Why:nice 是最简单的「间接」调度控制手段——非特权进程可降低自己的优先级(提高 nice)以「nice to others」;特权进程可提高优先级(降低 nice)。

      How

      // 摘自《The Linux Programming Interface》第 35 章
      #include <sys/resource.h>
      int getpriority(int which, id_t who);   /* 返回 nice 值或 -1 */
      int setpriority(int which, id_t who, int prio);

      API 要点:

      • which:PRIO_PROCESS(who=PID)、PRIO_PGRP(who=PGID)、PRIO_USER(who=UID)。

      • who=0 → 调用者 PID/PGID/UID。

      • getpriority 返回 unice = 20 – knice——避免负返回(系统调用错误返回)。

      • 调用 getpriority 前必须 errno = 0——成功也可能返回 -1。

      • setpriority 试图超出范围自动 bounding 到 –20/+19。

      • PRIO_PGRP/PRIO_USER 返回「优先级最高的」(最低 nice 值)。

      权限(§35.1):

      • 特权(CAP_SYS_NICE):可改任意进程的 nice。

      • 非特权:可改自己或 euid/ruid 匹配的进程。

      • Linux 2.6.12+ 非特权可提高自己 nice(受 RLIMIT_NICE 限制)——上限 20 – rlim_cur

      • 早期内核:非特权只能单向降低(提高 nice)——「irreversibly lower」。

      CFS 行为(§35.1):

      • 2.6.23+ CFS 调度器对 nice 差异敏感——低 nice 进程获更多 CPU(之前差异较小)。

      • 不会完全 starving——即使是 +19 也能拿到少量 CPU。

      • nice 跨 fork 继承、exec 保留。

      When:降低批量作业优先级(nice +19);实时/关键进程提权(需 CAP_SYS_NICE)。

      Example(§35.1,Listing 35-1):

      // 摘自《The Linux Programming Interface》第 35 章
      // 摘自 procpri/t_setpriority.c
      which = (argv[1][0] == 'p') ? PRIO_PROCESS :
              (argv[1][0] == 'g') ? PRIO_PGRP : PRIO_USER;
      who = getLong(argv[2], 0, "who");
      prio = getInt(argv[3], 0, "priority");
      setpriority(which, who, prio);
      errno = 0;
      prio = getpriority(which, who);   /* 成功可能返回 -1 */
      if (prio == -1 && errno != 0) errExit("getpriority");

      35.2 POSIX 实时调度

      What:POSIX 实时调度 API 提供 SCHED_RR(round-robin)与 SCHED_FIFO(first-in first-out)两种实时策略;总是优先于 SCHED_OTHER。

      Why:实时应用需保证最大响应时间——高优先级进程必须能立即抢占 SCHED_OTHER 进程;POSIX 仅「soft realtime」——硬实时需特殊 OS 支持。

      How

      策略对比(§35.2):

      策略 行为 放弃 CPU 条件

      SCHED_FIFO

      无时间片;独占 CPU

      阻塞系统调用/sched_yield/终止/被更高优先级抢占

      SCHED_RR

      有时间片;同优先级 round-robin

      时间片到/阻塞/sched_yield/终止/被抢占

      SCHED_OTHER

      标准 round-robin 时间共享

      默认

      SCHED_BATCH (2.6.16+)

      类似 SCHED_OTHER,减少频繁唤醒

      批处理

      SCHED_IDLE (2.6.23+)

      极低优先级(低于 nice +19)

      仅其他进程不要 CPU 时才运行

      Linux 优先级范围:

      • SCHED_RR/SCHED_FIFO:1(最低)到 99(最高)。

      • SCHED_OTHER:只能是 0。

      • 同优先级 SCHED_RR/SCHED_FIFO 等价——调度顺序取决于队列位置。

      多处理器注意(§35.2):

      • 每 CPU 独立 run queue——高优先级进程 A 等 CPU 0 不会抢占低优先级进程 B 在 CPU 1。

      • 解——用 sched_setaffinity 把关键进程绑到指定 CPU。

      • 典型:4 CPU 系统把 3 个隔离给实时应用,1 个给其他进程。

      When:实时控制(工业自动化、媒体播放);低延迟网络服务;音频处理。

      Example:见 §35.3.2。

      35.3 实时调度 API

      What:sched_setscheduler/sched_setparam/sched_getscheduler/sched_getparam + sched_yield/sched_rr_get_interval;改/查调度属性。

      Why:精细控制进程的调度策略与优先级——软实时应用必备。

      How

      API 速查(§35.3):

      // 摘自《The Linux Programming Interface》第 35 章
      #include <sched.h>
      struct sched_param { int sched_priority; };
      
      int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
      int sched_setparam(pid_t pid, const struct sched_param *param);
      int sched_getscheduler(pid_t pid);
      int sched_getparam(pid_t pid, struct sched_param *param);
      int sched_get_priority_min(int policy);
      int sched_get_priority_max(int policy);
      int sched_yield(void);
      int sched_rr_get_interval(pid_t pid, struct timespec *tp);

      参数与返回值:

      • pid=0 → 调用者进程/线程。

      • policy:SCHED_RR/SCHED_FIFO/SCHED_OTHER/SCHED_BATCH/SCHED_IDLE

      • sched_setscheduler 成功返回 0(Linux 偏离 SUSv3——SUSv3 应返回前 policy)。

      • sched_setscheduler/setparam 成功后将进程移到队列尾部。

      • fork 继承调度属性;exec 保留。

      权限与 RLIMIT_RTPRIO(§35.3.2):

      • 特权(CAP_SYS_NICE):任意改。

      • 非特权 + RLIMIT_RTPRIO > 0:可设 realtime policy;上限 = max(当前 realtime priority, RLIMIT_RTPRIO)。

      • 非特权 + RLIMIT_RTPRIO == 0:只能降低 realtime priority 或切回非实时。

      • SCHED_IDLE 进程不能改策略(无论 RLIMIT_RTPRIO)。

      • 跨用户:caller euid 匹配 target ruid 或 euid。

      • RLIMIT_RTPRIO 不授权改其他进程——只授权自己。

      SCHED_RESET_ON_FORK(§35.3.2):

      • 2.6.32+ 新增。

      • 标志 ORed with policy:sched_setscheduler(pid, SCHED_FIFO | SCHED_RESET_ON_FORK, &sp)

      • 效果:fork 子进程不会继承实时策略(SCHED_OTHER)+ nice 值重置为 0。

      • 用途:媒体播放应用——单一进程实时,避免 fork bomb。

      • 启用后只有特权能禁用;子进程的 reset-on-fork 标志自动清除。

      sched_yield(§35.3.3):

      • 同优先级有其他可运行进程 → 移到队尾,让其他进程运行。

      • 无其他 → 不动。

      • Linux 总是成功(SUSv3 允许失败);非实时进程行为未定义。

      sched_rr_get_interval(§35.3.4):

      • 返回 SCHED_RR 时间片长度到 timespec。

      • Linux 2.6+:0.1 秒(100 ms)。

      防止 runaway 实时进程(§35.3.2 末):

      • RLIMIT_CPU(秒)→ SIGXCPU。

      • alarm()(秒)→ SIGALRM。

      • watchdog 进程(高实时优先级)→ 监控其他进程 CPU time + 必要时降权/停止。

      • RLIMIT_RTTIME(2.6.25+,μs)→ 实时进程连续 CPU 时间不阻塞则计数;超过 SIGXCPU。

      • realtime scheduling groups(CONFIG_RT_GROUP_SCHED)→ 进程组级别时间限制。

      When:写实时应用;写 CPU 密集型服务需要精确控制优先级;debug 调度问题。

      Example(§35.3.2,Listing 35-2):

      // 摘自《The Linux Programming Interface》第 35 章 — Listing 35-2
      // 摘自 procpri/sched_set.c
      pol = (argv[1][0] == 'r') ? SCHED_RR :
            (argv[1][0] == 'f') ? SCHED_FIFO :
            (argv[1][0] == 'b') ? SCHED_BATCH :
            (argv[1][0] == 'i') ? SCHED_IDLE :
            SCHED_OTHER;
      sp.sched_priority = getInt(argv[2], 0, "priority");
      sched_setscheduler(getLong(argv[j], 0, "pid"), pol, &sp);

      35.4 CPU 亲和性

      What:sched_setaffinity/sched_getaffinity 设置/获取进程(或线程)允许运行的 CPU 子集。

      Why:避免跨 CPU cache invalidation;隔离关键进程;同 CPU 共享数据更高效。

      How

      // 摘自《The Linux Programming Interface》第 35 章
      #define _GNU_SOURCE
      #include <sched.h>
      int sched_setaffinity(pid_t pid, size_t len, cpu_set_t *set);
      int sched_getaffinity(pid_t pid, size_t len, cpu_set_t *set);
      
      void CPU_ZERO(cpu_set_t *set);
      void CPU_SET(int cpu, cpu_set_t *set);
      void CPU_CLR(int cpu, cpu_set_t *set);
      int CPU_ISSET(int cpu, cpu_set_t *set);

      要点:

      • cpu_set_t 应视为不透明结构(虽然实现为位掩码)——用宏操作。

      • CPU_SETSIZE = 1024——最大 1024 CPU(编号 0-1023)。

      • len 参数传 sizeof(cpu_set_t)。

      • pid=0 → 调用线程;gettid() → 改特定线程。

      • per-thread 属性——线程组内可独立设置。

      • fork 继承;exec 保留。

      • 未设置 → 默认所有 CPU(sched_getaffinity 返回全集合)。

      • 权限:caller euid 匹配 target ruid/euid,或特权。

      • set 中 CPU 不存在 → EINVAL。

      isolcpus 与 cpuset(§35.4):

      • isolcpus=2,3 内核参数:隔离 CPU 2,3——只有 sched_setaffinity 可在那运行。

      • cpuset:更精细控制 CPU + 内存节点分配(多 NUMA、大规模系统)。

      • 用 cpuset 实现「第 35.3 节最后场景」比 sched_setaffinity 更高效。

      When:实时应用隔离到专用 CPU;NUMA 亲和(同 node);多线程同数据共享绑同 CPU。

      Example

      // 摘自《The Linux Programming Interface》第 35 章
      cpu_set_t set;
      CPU_ZERO(&set);
      CPU_SET(1, &set); CPU_SET(2, &set); CPU_SET(3, &set);
      sched_setaffinity(pid, sizeof(set), &set);  /* 不在 CPU 0 运行 */

      三、关键图表

      Linux 调度策略对比(表 35-1)
      策略 描述 SUSv3

      SCHED_FIFO

      实时 FIFO;无时间片

      SCHED_RR

      实时 round-robin;有时间片

      SCHED_OTHER

      标准 round-robin 时间共享

      SCHED_BATCH

      类似 SCHED_OTHER,批处理(少唤醒调度)

      ✗ (Linux 2.6.16+)

      SCHED_IDLE

      极低优先级(低于 nice +19)

      ✗ (Linux 2.6.23+)

      实时优先级 1-99;Linux 上 SCHED_RR/SCHED_FIFO 同优先级等价。

      sched_setscheduler 权限与 RLIMIT_RTPRIO(2.6.12+)
      场景 允许的操作

      CAP_SYS_NICE 特权

      任意改任意进程

      非特权 + RLIMIT_RTPRIO > 0

      设 realtime policy;上限 = max(当前 realtime priority, RLIMIT_RTPRIO)

      非特权 + RLIMIT_RTPRIO == 0

      降低 realtime priority 或切回非实时

      SCHED_IDLE 进程

      不能改策略(无论 RLIMIT_RTPRIO)

      跨用户

      caller euid 匹配 target ruid/euid

      RLIMIT_RTPRIO 不授权改其他进程

      只授权自己

      防止 runaway 实时进程的 5 种方法
      方法 机制

      RLIMIT_CPU (setrlimit)

      超过 → SIGXCPU(默认杀)

      alarm()

      超过 wall time → SIGALRM

      watchdog 进程

      高优先级监控,必要时降权/停止

      RLIMIT_RTTIME (2.6.25+)

      实时进程连续 CPU 时间不阻塞则计数;超过 → SIGXCPU

      realtime scheduling groups

      进程组级别时间限制(CONFIG_RT_GROUP_SCHED)

      四、思维导图

      mindmap
        root((第 35 章 调度))
          nice 值
            范围 20 到 19
            默认 0
            CFS 权重 2 6 23 强敏感
            getpriority setpriority
            PRIO PROCESS PGRP USER
            unice 20 knice
            errno 0 必须
            RLIMIT NICE 非特权可设负值
          POSIX 实时策略
            SCHED FIFO 无时间片
            SCHED RR round robin
            1 99 Linux
            同优先级 FIFO RR 等价
            SCHED OTHER 标准
            SCHED BATCH 2 6 16
            SCHED IDLE 2 6 23
            高优先级独占 CPU
          sched setscheduler API
            setscheduler 改策略 优先级
            setparam 仅优先级
            getscheduler getparam
            成功移到队列尾
            fork 继承 exec 保留
            RLIMIT RTPRIO 限制
            SCHED RESET ON FORK 2 6 32
          sched yield
            同优先级让出
            队尾
            Linux 总成功
            非实时未定义
          资源限制
            RLIMIT CPU SIGXCPU
            alarm SIGALRM
            watchdog 进程
            RLIMIT RTTIME 2 6 25
            realtime group CONFIG
            防 runaway 实时进程
          CPU 亲和性
            sched setaffinity
            sched getaffinity
            cpu set t 1024 CPU
            CPU ZERO SET CLR ISSET
            per thread 属性
            gettid 特定线程
            fork exec 保留
            isolcpus 启动参数
            cpuset 精细控制

      五、重点与易错点

      1. *nice 值范围 –20 到 19*:高 nice = 低优先级;CFS 2.6.23 对 nice 差异更敏感(之前差异小)。

      2. getpriority 返回 unice = 20 – knice:避免与错误返回 -1 冲突;调用前必须 errno = 0。

      3. 非特权只能降低自己的优先级(提高 nice);Linux 2.6.12+ RLIMIT_NICE 允许非特权设负值。

      4. POSIX 仅 soft realtime:硬实时需 RTOS 或 PREEMPT_RT Linux patch;Linux 主线 2.6.18+ 加了部分功能。

      5. 实时进程可能 lock 系统——runaway SCHED_FIFO/SCHED_RR 独占 CPU;要 RLIMIT_CPU + alarm + watchdog + RLIMIT_RTTIME 多重防护。

      6. SCHED_FIFO 无时间片——独占 CPU 直到阻塞/sched_yield/终止/被抢占;SCHED_RR 同优先级 round-robin。

      7. Linux 上 SCHED_RR/SCHED_FIFO 同优先级等价——调度顺序取决于队列位置。

      8. sched_setscheduler Linux 返回 0(成功)——SUSv3 应返回前 policy;可移植代码只检查 != -1。

      9. RLIMIT_RTPRIO 限制非特权设 realtime 优先级上限——非特权 RLIMIT_RTPRIO > 0 可设,但不超过 max(当前, limit);RLIMIT_RTPRIO == 0 只能降低。

      10. SCHED_IDLE 进程不能改策略——无论 RLIMIT_RTPRIO。

      11. SCHED_RESET_ON_FORK (2.6.32+) 防止子继承实时策略——fork 时子切回 SCHED_OTHER + nice=0;用于媒体播放;启用后只有特权能禁用。

      12. RLIMIT_RTTIME (2.6.25+) 控制实时进程连续 CPU 时间——单位 μs;阻塞系统调用时计数清零;超过 → SIGXCPU;不防止调度切换清零(preempt 不清零)。

      13. sched_setaffinity 是 per-thread 属性——线程组内可独立设置;gettid() 改特定线程;pid=0 改调用线程。

      14. cpu_set_t 应视为不透明——用 CPU_ZERO/SET/CLR/ISSET 宏操作;CPU_SETSIZE = 1024。

      15. CPU 亲和性 per-thread——fork 继承;exec 保留;isolcpus 内核参数 + cpuset 实现 CPU 隔离。

      16. sched_setscheduler 成功移到队列尾——避免「刚设完就抢 CPU」导致不公平;同理 sched_setparam。

      17. 多 CPU 系统每 CPU 独立 run queue——A 高优先级等 CPU 0 不会抢 B 低优先级在 CPU 1;用 sched_setaffinity 绑关键进程到专用 CPU。

      18. sched_yield 非实时进程行为未定义——只对实时进程有意义;同优先级让出,无其他不动。

        • 跨章衔接:第 24 章 fork 基础 → 第 25 章进程终止 → 第 35 章调度策略;第 36 章资源限制 RLIMIT_CPU/RLIMIT_RTPRIO/RLIMIT_RTTIME;第 39 章 capabilities CAP_SYS_NICE;第 33 章线程 → per-thread 调度属性 + SCHED_FIFO/RR 线程调度;第 28 章 clone → CLONE_THREAD 共享调度策略。