当前位置: 华文星空 > 新闻

进程优先级

2022-03-31新闻

1.进程优先级:

内核使用[0~139]这140个数来表示140种优先级。

内核使用一个简单些的数值范围,从0到139(包含),用来表示内部优先级。同样是值越低,优

先级越高。从0到99的范围专供实时进程使用。 nice值[20, +19]映射到范围100到139,如图2-14所示。

实时进程的优先级总是比普通进程更高。

下面列出了task_struct结构体中与权限相关的几个成员:

a) static_prio,指普通进程的静态优先级(实时进程没用该参数),值越小优先级越高。静态优先级是进程启动时分配的优先级。它可以用nice()或者sched_setscheduler()系统调用更改,否则在运行期间一直保持恒定。

b) rt_priority,表示实时进程的优先级(普通进程没用该参数),它的值介于[0~99]之间(包括0和99)。注意:rt_priority是值越大优先级越高。

c) normal_prio是基于前两个参数static_prio或rt_priority计算出来的。可以这样理解:static_prio和rt_priority分别代表普通进程和实时进程「静态」的优先级,代表进程的固有属性。由于他们两的「单位」不同(一个是点头yes,摇头no;另一个是摇头yes,点头no),一个是值越小优先级越高,另一个是值越大优先级越高。有必要用normal_prio统一下"单位"。统一成值越小优先级越高,因此,normal_prio也可以理解为:统一了单位的「静态」优先级。

d) prio,叫做动态优先级,它表示进程的有效优先级,顾名思义,在系统中需要判断进程优先级时用的便是该参数,调度器考虑的优先级也就是它。对于实时进程来说,有效优先级prio就等于它的normal_prio(「统一单位」后的优先级)。有效优先级对普通进程来说尤为重要,进程可以临时提高优先级,通过改变prio的值实现,所以优先级的提高不影响进程的静态优先级。顺带说明一下,子进程的有效优先级prio初始划为父进程的静态优先级,而不是父进程的有效优先级(也就是说,父进程的优先级如果临时提高了,该特性不会遗传给子进程)。

e) policy, 调度策略,共有五种可能值:SCHED_NORMAL,SCHED_IDLE,SCHED_BATCH,SCHED_FIFO,SCHED_RR。普通进程的policy是前三种值之一,实时进程的policy是后两种值之一。

下列宏用于在各种不同表示形式之间转换(MAX_RT_PRIO指定实时进程的最大优先级,而MAX_PRIO则是普通进程的最大优先级数值):

#define MAX_USER_RT_PRIO 100 #define MAX_RT_PRIO MAX_USER_RT_PRIO #define MAX_PRIO (MAX_RT_PRIO + 40) #define DEFAULT_PRIO (MAX_RT_PRIO + 20) /* * Convert user-nice values [ -20 ... 0 ... 19 ] * to static priority [ MAX_RT_PRIO..MAX_PRIO-1 ], * and back. */ #define NICE_TO_PRIO(nice) (MAX_RT_PRIO + (nice) + 20) #define PRIO_TO_NICE(prio) ((prio) - MAX_RT_PRIO - 20) #define TASK_NICE(p) PRIO_TO_NICE((p)->static_prio)

2.进程优先级的计算

static_prio是计算的起点。假定它已经设置好,而内核现在想要计算其他进程p的动态优先级是用函数effective_prio(p)计算出来的:

p->prio= effective_prio(p);

看看 effective_prio函数的具体实现:该函数有两个作用:

1.设置了进程p的normal_prio。

2.返回了进程的有效优先级。

/* * 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 ) { undefined //计算普通优先级 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 ; } /* * 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 ) { undefined int prio ; if ( task_has_dl_policy ( p )) //SCHED_DEADLINE 新支持的实时进程调度策略 prio = MAX_DL_PRIO - 1 ; // MAX_DL_PRIO = -1

//判断进程的调度策略policy是不是SCHED_FIFO和SCHED_RR中的一种,如果是则它是实时进程,返回true,反之则返回false。

else if ( task_has_rt_policy ( p )) prio = MAX_RT_PRIO - 1 - p -> rt_priority ; else prio = __normal_prio ( p ); return prio ; }

普通优先级需要根据普通进程和实时进程进行不同的计算。 __normal_prio的计算只适用于普通进程。而实时进程的普通优先级计算,则需要根据其rt_priority设置。由于更高的rt_priority值表示更高的实时优先级,内核内部优先级的表示刚好相反,越低的值表示的优先级越高。因此,实时进程在内核内部的优先级数值,正确的算法是MAX_RT_PRIO - 1 - p->rt_priority。这一次请注意,与effective_prio相比,实时进程的检测不再基于优先级数值,而是通过task_struct中设置的调度策略来检测

MAX_RT_PRIO的值是100(也就是实时进程的优先级的最大数值加1),normal_prio()函数实际上就是了单位统一的过程。它的执行流程是这样的:如果p是实时进程,那么就返回99-rt_priority(rt_priority是值越大表示进程优先级越高,normal_priority反之,所以通过这个方式将rt_priority转换为normal_priority),如果进程p是普通进程,不需要统一"单位",那么直接返回它的静态优先级static_prio。

/* * __normal_prio - return the priority that is based on the static prio */ static inline int __normal_prio ( struct task_struct * p ) { undefined return p -> static_prio ; }

