自Linux 2.6.23开始,默认的调度器就是CFS调度器,即完全公平调度器,其期待了早起的“O(1)”调度器。
Linux提供了以下系统调用来控制CPU调度行为、调度策略、进程(线程)优先级。
nice函数:为调用线程设置新的nice值,并返回新的nice值
getpriority函数:返回一个线程、进程组或由特定用户拥有的线程集的nice值
setpriority函数:设置一个线程、进程组或由特定用户拥有的线程集的nice值
sched_setscheduler函数:设置特定线程的调度策略和调度参数
sched_getscheduler函数:返回特定线程的调度策略
sched_setparam函数:设置特定线程的调度参数
sched_getparam函数:获取特定线程的调度参数
sched_get_priority_max函数:返回特定调度策略的最大可用优先级,即优先级上限
sched_get_priority_min函数:返回特定调度策略的最小可用优先级,即优先级下限
sched_rr_get_interval函数:获取采用时间片轮转调度策略的线程所使用的量
sched_yield函数:调用者主动放弃CPU,以便其他线程可被执行
sched_setaffinity函数:设置特定线程的CPU亲和性(Linux独有)
sched_getaffinity函数:获取特定线程的CPU亲和性(Linux独有)
sched_setattr函数:设置特定线程的调度策略和调度参数。这个(特定于linux的)系统调用提供了sched_setscheduler(2)和sched_setparam(2)功能的超集。
sched_getattr函数:获取特定线程的调度策略和调度参数。这个(特定于linux的)系统调用提供了sched_setscheduler(2)和sched_setparam(2)功能的超集。
调度策略:
调度器是内核的组件,其决定哪个就绪线程将上CPU运行。每个线程都有一个与之关联的调度策略和静态调度优先级sched_priority。调度器会基于系统中所有线程的调度策略及静态优先级来做决策。
对于采用普通调度策略(SCHED_OTHER、SCHED_IDLE、SCHED_BATCH)的线程,其静态优先级sched_priority必须是0且不会被调度器用于做决策。
对于采用实时调度策略(SCHED_FIFO、SCHED_RR)的线程,其具有范围在1~99的静态优先级sched_priority,值越大优先级越高。因此,实时调度线程总是比普通线程拥有更高的优先级。注意:对于实时调度策略,POSIX.1要求系统必须支持至少32个不同的优先级等级,而一些系统就将这个最小值作为静态优先级的范围。因此,可移植程序应使用sched_get_priority_max函数和sched_get_priority_min函数来获取特定调度策略所支持的优先级范围。
从概念上来说,调度器会为每个可能的静态优先级sched_priority值维护一张就绪线程的列表。为了决定下一个要运行的线程,调度器会查找具有最高优先级的非空列表,然后在这个列表的头部选择一个线程来运行。
一个线程的调度策略决定了它会被插入到具有相同静态优先级的线程列表的哪个位置。
所有的调度都是可抢占的:如果具有更高优先级的线程处于就绪状态,当前正在运行的线程将被抢占并被放回其静态优先级对应的等待列表中。
SCHED_FIFO:先入先出调度策略
该策略仅能用于静态优先级大于0的线程,这也意味着一旦SCHED_FIFO线程就绪,它就会立即抢占任何正在运行的普通线程(SCHED_OTHER、SCHED_IDLE、SCHED_BATCH)。
SCHED_FIFO是一种简单的、不支持时间片的调度算法。采用这种调度策略的线程,会遵循以下规则:
- 当一个正在运行的SCHED_FIFO线程被另一个具有更到优先级的线程抢占了,该线程将被放回对应静态优先级列表的头部,一旦所有的更高优先级的线程再次被阻塞,就会立即恢复该线程的运行。
- 当被阻塞的SCHED_FIFO线程就绪时,该线程将被插入对应静态优先级列表的末尾。
- 如果调用了sched_setscheduler函数、sched_setparam函数、sched_setattr函数、pthread_setschedparam函数或pthread_setschedprio函数来改变由pid标识的正在运行或就绪的SCHED_FIFO线程的优先级,则对线程在列表中的位置的影响取决于线程优先级改变的方向:
- 如果提高线程优先级,就将该线程放入新的静态优先级对应的列表的末尾。因此,它可能会抢占具有相同优先级的当前正在运行的线程。
- 如果未改变线程优先级,该线程在列表中的位置也不会变。
- 如果降低线程优先级,就将该线程放入新的静态优先级对应的列表的头部。
根据POSIX.1-2008,使用除了pthread_setschedprio函数以外的任何机制来改变线程的优先级或调度策略,都应该导致线程被放入新优先级列表的末尾。
4. 调用了sched_yield函数的线程将被放到列表末尾。
除以上4种事件外,没有其他事件会将采用SCHED_FIFO调度策略的线程移动到对应静态优先级的就绪线程等待列表中。
一个SCHED_FIFO线程会一直运行直至被I/O请求阻塞、被更高优先级线程抢占或主动调用sched_yield函数。
SCHED_RR:轮询调度
SCHED_RR是对SCHED_FIFO策略的简单强化。前面对SCHED_FIFO描述的每一件事情都适用于SCHED_RR,唯一不同的是每个线程仅被允许运行一个最大时间量。如果SCHED_RR线程已经运行了等于或大于时间量的时间周期,该线程将被放入对应优先级的列表末尾。SCHED_RR线程被另一个高优先级线程抢占后并且随后又恢复运行后,它将完成其轮询时间量中未过期的部分。时间量的长度可由sched_rr_get_interval函数获取。
SCHED_DEADLINE:Sporadic task model deadline scheduling,零星任务模型最后期限调度
自3.14版本开始,Linux提供了一个最后期限调度策略。该策略当前由GEDF(Global Earliest Deadline First)和CBS(Constant Bandwidth Server)共同实现。为了设置和获取该策略和相应的属性,必须使用Linux独有的sched_setattr函数和sched_getattr函数。
一个零星任务就是其有一个工作序列,序列中每一个工作在每个周期最多被激活一次。
SCHED_OTHER:默认的Linux分时调度策略
SCHED_OTHER的线程静态优先级只能是0。SCHED_OTHER是标准的Linux分时调度,用于对实时性没有要求的线程。
调度器会从静态优先级为0的列表中根据动态优先级来选择要运行的线程,该动态优先级只能在这个列表中确定。动态优先级基于nice值得到,并且会在线程准备运行但被调度器拒绝时的每个时间量中增加。这确保了所有SCHED_OTHER线程的公平处理。
在Linux内核源码中,SCHED_OTHER策略实际上叫做SCHED_NORMAL策略。
nice值:
nice值是一种属性,可用于影响CPU调度器来赞成或不赞成调度决策的过程。nice值会影响SCHED_OTHER和SCHED_BATCH进程的调度。nice值可通过nice函数、setpriority函数或sched_setattr函数修改。
根据POSIX.1,nice值是每一个进程的属性,进程中的线程应该共享同一个nice值。但是,在Linux中,nice值是每一个线程的属性:同一个进程中不同的线程可拥有不同的nice值。
nice值的范围不同于UNIX系统。在现代Linux系统中,nice值的范围是-20(高优先级)至+19(低优先级)。
在Linux内核中,nice值范围实际上是40~1,但glibc做了一层转换,即用户层nice值 = 20 - 内核nice值。
UNIX系统和Linux系统中,nice值影响SCHED_OTHER进程的相关调度的维度也是不同的。
随着Linux内核2.6.23的CFS调度器的出现,Linux采用了一种算法,使nice值的相对差异产生更强的影响。在当前的实现中,两个进程的nice值每相差一个单位,调度器对高优先级进程的偏爱程度就会增加1.25倍。这导致非常低的nice值(+19)在系统上有任何其他高优先级负载时真正为进程提供很少的CPU,并使高nice值(-20)将大部分CPU交付给需要它的应用程序。
在Linux中,RLIMIT_NICE资源限制可用于定义非特权进程的nice值能被提升的上限。setrlimit函数有更多的细节。
SCHED_BATCH:间歇过程调度
自Linux2.6.16开始,SCHED_BATCH仅在静态优先级0下使用。该调度策略与SCHED_OTHER策略相似,都会根据基于nice值的动态优先级来调度线程。不同之处在于,该调度策略会导致调度器总是认为线程是CPU密集型的。因此,调度器将对唤醒行为施加较小的调度惩罚,因此该线程在调度决策中不太受欢迎。
该策略对于非交互但不希望降低其nice值的工作负载,以及希望使用确定性调度策略而不需要交互导致额外抢占的工作负责非常有用。
SCHED_IDLE:调度非常低优先级的工作
自Linux2.6.23开始,SCHED_IDLE仅能在静态优先级0下使用,进程的nice值对该策略无影响。
该策略用于以极低的优先级运行作业(甚至低于SCHED_OTHER或SCHED_BATCH策略的+19nice值)。
函数原型
nice函数
调整调用线程的nice值,并返回新的nice值。传统上只有特权进程才能降低nice值(即提高优先级)。但自Linux 2.6.12开始,非特权进程也可以降低nice值。
该函数将实参inc加到调用线程的nice值上。
#include <unistd.h>
int nice(int inc);
getpriority函数和setpriority函数
由which和who标识的进程、进程组或用户的调度优先级可通过这两个函数获取和设置,实际上是设置和获取nice值。传统上只有特权进程才能降低nice值(即提高优先级)。但自Linux 2.6.12开始,非特权进程也可以降低nice值。
getpriority函数返回一个线程、进程组或由特定用户拥有的线程集的nice值。
setpriority函数设置一个线程、进程组或由特定用户拥有的线程集的nice值。
which实参是以下三个值之一,而who实参则与which实参相关:
- PRIO_PROCESS:who实参就是进程标识符,who为0表示调用进程
- PRIO_PGRP:who实参就是进程组标识符,who为0表示调用进程所在的进程组
- PRIO_USER:who实参就是用户ID,who为0表示调用进程的真实用户ID
#include <sys/time.h>
#include <sys/resource.h>
int getpriority(int which, id_t who);
int setpriority(int which, id_t who, int prio);
sched_setscheduler函数和sched_getscheduler函数
sched_setscheduler函数:设置特定线程的调度策略和调度参数。
sched_getscheduler函数:返回特定线程的调度策略。
调度参数实际上只有一个,即调度优先级。若pid为0,则是操作调用线程的调度策略和调度参数。
Linux支持以下普通调度策略作为policy参数的值,但优先级必须都设置为0:
- SCHED_OTHER:标准的轮询分时策略
- SCHED_BATCH
- SCHED_IDLE:后台运行的优先级级非常低的任务
Linux支持以下实时调度策略作为policy参数的值,可指定优先级:
- SCHED_FIFO:先入先出策略
- SCHED_RR:轮询策略
#include <sched.h>
int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param);
int sched_getscheduler(pid_t pid);
sched_setparam函数和sched_getparam函数
sched_setparam函数:设置特定线程的调度参数,即优先级。
sched_getparam函数:获取特定线程的调度参数,即优先级。
pid参数为0,则表示操作调用线程。
#include <sched.h>
int sched_setparam(pid_t pid, const struct sched_param *param);
int sched_getparam(pit_t pid, struct sched_param *param);
sched_get_priority_max函数和sched_get_priority_min函数
sched_get_priority_max函数:返回特定调度策略的最大可用优先级,即优先级上限。
sched_get_priority_min函数:返回特定调度策略的最小可用优先级,即优先级下限。
支持的调度策略:
- SCHED_FIFO
- SCHED_RR
- SCHED_OTHER
- SCHED_BATCH
- SCHED_IDLE
- SCHED_DEADLINE
#include <sched.h>
int sched_get_priority_max(int policy);
int sched_get_priority_min(int policy);
sched_rr_get_interval函数
获取采用时间片轮转调度策略的线程所使用的量,仅用于采用SCHED_RR调度策略的线程。
若pid参数值为0,则获取调用线程的时间片的量。
#include <sched.h>
int sched_rr_get_interval(pid_t pid, struct timespec *tp);
struct timespec {
time_t tv_sec; /* 秒 */
long tv_nsec; /* 纳秒 */
};
sched_yield函数
调用线程主动放弃CPU,以便其他线程可被执行。该调用线程会移动到静态优先级对应的列表的末尾。
注意:
- 如果调用线程是最高优先级列表中唯一的线程,则调用该函数后仍是继续运行调用线程。
- 该函数专门用于实时调度策略(SCHED_FIFO和SCHED_RR),对分时调度策略如SCHED_OTHER使用是未指明的且很大概率表明你的应用程序设计被打破了。
#include <sched.h>
int sched_yield(void);
sched_setaffinity函数和sched_getaffinity函数
sched_setaffinity函数:设置特定线程的CPU亲和性(Linux独有)。
sched_getaffinity函数:获取特定线程的CPU亲和性(Linux独有)。
#define _GNU_SOURCE
#include <sched.h>
int sched_setaffinity(pid_t pid, size_t cpusetsize, const cpu_set_t *mask);
int sched_getaffinity(pid_t pid, size_t cpusetsize, cpu_set_t *mask);
sched_setattr函数和sched_getattr函数
sched_setattr函数:设置特定线程的调度策略和调度参数。这个(特定于linux的)系统调用提供了sched_setscheduler(2)和sched_setparam(2)功能的超集。
sched_getattr函数:获取特定线程的调度策略和调度参数。这个(特定于linux的)系统调用提供了sched_setscheduler(2)和sched_setparam(2)功能的超集。
pid参数为0,则操作调用线程。
flags参数用于未来的扩展,目前必须设置为0。
#include <sched.h>
int sched_setattr(pid_t pid, struct sched_attr *attr, unsigned int flags);
int sched_getattr(pid_t pid, struct sched_attr *attr, unsigned int size, unsigned int flags);
struct sched_attr {
u32 size; /* 结构体大小 */
u32 sched_policy; /* 调度策略 */
u64 sched_flags; /* 标志位 */
s32 sched_nice; /* nice值,用于SCHED_OTHER和SCHED_BATCH */
u32 sched_priority; /* 静态优先级,用于SCHED_FIFO和SCHED_RR */
/* 保留字段,用于SCHED_DEADLINE */
u64 sched_runtime;
u63 sched_deadline;
u64 sched_period;
};