Linux-线程优先级学习

发布时间 2023-12-07 22:29:33作者: zhengcixi

概念

Linux系统中常用的几种调度类为SCHED_NORMAL、SCHED_FIFO、SCHED_RR

  • SCHED_NORMAL:用于普通线程的调度类
  • SCHED_FIFO和SCHED_RR是用于实时线程的调度类,优先级高于SCHED_NORMAL。

内核中区分普通线程与实时线程是根据线程的优先级,实时线程拥有实时优先级(real-time priority),默认取值为0~99,数值越高优先级越高

普通线程只具有nice值,nice值映射到用户层的取值范围为-20~+19,数值越高优先级越低,默认初始值为0 ,子线程会继承父线程的优先级。

标准函数

#include <pthread.h>

pthread_setschedparam(pthread_t thread, int policy,
                        const struct sched_param *param);
pthread_getschedparam(pthread_t thread, int *policy,
                        struct sched_param *param);

Compile and link with -pthread.
  • 描述:

    • pthread_setschedparam函数用于设置调度策略policy和线程参数(也就是优先级)
    • pthread_getschedparam函数用户获取调度策略policy和线程参数(也就是优先级)
  • 参数说明:

    • policy参数:线程新的调度策略,policy的描述可查看sched_setscheduler(2)
    // /usr/include/bits/sched.h
    #define SCHED_OTHER  0
    #define SCHED_FIFO   1
    #define SCHED_RR     2
    
    • param参数:指定新的调度策略的参数,每一种调度策略的优先级允许设置的范围可查看sched_setscheduler(2)
    struct sched_param {
        int sched_priority;     /* Scheduling priority */
    };
    
  • 返回值:成功返回0,失败返回非0值。常见的错误值

    // /usr/include/asm-generic/errno-base.h
    #define EPERM        1  /* Operation not permitted */
    #define ESRCH        3  /* No such process */
    #define EINVAL      22  /* Invalid argument */
    
    • ESRCH: 线程ID未发现
    • EINVAL: policy不合法,或者param不合法
    • EPERM: 没有权限,policy为SCHED_FIFO、SCHED_RR时,需要root权限

标准示例

示例演示了使用pthread_setschedparam() 和 pthread_getschedparam()和其它一些与实施调度相关的一些函数。

  • main thread设置为SCHED_FIFO调度策略,优先级设置为10,并且初始化一个线程属性对象:调度策略SCHED_RR,优先级20。
  • 接着程序设置线程的继承调度器属性为PTHREAD_EXPLICIT_SCHED -- 创建的线程属性应该为线程属性对象的调度器属性。
  • 接着使用线程属性对象创建一个线程,然后显示线程的调度策略和优先级。

源码:standerd_examp.c.c

测试:

# 默认启动的线程为调度policy为SCHED_OTHER, 默认的调度优先级为0
root@ubuntu:/home/grace# ./a.out 
Scheduler settings of main thread
    policy=SCHED_OTHER, priority=0
# 没有设置attribute
Scheduler settings in 'attr'
    policy=SCHED_OTHER, priority=0
    inheritsched is INHERIT
# 新创建线程优先级和main一致
Scheduler attributes of new thread
    policy=SCHED_OTHER, priority=0
# 设置main线程调度策略为SCHED_FIFO, 调度优先级为10
# 设置thread attributes object的调度策略为SCHED_RR, 优先级为20
# 设置inherit scheduler attribute为PTHREAD_EXPLICIT_SCHED
# 测试结果为新创建的线程的调度策略和优先级为attribute object设置的值
root@ubuntu:/home/grace# ./a.out -mf10 -ar20 -i e
Scheduler settings of main thread
    policy=SCHED_FIFO, priority=10

Scheduler settings in 'attr'
    policy=SCHED_RR, priority=20
    inheritsched is EXPLICIT

Scheduler attributes of new thread
    policy=SCHED_RR, priority=20
# 设置main线程调度策略为SCHED_FIFO, 调度优先级为10
# 设置thread attributes object的调度策略为SCHED_RR, 优先级为20
# 设置inherit scheduler attribute为PTHREAD_INHERIT_SCHED,
# 测试结果为新创建的线程会忽略attribute object设置的值, 属性值直接从父线程继承
root@ubuntu:/home/grace# ./a.out -mf10 -ar20 -i i
Scheduler settings of main thread
    policy=SCHED_FIFO, priority=10

Scheduler settings in 'attr'
    policy=SCHED_RR, priority=20
    inheritsched is INHERIT

Scheduler attributes of new thread
    policy=SCHED_FIFO, priority=10

相关工具

ps

可以查看线程的调度优先级和nice值。

测试代码:ps.c