为什么内核在effective_prio中检测实时进程是基于优先级数值,而非task_has_rt_policy?对于临时提高至实时优先级的非实时进程来说,这是必要的,这种情况可能发生在

使用实时互斥量(RT-Mutex)时。

综上:a) 因此对于实时进程来说:prio=effective_prio()=normal_prio。normal_prio=MAX_RT_PRIO-1-rt_priority

b) 对于优先级没有提高的普通进程来说:prio=effective_prio()=normal_prio=static_prio

c) 对于优先级提高的普通进程来说:prio=effective_prio(),normal_prio=static_prio。prio的值被其他函数更改过,所以与初始时不同。

d) nice值

nice值也用来用来表示普通进程的优先等级,它介于[-20~19]之间,也是值越小优先级越高。之前讲过普通进程的优先值范围是[100~139],刚好和nice值一一对应起来:优先等级=nice值+120。nice值并不是表示进程优先级的一种新的机制,只是优先级的另一个表示而已。sys_nice()系统调用设置的是进程的静态优先级static_prio.

3.计算负荷权重

进程的重要性不仅是由优先级指定的,而且还需要考虑保存在task_struct->se.load的负荷权重。 set_load_weight负责根据进程类型及其静态优先级计算负荷权重。

在进程被调度的先后顺序中,讲到影响进程在就绪队列中的参数是进程的权重值weight。而weight是由进程的静态优先级static_prio决定的,静态优先级越高(static_prio值越小)weight值越大。静态优先级和weight是通过prio_to_weight数组对应起来的。静态优先级为100(nice值为-20)的进程,其weight值为prio_to_weight[0],静态优先级为k的(nice值为k-120)的进程,weight值为prio_to_weight[k-100]。

普通进程的默认nice值为0,即默认静态优先级为120,它的weight值为prio_to_weight[20],即1024。因此NICE_O_LOAD的值就是1024,NICE_0_LOAD的命名也就是这么来的。

很重要的规定:nice值为0的进程虚拟运行时间(vruntime)行走速度和真实运行时间(runtime)行走的速度相同。

权重计算的代码也需要考虑进程类型。实时进程的权重是普通进程的两倍。另一方面,SCHED_IDLE进程的权重总是非常小:

set_load_weight代码的实现:

static void set_load_weight ( struct task_struct * p ) { undefined int prio = p -> static_prio - MAX_RT_PRIO ; struct load_weight * load = & p -> se . load ; /* * SCHED_IDLE tasks get minimal weight: */ if ( p -> policy == SCHED_IDLE ) { undefined load -> weight = scale_load ( WEIGHT_IDLEPRIO ); load -> inv_weight = WMULT_IDLEPRIO ; return ; } //# define scale_load(w) (w) 内核不仅计算出权重本身,还存储了用于除法的值。 load -> weight = scale_load ( prio_to_weight [ prio ]); load -> inv_weight = prio_to_wmult [ prio ]; }

不仅进程,而且就绪队列也关联到一个负荷权重。每次进程被加到就绪队列时,内核会调用inc_nr_running。这不仅确保就绪队列能够跟踪记录有多少进程在运行,而且还将进程的权重添加到就绪队列的权重中:

static void enqueue_task_fair ( struct rq * rq , struct task_struct * p , int flags ) { .... inc_nr_running ( rq ); } static inline void inc_nr_running ( struct rq * rq ) { undefined rq -> nr_running ++ ; //队列上进程数统计 ..... } static inline void update_load_add ( struct load_weight * lw , unsigned long inc ) { undefined //inc 对应于调用函数入参 se->load.weight lw -> weight += inc ; lw -> inv_weight = 0 ; }

在 进 程 从 就 绪 队 列 移 除 时 , 会 调 用 对 应 的 函 数 (dec_nr_running、 dec_nr_running、 update_load_sub)。

更多Linux内核源码高阶知识请加开发交流Q群篇【318652197】获取,进群免费获取相关资料,免费观看公开课技术分享,入群不亏,快来加入我们吧~
前100名进群领取,额外赠送一份价值699的内核资料包(含视频教程、电子书、实战项目及代码)

资源免费领

学习直通车