linux优先级概述html
在用户空间经过nice命令设置进程的静态优先级, 这在内部会调用nice系统调用, 进程的nice值在-20~+19之间. 值越低优先级越高.
setpriority系统调用也能够用来设置进程的优先级. 它不只可以修改单个线程的优先级, 还能修改进程组中全部进程的优先级, 或者经过制定UID来修改特定用户的全部进程的优先级linux
内核使用一些简单的数值范围0~139表示内部优先级, 数值越低, 优先级越高。算法
从0~99的范围专供实时进程使用, nice的值[-20,19]则映射到范围100~139c#
linux2.6内核将任务优先级进行了一个划分, 实时优先级范围是0到MAX_RT_PRIO-1(即99),而普通进程的静态优先级范围是从MAX_RT_PRIO到MAX_PRIO-1(即100到139).electron
优先级范围 | 描述 |
---|---|
0——99 | 实时进程 |
100——139 | 非实时进程 |
内核的优先级表示函数
内核表示优先级的全部信息基本都放在include/linux/sched/prio.h中, 其中定义了一些表示优先级的宏和函数.优化
优先级数值经过宏来定义, 以下所示,ui
其中MAX_NICE和MIN_NICE定义了nice的最大最小值.net
而MAX_RT_PRIO指定了实时进程的最大优先级,而MAX_PRIO则是普通进程的最大优先级数值线程
/* http://lxr.free-electrons.com/source/include/linux/sched/prio.h?v=4.6#L4 */ #define MAX_NICE 19 #define MIN_NICE -20 #define NICE_WIDTH (MAX_NICE - MIN_NICE + 1) /* http://lxr.free-electrons.com/source/include/linux/sched/prio.h?v=4.6#L24 */ #define MAX_PRIO (MAX_RT_PRIO + 40) #define DEFAULT_PRIO (MAX_RT_PRIO + 20)
宏 | 值 | 描述 |
---|---|---|
MIN_NICE | -20 | 对应于优先级100, 可使用NICE_TO_PRIO和PRIO_TO_NICE转换 |
NICE_WIDTH | 40 | nice值得范围宽度, 即[-20, 19]共40个数字的宽度 |
MAX_RT_PRIO, MAX_USER_RT_PRIO | 10 0 |
实时进程的最大优先级 |
MAX_PRIO | 14 0 |
普通进程的最大优先级 |
DEFAULT_PRIO | 12 0 |
进程的默认优先级, 对应于nice=0 |
MAX_DL_PRIO | 0 | 使用EDF最先截止时间优先调度算法的实时进程最大的优先级 |
而内核提供了一组宏将优先级在各类不一样的表示形之间转移
// http://lxr.free-electrons.com/source/include/linux/sched/prio.h?v=4.6#L27 /* * Convert user-nice values [ -20 ... 0 ... 19 ] * to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ], * and back. */ #define NICE_TO_PRIO(nice) ((nice) + DEFAULT_PRIO) #define PRIO_TO_NICE(prio) ((prio) - DEFAULT_PRIO) /* * 'User priority' is the nice value converted to something we * can work with better when scaling various scheduler parameters, * it's a [ 0 ... 39 ] range. */ #define USER_PRIO(p) ((p)-MAX_RT_PRIO) #define TASK_USER_PRIO(p) USER_PRIO((p)->static_prio) #define MAX_USER_PRIO (USER_PRIO(MAX_PRIO))
还有一些nice值和rlimit值之间相互转换的函数nice_to_rlimit和rlimit_to_nice, 这在nice系统调用进行检查的时候颇有用, 他们定义在include/linux/sched/prio.h, L47中, 以下所示
/* * Convert nice value [19,-20] to rlimit style value [1,40]. */ static inline long nice_to_rlimit(long nice) { return (MAX_NICE - nice + 1); } /* * Convert rlimit style value [1,40] to nice value [-20, 19]. */ static inline long rlimit_to_nice(long prio) { return (MAX_NICE - prio + 1); }
DEF最先截至时间优先实时调度算法的优先级描述
此外新版本的内核还引入了EDF实时调度算法, 它的优先级比RT进程和NORMAL/BATCH进程的优先级都要高, 关于EDF的优先级的设置信息都早内核头文件include/linux/sched/deadline.h
所以内核将MAX_DL_PRIO设置为0, 能够参见内核文件include/linux/sched/deadline.h
#define MAX_DL_PRIO 0
此外也提供了一些EDF优先级处理所需的函数, 以下所示, 能够参见内核文件include/linux/sched/deadline.h
static inline int dl_prio(int prio) { if (unlikely(prio < MAX_DL_PRIO)) return 1; return 0; } static inline int dl_task(struct task_struct *p) { return dl_prio(p->prio); } static inline bool dl_time_before(u64 a, u64 b) { return (s64)(a - b) < 0; }
struct task_struct { /* 进程优先级 * prio: 动态优先级,范围为100~139,与静态优先级和补偿(bonus)有关 * static_prio: 静态优先级,static_prio = 100 + nice + 20 (nice值为-20~19,因此static_prio值为100~139) * normal_prio: 没有受优先级继承影响的常规优先级,具体见normal_prio函数,跟属于什么类型的进程有关 */ int prio, static_prio, normal_prio; /* 实时进程优先级 */ unsigned int rt_priority; }
动态优先级 静态优先级 实时优先级
其中task_struct采用了三个成员表示进程的优先级:prio和normal_prio表示动态优先级, static_prio表示进程的静态优先级.
为何表示动态优先级须要两个值prio和normal_prio
调度器会考虑的优先级则保存在prio. 因为在某些状况下内核须要暂时提升进程的优先级, 所以须要用prio表示. 因为这些改变不是持久的, 所以静态优先级static_prio和普通优先级normal_prio不受影响.
此外还用了一个字段rt_priority保存了实时进程的优先级
字段 | 描述 |
---|---|
static_prio | 用于保存静态优先级, 是进程启动时分配的优先级, ,能够经过nice和sched_setscheduler系统调用来进行修改, 不然在进程运行期间会一直保持恒定 |
rt_priority | 用于保存实时优先级 |
normal_prio | 表示基于进程的静态优先级static_prio和调度策略计算出的优先级. 所以即便普通进程和实时进程具备相同的静态优先级, 其普通优先级也是不一样的, 进程分叉(fork)时, 子进程会继承父进程的普通优先级 |
prio | 保存进程的动态优先级 |
实时进程的优先级用实时优先级rt_priority来表示
前面说了task_struct中的几个优先级的字段
静态优先级 | 实时优先级 | 普通优先级 | 动态优先级 |
---|---|---|---|
static_prio | rt_priority | normal_prio | prio |
可是这些优先级是如何关联的呢, 动态优先级prio又是如何计算的呢?
静态优先级static_prio(普通进程)和实时优先级rt_priority(实时进程)是计算的起点
所以他们也是进程建立的时候设定好的, 咱们经过nice修改的就是普通进程的静态优先级static_prio
首先经过静态优先级static_prio计算出普通优先级normal_prio, 该工做能够由nromal_prio来完成, 该函数定义在kernel/sched/core.c#L861
/* * __normal_prio - return the priority that is based on the static prio * 普通进程(非实时进程)的普通优先级normal_prio就是静态优先级static_prio */ static inline int __normal_prio(struct task_struct *p) { return p->static_prio; } /* * Calculate the expected normal priority: i.e. priority * without taking RT-inheritance into account. Might be * boosted by interactivity modifiers. Changes upon fork, * setprio syscalls, and whenever the interactivity * estimator recalculates. */ static inline int normal_prio(struct task_struct *p) { int prio; if (task_has_dl_policy(p)) /* EDF调度的实时进程 */ prio = MAX_DL_PRIO-1; else if (task_has_rt_policy(p)) /* 普通实时进程的优先级 */ prio = MAX_RT_PRIO-1 - p->rt_priority; else /* 普通进程的优先级 */ prio = __normal_prio(p); return prio; }
进程类型 | 调度器 | 普通优先级normal_prio |
---|---|---|
EDF实时进程 | EDF | MAX_DL_PRIO-1 = -1 |
普通实时进程 | RT | MAX_RT_PRIO-1 - p->rt_priority = 99 - rt_priority |
普通进程 | CFS | __normal_prio(p) = static_prio |
普通优先级normal_prio须要根据普通进程和实时进程进行不一样的计算, 其中__normal_prio适用于普通进程, 直接将普通优先级normal_prio设置为静态优先级static_prio. 而实时进程的普通优先级计算依据其实时优先级rt_priority.
定义在kernel/sched/sched.h#L117 中
其本质其实就是传入task->policy调度策略字段看其值等于SCHED_NORMAL, SCHED_BATCH, SCHED_IDLE, SCHED_FIFO, SCHED_RR, SCHED_DEADLINE中的哪一个, 从而肯定其所属的调度类, 进一步就肯定了其进程类型
static inline int idle_policy(int policy) { return policy == SCHED_IDLE; } static inline int fair_policy(int policy) { return policy == SCHED_NORMAL || policy == SCHED_BATCH; } static inline int rt_policy(int policy) { return policy == SCHED_FIFO || policy == SCHED_RR; } static inline int dl_policy(int policy) { return policy == SCHED_DEADLINE; } static inline bool valid_policy(int policy) { return idle_policy(policy) || fair_policy(policy) || rt_policy(policy) || dl_policy(policy); } static inline int task_has_rt_policy(struct task_struct *p) { return rt_policy(p->policy); } static inline int task_has_dl_policy(struct task_struct *p) { return dl_policy(p->policy); }
咱们前面提到了数值越小, 优先级越高, 可是此处咱们会发现rt_priority的值越大, 其普通优先级越小, 从而优先级越高.
所以网上出现了一种说法, 优先级越高?这又是怎么回事?难道有一种说法错了吗?
实际的缘由是这样的,对于一个实时进程,他有两个参数来代表优先级——prio 和 rt_priority,
prio才是调度所用的最终优先级数值,这个值越小,优先级越高;
而rt_priority 被称做实时进程优先级,他要通过转化——prio=MAX_RT_PRIO - 1- p->rt_priority;
MAX_RT_PRIO = 100, ;这样意味着rt_priority值越大,优先级越高;
而内核提供的修改优先级的函数,是修改rt_priority的值,因此越大,优先级越高。
因此用户在使用实时进程或线程,在修改优先级时,就会有“优先级值越大,优先级越高的说法”,也是对的。
能够经过函数effective_prio用静态优先级static_prio计算动态优先级prio, 即·
p->prio = effective_prio(p);
该函数定义在kernel/sched/core.c, line 861
/* * Calculate the current priority, i.e. the priority * taken into account by the scheduler. This value might * be boosted by RT tasks, or might be boosted by * interactivity modifiers. Will be RT if the task got * RT-boosted. If not then it returns p->normal_prio. */ static int effective_prio(struct task_struct *p) { p->normal_prio = normal_prio(p); /* * If we are RT tasks or we were boosted to RT priority, * keep the priority unchanged. Otherwise, update priority * to the normal priority: */ if (!rt_prio(p->prio)) return p->normal_prio; return p->prio; }
咱们会发现函数首先effective_prio设置了普通优先级, 显然咱们用effective_prio同时设置了两个优先级(普通优先级normal_prio和动态优先级prio)
所以计算动态优先级的流程以下
最后, 咱们综述一下在针对不一样类型进程的计算结果
进程类型 | 实时优先级rt_priority | 静态优先级static_prio | 普通优先级normal_prio | 动态优先级prio |
---|---|---|---|---|
EDF调度的实时进程 | rt_priority | 不使用 | MAX_DL_PRIO-1 | 维持原prio不变 |
RT算法调度的实时进程 | rt_priority | 不使用 | MAX_RT_PRIO-1-rt_priority | 维持原prio不变 |
普通进程 | 不使用 | static_prio | static_prio | static_prio |
t_prio会检测普通优先级是否在实时范围内, 便是否小于MAX_RT_PRIO.参见include/linux/sched/rt.h#L6
static inline int rt_prio(int prio) { if (unlikely(prio < MAX_RT_PRIO)) return 1; return 0; }
而前面咱们在normal_prio的时候, 则经过task_has_rt_policy来判断其policy属性来肯定
policy == SCHED_FIFO || policy == SCHED_RR;
那么为何effective_prio重检测实时进程是rt_prio基于优先级数值, 而非task_has_rt_policy或者rt_policy?
对于临时提升至实时优先级的非实时进程来讲, 这个是必要的, 这种状况可能发生在是哦那个实时互斥量(RT-Mutex)时.
wake_up_new_task(), 计算此进程的优先级和其余调度参数,将新的进程加入到进程调度队列并设此进程为可被调度的,之后这个进程能够被进程调度模块调度执行。
nice系统调用是的内核实现是sys_nice, 其定义在kernel/sched/core.c#L7498,
它在经过一系列检测后, 经过set_user_nice函数, 其定义在kernel/sched/core.c#L3497
关于其具体实现咱们会在另一篇博客里面详细讲
在进程分叉处子进程时, 子进程的静态优先级继承自父进程. 子进程的动态优先级p->prio则被设置为父进程的普通优先级, 这确保了实时互斥量引发的优先级提升不会传递到子进程.
能够参照sched_fork函数, 在进程复制的过程当中copy_process经过调用sched_fork来设置子进程优先级, 参见sched_fork函数
/* * fork()/clone()-time setup: */ int sched_fork(unsigned long clone_flags, struct task_struct *p) { /* ...... */ /* * Make sure we do not leak PI boosting priority to the child. * 子进程的动态优先级被设置为父进程普通优先级 */ p->prio = current->normal_prio; /* * Revert to default priority/policy on fork if requested. * sched_reset_on_fork标识用于判断是否恢复默认的优先级或调度策略 */ if (unlikely(p->sched_reset_on_fork)) /* 若是要恢复默认的调度策略, 即SCHED_NORMAL */ { /* 首先是设置静态优先级static_prio * 因为要恢复默认的调度策略 * 对于父进程是实时进程的状况, 静态优先级就设置为DEFAULT_PRIO * * 对于父进程是非实时进程的状况, 要保证子进程优先级不小于DEFAULT_PRIO * 父进程nice < 0即static_prio < 的从新设置为DEFAULT_PRIO的从新设置为DEFAULT_PRIO * 父进程nice > 0的时候, 则什么也没作 * */ if (task_has_dl_policy(p) || task_has_rt_policy(p)) { p->policy = SCHED_NORMAL; /* 普通进程调度策略 */ p->static_prio = NICE_TO_PRIO(0); /* 静态优先级为nice = 0 即DEFAULT_PRIO*/ p->rt_priority = 0; /* 实时优先级为0 */ } else if (PRIO_TO_NICE(p->static_prio) < 0) /* */ p->static_prio = NICE_TO_PRIO(0); /* */ /* 接着就经过__normal_prio设置其普通优先级和动态优先级 * 这里作了一个优化, 由于用sched_reset_on_fork标识设置恢复默认调度策略后 * 建立的子进程是是SCHED_NORMAL的非实时进程 * 所以就不须要绕一大圈用effective_prio设置normal_prio和prio了 * 直接用__normal_prio设置就可 */ p->prio = p->normal_prio = __normal_prio(p); /* 设置*/ /* 设置负荷权重 */ set_load_weight(p); /* * We don't need the reset flag anymore after the fork. It has * fulfilled its duty: */ p->sched_reset_on_fork = 0; } /* ...... */ }
task_struct采用了四个成员表示进程的优先级:prio和normal_prio表示动态优先级, static_prio表示进程的静态优先级. 同时还用了rt_priority表示实时进程的优先级
字段 | 描述 |
---|---|
static_prio | 用于保存静态优先级, 是进程启动时分配的优先级, ,能够经过nice和sched_setscheduler系统调用来进行修改, 不然在进程运行期间会一直保持恒定 |
prio | 进程的动态优先级, 这个有显示才是调度器重点考虑的进程优先级 |
normal_prio | 普通进程的静态优先级static_prio和调度策略计算出的优先级. 所以即便普通进程和实时进程具备相同的静态优先级, 其普通优先级也是不一样的, 进程分叉(fork)时, 子进程会继承父进程的普通优先级, 能够经过normal_prio来计算(非实时进程用static_prIo计算, 实时进程用rt_priority计算) |
rt_priority | 实时进程的静态优先级 |
调度器会考虑的优先级则保存在prio. 因为在某些状况下内核须要暂时提升进程的优先级, 所以须要用prio表示. 因为这些改变不是持久的, 所以静态优先级static_prio和普通优先级normal_prio不受影响.
此外还用了一个字段rt_priority保存了实时进程的优先级静态优先级static_prio(普通进程)和实时优先级rt_priority(实时进程)是计算的起点, 经过他们计算进程的普通优先级normal_prio和动态优先级prio.
内核经过normal_prIo函数计算普通优先级normal_prio
经过effective_prio函数计算动态优先级prio
参考