进程优先级和调度
进程优先级(nice值)
进程优先级nice值会影响CPU的调度策略,每个进程都拥有一个nice值,其取值范围为-20~-19,默认值为0
在传统UNIX实现中,只有特权进程才能够赋给自己一个负的优先级,非特权级进程只能够降低自己的优先级,即赋予一个正值
这样做的话他们对于其他进程来说就很"nice"了
通过fork()会继承nice的值,并且该值会在exec()中得以保存
-
获取和修改nice值
#include<sys/resourece.h> int getpriority(int which, id_t who); //成功:返回对应nice值 失败:返回-1 int setpriority(int which, id_t who, int prio) //成功:返回0 失败:返货-1
参数解释:
which确定参数who如何被解释,有以下选择
-
PRIO_PROCESS
操作ID为who的进程,如果who为0,那么使用调用者的进程ID
-
PRIO_PGRP
操作进程组ID为who的进程组的所有成员,如果who为0,那么使用调用者的进程组
-
PRIO_USER
操作所有真实用户ID为who的进程,如果who为0,那么使用调用者的真实用户ID
getpriority()系统调用返回由which和who指定的nice值,如果有多个进程符合要求,那么将会返回优先级最高的nice
由于nice值可能为-1,所以需要加上errno来确定是否发生了错误
errno = 0; if(errno != 0 && getpriority(which, who) == -1) errExit("getpriority");
setpriority()系统调用会将which和who指定的进程的nice值设置为prio,如果超出之前所说的范围,会取边界值
-
CPU调度模型
SCHED_OTHER
Linux调用CPU使用的默认模型是SCHED_OTHER(循环时间共享),在该模型中,每个进程轮流使用CPU一段时间,这段时间被称为时间片在该模型中,进程的调度并不是严格按照nice值来进行调度的,nice值只是一个权重因素,它导致内核调度器倾向于调度拥有高优先级的进程。给一个进程赋予一个低优先级,不会导致它完全无法使用CPU,只会导致他使用到的CPU时间变少
SCHED_RR
该策略与接下来的SCHED_FIFO均为实时调度策略,使用这两种策略中的任意一种进行调度的优先级会高于SCHED_OTHER在实时调度策略中,拥有高优先级的可运行进程总是优先于低优先级的进程访问CPU,实际上,每个优先级级别都维护着一个可运行的进程队列,下一个运行的进程是从优先级最高的非空队列中选择出来的
在SCHED_RR中,优先级相同的进程以循环时间分享的方式执行,进程每次使用CPU的时间是一个固定长度的时间片,一旦被调度执行,使用SCHED_RR策略的进程会运行直到以下情形
- 时间片用完
- 自愿放弃CPU。可能是执行了阻塞系统调用或者sched_yield()(??不知道)
当遇到这两种情况时,原有的进程将会被放置到对应优先级别队列的队尾
- 终止
- 被更高优先级抢占
当更高优先级执行完毕后,原有进程会继续执行(即还在队列头)
SCHED_FIFO
该策略中没有时间片的概念,进程会一直执行直到以下情形
- 自动放弃CPU
- 终止
- 被高优先级抢占
与SCHED__RR基本类似
进程被抢占的原因:
- 之前被阻塞的高优先级进程解除阻塞
- 另一个进程的优先级被提高
- 当前进程优先级被降低
SCHED_BATCH和SCHED_IDLE
这两种策略均与SCHED_OTHER十分相似,并且不属于实时策略
SCHED_BATCH会导致频繁被唤醒的任务被调度的次数较少
SCHED_IDLE提供的功能等同于一个十分低的nice值,在该策略中,进程的nice值将毫无意义
实时进程调用API
-
实时优先级范围
#include<sched.h> int sched_get_priority_min(int priority); int sched_get_priority_max(int priority); //成功:返回对应优先级 失败:返回-1
policy指定了需要获取哪种调度策略的信息
-
修改调度策略和优先级
#include<sched.h> int sched_setscheduler(pid_t pid, int policy, const struct sched_param *param); //成功:返回0 失败:返回-1
param是一个指向如下类型的指针
struct sched_param{ int sched_priority; }
该系统调用可以将指定进程设置为指定策略以及指定优先级
对于SCHED_RR和SCHED_FIFO来说,优先级的取值范围必须在sched_get_priority_min()和sched_get_priority_max()之间,如果是其他策略,则其优先级必须是0
成功调用sched_setscheduler()会将pid指定的进程移到对应优先级队列的队尾
通过fork()创建的子进程会继承父进程的调度优先级以及调度策略,并且在exec()中得以保持
#include<sched.h> int sched_setparam(pid_t pid, const struct sched_param* param); //成功:返回0 失败:返回-1
sched_setparam()只会修改优先级,成功调用sched_setparm()会将指定进程移动到队列尾部
-
获取调度策略和优先级
#include<sched.h> int sched_getscheduler(pid_t pid); //成功:返回策略 失败:返回-1 int sched_getparam(pid_t pid, struct sched_param *param) //成功:返回0 失败:返回-1
防止实时进程锁住系统
由于高优先级实时进程会抢占所有低优先级进程,因此需要注意这些进程锁住CPU,具体有以下四种解决方案
-
使用setrlimit()设置一个合理的低软CPU时间组员限制(RLIMIT_CPU)。如果进程消耗了太多CPU,那么它将会收到一个SIGXCPU信号,该信号在默认情况下会杀死进程
-
使用alarm()设置一个警报定时器。如果进程的运行时间超出了由alarm()调用指定的秒数,那么该进程会被SIGALRM杀死
-
创建一个拥有高实时优先级的看门狗进程
这个进程可以进行无限循环,每次循环都睡眠指定的时间间隔,然后醒来并监控其他进程的状态
这种监控可以包含对每个进程消耗的CPU时间的度量并使用sched_getscheduler()和 sched_getparam()来检查进程的调度策略和优先级
如果一个进程看起来行为异常,那么看门狗线程可以降低该进程的优先级或者向其发送合适的信号来停止或者终止该进程
-
Linux提供了一个非标准的资源限制RLIMIT_RTTIME用于控制一个运行在实时调度策略下的进程在单次运行中能够消耗的CPU事件
当进程达到了CPU时间限制RLIMIT_RTTIME之后,系统会向其发送一个SIGXCPU信号,该信号默认情况下会杀死这个进程
SCHED_RESET_ON_FORK
在调用 sched_setscheduler()时可以将 policy 参数的值设置为该常量,如果设置了这个标志,那么由这个进程使用fork()创建的子进程就不会继承特权调度策略和优先级了。其规则如下:
- 如果调用进程拥有一个实时调度策略(SCHED_RR 或 SCHED_FIFO),那么子进程的策略会被重置为标准的循环时间分享策略 SCHED_OTHER
- 如果进程的 nice 值为负值(即高优先级),那么子进程的 nice 值会被重置为 0
释放CPU
实时进程可以通过两种方式自愿释放CPU:通过调用一个阻塞进程的系统调用(如从终端中read())或者调用sched_yield()
#include<sched.h>
int sched_yield(void);
//成功:返回0 失败:返回-1
SCHED_RR时间片
使用sched_rr_et_interval()获取SCHED_RR时间片
#include<sched.h>
int sched_rr_get_interval(pid_t pid, struct timespec *tp);
//成功: 返回0, 失败:返回-1
CPU亲和力
Linux中CPU亲和性(affinity) - 知乎 (zhihu.com)