功能:在主线程中会创建三个线程

  • 第一个线程的调度策略为SCHED_FIFO, 优先级为60
  • 第二个线程的调度策略为SCHED_RR, 优先级为20
  • 第三个线程德调度策略为SCHED_OTHER, 优先级只能配置为0
$ ps -Telf
F S UID     PID  SPID  PPID   C  PRI NI ADDR SZ WCHAN STIME TTY     TIME     CMD
4 S root   2139  2139  2138   0  80   0  -  6840 -    20:41 pts/6  00:00:00 ./a.out # 线程调度策略为SCHED_OTHER, nice值为0
5 S root   2139  2140  2138   0  -1   -  -  6840 -    20:41 pts/6  00:00:00 ./a.out # real-time调度策略
5 S root   2139  2141  2138   0  39   -  -  6840 -    20:41 pts/6  00:00:00 ./a.out # real-time调度策略
5 S root   2139  2142  2138   0  80   0  -  6840 -    20:41 pts/6  00:00:00 ./a.out

部分字段含义:

  • PRI: priority of the process. Higher number means lower priority.
  • NI: nice value. This ranges from 19 (nicest) to -20 (not nice to others), see nice(1). (alias nice).

chrt工具

用来查看或改变线程的real-time调度属性


F S UID     PID  SPID  PPID   C  PRI NI ADDR SZ WCHAN STIME TTY     TIME     CMD
4 S root   2139  2139  2138   0  80   0  -  6840 -    20:41 pts/6  00:00:00 ./a.out # 线程调度策略为SCHED_OTHER, nice值为0
5 S root   2139  2140  2138   0  -1   -  -  6840 -    20:41 pts/6  00:00:00 ./a.out # real-time调度策略
5 S root   2139  2141  2138   0  39   -  -  6840 -    20:41 pts/6  00:00:00 ./a.out # real-time调度策略
5 S root   2139  2142  2138   0  80   0  -  6840 -    20:41 pts/6  00:00:00 ./a.out

$ chrt -p 2139
pid 2139's current scheduling policy: SCHED_OTHER
pid 2139's current scheduling priority: 0

$ chrt -p 2140
pid 2140's current scheduling policy: SCHED_FIFO
pid 2140's current scheduling priority: 60

$ chrt -p 2141
pid 2141's current scheduling policy: SCHED_RR
pid 2141's current scheduling priority: 20

$ chrt -p 2142
pid 2142's current scheduling policy: SCHED_OTHER
pid 2142's current scheduling priority: 0

chrt工具看到的SCHED_OTHER类型的进程的优先级都为0,可以通过cat /proc/tast-num/sched查看:

SessionLeader (734, #threads: 1)
-------------------------------------------------------------------
policy                                       :                    0   # 调度策略
prio                                         :                  120   # 优先级
clock-delta                                  :                  100

SCHED_SETSCHEDULER(2)

参考:https://zhuanlan.zhihu.com/p/618044514?utm_id=0

#include <sched.h>
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
int sched_getscheduler(pid_t pid);

struct sched_param {
    ...
    int sched_priority;
    ...
};

sched_setscheduler函数的作用是为pid指定的线程设置调度策略和优先级。如果pid为0,则会设置调用该函数的线程的策略和优先级。

sched_getscheduler函数作用是查询pid指定的线程的调度策略和优先级。如果pid为0,则会查询调用该函数的线程的策略和优先级。

Linux支持的调度策略分为两类:

  • normal类型:
    • SCHED_OTHER the standard round-robin time-sharing policy; # 循环分时调度策略
    • SCHED_IDLE for running very low priority background jobs. # 低优先级线程作为背景线程运行
  • real-time类型
    • SCHED_FIFO a first-in, first-out policy; # 先进先出调度策略
    • SCHED_RR a round-robin policy. # 轮转调度

Scheduling policies

  • 调度器是内核的一部分,用来决定哪个就绪态线程获取CPU执行。每一个线程有一个关联的调度策略和一个static调度优先级sched_priority,这些参数可以通过sched_setscheduler()函数设置。内核中的调度器根据调度策略和所有处理器的static priority决定调度哪个线程。
  • 使用normal调度策略(SCHED_OTHER, SCHED_IDLE, SCHED_BATCH)的线程,sched_priority参数没有使用,必须设置为0。
  • 使用real-time调度策略(SCHED_FIFO, SCHED_RR)有一个sched_priority,范围1 (low) 到 99 (high)。(real-time类型线程总是高于normal类型线程)。可以使用sched_get_priority_min()和sched_get_priority_max()函数查看每个调度策略对应的优先级范围。
  • 调度器根据sched_priority值保存一个可运行线程列表,然后调度器选择最高的static priority线程列表,选择列表开头的线程运行。
  • 同样的static priority的线程,调度器根据调度算法决定哪个运行。
  • 线程的调度策略决定线程将插入到具有同等优先级的线程列表中的位置。
  • 所有的调度都是抢占式的,如果具有高静态优先级的线程准备运行,则当前正在运行的线程将被抢占并返回到其静态优先级的等待列表中。调度策略仅在具有同等静态优先级的可运行线程列表中确定排序。

SCHED_FIFO: First in-first out scheduling

  • SCHED_FIFO只能使用static priorities大于0的值。这表示,当一个调度策略为SCHED_FIFO的线程变成运行态时,总是优先于当前正在运行的SCHED_OTHER, SCHED_BATCH, or SCHED_IDLE策略的线程。
  • SCHED_FIFO是一个简单的调度算法,没有切分时间片。
  • 调度策略为SCHED_FIFO的线程遵循下面的规则:
    • 一个低优先级SCHED_FIFO调度的线程被更高优先级的线程抢占后,会放在同等优先级列表的头部,等待所有更高优先级的线程执行完毕后,会恢复执行。
    • 当一个阻塞的SCHED_FIFO线程变成就绪态时,会放在同等优先级列表的尾部。
    • 可以使用sched_setscheduler(2)、sched_setparam(2)、sched_setattr(2)、pthread_setschedparam(3) 或 pthread_setschedprio(3)函数设置正在运行的SCHED_FIFO调度线程的优先级,则对线程在列表中的位置影响却决于线程优先级的更改方向。
      • 如果线程的优先级提高,将其放在新的线程列表的尾部。
      • 如果线程的优先级不变,则其在列表中的位置不变。
      • 如果线程的优先级降低,则将其放在新优先级列表的前面。
    • 一个线程调用sched_yield()函数将放到列表的尾部。
    • 一个SCHED_FIFO线程会一直运行,除非被I/O请求阻塞,或者被更高优先级线程抢占,或者调用sched_yield()函数。

SCHED_RR: Round-robin scheduling

  • SCHED_RR调度策略是SCHED_FIFO策略的增强版本。
  • 适用于SCHED_FIFO的策略也适用于SCHED_RR,除了每一个线程仅被运行执行一个最大的时间片。
  • 如果SCHED_RR线程运行时间已经大于等于了时间片,它会被放在同等优先级列表的尾部。
  • 一个SCHED_RR线程被更高优先级的线程抢占后,恢复执行时使用未消耗完的时间片。
  • 时间片可以使用sched_rr_get_interval()函数查询。

SCHED_OTHER: Default Linux time-sharing scheduling

  • SCHED_OTHER调度策略仅能使用static priority为0(real-time的优先级始终高于normal)。
  • SCHED_OTHER是标准的时间片轮转调度,适用于不需要特殊实时机制的所有线程。
  • 要运行的线程是从static priority为0列表中选择的,该列表基于一个动态优先级。
  • 动态优先级基于nice值,可以通过nice()或者setpriority()设置。
  • 当线程准备运行但被调度器拒绝运行时,nice值也会增加。
  • nice值是一个属性,它会影响SCHED_OTHER和SCHED_BATCH(见下文)进程的调度。nice 值可以使用 nice(2)、setpriority(2) 或 sched_setattr(2) 进行修改。
  • Linux上,nice值的范围是-20(高优先级)到+19(低优先级)。

SCHED_IDLE: Scheduling very low priority jobs

  • SCHED_IDLE线程的static priority值为0。线程的nice值对此调度策略没有影响。
  • SCHED_IDLE调度策略适用于优先级非常低的线程。

Resetting scheduling policy for child processes

  • 每一个线程有一个reset-on-fork调度标志。设置此标志后,fork()创建的子进程不会继承特权调度策略,可以通过下面的方式设置此标志:
    • 当调用sched_setscheduler()函数时,将标志SCHED_RESET_ON_FORK放入策略参数中。
    • 调用 sched_setattr(2) 时,在 attr.sched_flags 中指定 SCHED_FLAG_RESET_ON_FORK 标志。
  • 当SCHED_RESET_ON_FORK标志设置后,创建的子线程将遵循下面的规则:
    • 如果父线程调度策略为SCHED_FIFO或者SCHED_RR,子线程将恢复为SCHED_OTHER;
    • 如果父线程有一个负的nice值,子线程的nice值将变为0;
    • 这个标志将在fork()函数创建子线程后失效,也就是子线程不继承这个标志;
    • 这个标志可以通过sched_getscheduler()函数查询。

Privileges and resource limits

  • 在Linux 2.6.12版本后,RLIMIT_RTPRIO限制了SCHED_RR和SCHED_FIFO调度策略的非特权程序的static priority上限。更改调度策略和优先级遵守下面的规则:
    • 如果一个非特权程序有非0的RLIMIT_RTPRIO软件限制,它可以改变它的调度策略和优先级。但优先级不能设置高于它当前的优先级的最大值和RLIMIT_RTPRIO软限制。
    • 如果RLIMIT_RTPRIO为0,则唯一允许的更改是降低优先级或切换到非real-time调度策略。
  • 可以使用RLIMIT_RTTIME资源限制来设置实时进程可能消耗的 CPU 时间的上限,详见getrlimit(2)
  • 从 Linux 2.6.25 开始,Linux 还提供了两个 /proc 文件,可用于保留一定数量的 CPU 时间供非real-time进程使用。以这种方式保留 CPU 时间允许将一些 CPU 时间分配给(例如)可用于终止失控进程的根 shell。这两个文件都以微秒为单位指定时间值:
    • /proc/sys/kernel/sched_rt_period_us: 指定相当于 100% CPU 带宽的调度周期。此文件中的值范围为 1 到 INT_MAX,工作范围为 1 微秒到 35 分钟左右。此文件中的默认值为 1,000,000(1 秒)。
    • /proc/sys/kernel/sched_rt_runtime_us: 此文件中的值指定系统上所有实时进程可以使用多少“周期”时间。此文件中的值的范围可以从 -1 到 INT_MAX-1。指定 -1 可使运行时间与周期相同;也就是说,没有为非实时进程留出 CPU 时间(这是 Linux 2.6.25 之前的行为)。此文件中的默认值为 950,000(0.95 秒),这意味着 5% 的 CPU 时间保留给不在实时调度策略下运行的进程。

测试代码

sched_test.c

功能:设置进程的调度策略为SCHED_FIFO, 并且优先级设置为最大,创建的子进程也会继承父进程的策略和优先级。

F S UID          PID    SPID    PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 R root       19435   19435   19434 99 -40   - -   693 -      22:07 pts/5    00:00:07 ./a.out
1 R root       19436   19436   19435 99 -40   - -   693 -      22:07 pts/5    00:00:07 ./a.out
1 R root       19437   19437   19435 99 -40   - -   693 -      22:07 pts/5    00:00:07 ./a.out
1 R root       19438   19438   19436 99 -40   - -   693 -      22:07 pts/5    00:00:07 ./a.out

grace@LAPTOP-UNTITVOP:~/thread-policy$ chrt -p 19435
pid 19435's current scheduling policy: SCHED_FIFO
pid 19435's current scheduling priority: 99
grace@LAPTOP-UNTITVOP:~/thread-policy$ chrt -p 19436
pid 19436's current scheduling policy: SCHED_FIFO
pid 19436's current scheduling priority: 99
grace@LAPTOP-UNTITVOP:~/thread-policy$ chrt -p 19437
pid 19437's current scheduling policy: SCHED_FIFO
pid 19437's current scheduling priority: 99
grace@LAPTOP-UNTITVOP:~/thread-policy$ chrt -p 19438
pid 19438's current scheduling policy: SCHED_FIFO
pid 19438's current scheduling priority: 99

如果把#if 1改为#if 0, 则默认创建的是SCHED_OTHER类型进程

F S UID          PID    SPID    PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
0 R grace      21187   17702 99  80   0 -   694 -      22:13 pts/0    00:00:02 ./a.out
1 R grace      21189   21187 99  80   0 -   694 -      22:13 pts/0    00:00:02 ./a.out
1 R grace      21190   21187 99  80   0 -   694 -      22:13 pts/0    00:00:02 ./a.out
1 R grace      21191   21189 99  80   0 -   694 -      22:13 pts/0    00:00:02 ./a.out

grace@LAPTOP-UNTITVOP:~/thread-policy$ chrt -p 17702
pid 17702's current scheduling policy: SCHED_OTHER
pid 17702's current scheduling priority: 0
grace@LAPTOP-UNTITVOP:~/thread-policy$ chrt -p 21187
pid 21187's current scheduling policy: SCHED_OTHER
pid 21187's current scheduling priority: 0
grace@LAPTOP-UNTITVOP:~/thread-policy$ chrt -p 21189
pid 21189's current scheduling policy: SCHED_OTHER
pid 21189's current scheduling priority: 0
grace@LAPTOP-UNTITVOP:~/thread-policy$ chrt -p 21190
pid 21190's current scheduling policy: SCHED_OTHER
pid 21190's current scheduling priority: 0
grace@LAPTOP-UNTITVOP:~/thread-policy$ chrt -p 21191
pid 21191's current scheduling policy: SCHED_OTHER
pid 21191's current scheduling priority: 0