在内核中,肯定不能对所有的进程一视同仁,有的进程需要优先运行,有的进程需要运行更长的时间
为了更好地实现进程调度,每个进程都有自己的优先级和调度策略
所谓优先级,就是表示这个进程的重要性,优先级高的自然会被更好的对待
那调度策略又是什么呢?想一想,进程调度其实是一个非常复杂的问题,想使用一种算法来实现良好的进程调度是不可能的,Linux内核实现了好几种调度算法。所谓调度策略,你可以理解为使用哪种算法来管理进程
每个进程都使用 task_struct 结构来表示,在这个结构体中,关于调度策略的定义如下:
unsigned int policy;
policy 表示该进程采用哪种调度策略,内核提供了以下几种调度策略policy 表示该进程采用哪种调度策略,内核提供了以下几种调度策略:
#define SCHED_NORMAL 0
#define SCHED_FIFO 1
#define SCHED_RR 2
#define SCHED_BATCH 3
#define SCHED_IDLE 5
#define SCHED_DEADLINE 6
Linux内核的进程大概可分为两类,一类是普通进程,一类是实时进程
其中属于实时进程的调度策略是:
属于普通进程的调度策略是:
下面我来跟你详细讲解每个调度策略代表什么:
SCHED_DEADLINE:这是实时进程的调度策略,它是按照任务的deadline来调度的,当产生一个调度点的时候,总会选取距离deadline最近的进程来运行
SCHED_FIFO:这是实时进程的一种调度策略,FIFO表示先进先出机制,在使用该调度策略的进程被选中运行后,它可以运行任意长时间,直到更高优先级的进程抢占或者自己让出CPU
SCHED_RR:这也是实时进程的调度策略,RR是时间片轮转调度,每个使用该调度策略的进程都有自己的时间片,进程运行直到时间片耗尽,再将其添加到运行队列尾部,如此循环
SCHED_NORMAL:表示普通进程的调度策略,内核大多数进程都属于普通进程,普通进程使用完全公平调度算法实现调度
SCHED_BATCH:是用于非交互,CPU使用密集的批处理进程,它和普通进程都是使用完全公平调度算法来实现。内核中在某时刻可以去唤醒某个进程,如果这个进程的调度策略是SCHED_BATCH,那它就不会去抢占当前正在运行的进程
SCHED_IDLE:是用于特别空闲的进程使用的调度策略
讲完调度策略,我们来将优先级
task_struct 中关于优先级的定义如下:
int prio, static_prio, normal_prio;
unsigned int rt_priority;
是的,内核使用了四个变量来表示优先级,这四个变量之间的关系相当复杂,不过没关系,我会尽量地解释清楚:
prio:动态优先级,进程调度中判断一个进程的优先级都是使用此变量
normal_prio:这个变量也表示动态优先级,它表示正常的优先级。最初的时候 prio 是等于 normal_prio 的,只不过有的时候进程的优先级需要临时改变,所以会改变prio,但是 normal_prio 是不会变的。在创建子进程的时候,子进程继承的优先级是normal_prio,而不是prio
static_prio:表示进程优先级,进程启动的时候赋值的,内核不会去改变它,只能用户通过nice和sched_setscheduler 系统调用来设置
rt_priority:只有实时进程才会用到的优先级,其值范围是0~99,最低优先级是0,最高优先级是99
这四个变量有什么联系呢?
prio 和 normal_prio 最初的值是相等的,它们都是基于 static_prio 或者 rt_priority 计算的(至于基于 staticc_prio 还是 rt_priority,取决于调度策略)
下面来看一看内核的代码
内核中将0139的优先级划分为两个范围,099表示实时进程优先级,100~139的优先级表示普通进程的优先级,数值越小表示优先级越高:
首先我们讲static_prio,进程启动的时候会设置好静态优先级。如果需要修改,可以通过nice系统调用来设置,nice的范围是-2019,最终映射到优先级为100139的部分,如下所示:
内核中定义如下:
/* nice的范围 */
#define MAX_NICE 19
#define MIN_NICE -20
#define NICE_WIDTH (MAX_NICE - MIN_NICE + 1) //20
#define DEFAULT_PRIO (MAX_RT_PRIO + NICE_WIDTH / 2) //120
#define NICE_TO_PRIO(nice) ((nice) + DEFAULT_PRIO) //100~139
void set_user_nice(struct task_struct *p, long nice)
{
...
p->static_prio = NICE_TO_PRIO(nice);
...
}
而 rt_priority 动态优先级又是怎么指定的呢?
用户层可以通过 sched_setscheduler,将普通进程更改为实时进程,通过更改进程的调度策略,同时设置 rt_priority,也就是说它的值可以是应用程序指定的,范围是0~99
在清楚 static_prio 和 rt_priority 怎么得来之后,我们来看看 normal_prio 和 prio 这两个变量是怎么计算的
内核中通过下面的代码来设置:
p->prio = effective_prio(p);
看一下 effective_prio 的定义:
static int effective_prio(struct task_struct *p)
{
p->normal_prio = normal_prio(p);
if (!rt_prio(p->prio))
return p->normal_prio;
/* 如果进程的优先级本来是实时优先级或者进程被提高到实时进程,那么就保持不变 */
return p->prio;
}
可以看到,通过这条指令 p->prio = effective_prio§,会同时设置 prio 和 normal_prio,下面来看看 normal_prio 函数的定义,这个函数也是解开这几个变量之间关系的关键:
#define MAX_DL_PRIO 0
static inline int normal_prio(struct task_struct *p)
{
int prio;
if (task_has_dl_policy(p)) //deadline调度策略
prio = MAX_DL_PRIO-1; //-1
else if (task_has_rt_policy(p)) //FIFO或者RR的调度策略
prio = MAX_RT_PRIO-1 - p->rt_priority; //99-rt_priority
else //普通进程调度策略(NORMAL、BATCH、IDLE)
prio = __normal_prio(p);
return prio;
}
normal_prio 根据进程不同的调度策略,使用不同的方法来设置进程的优先级:
如果进程采用 SCHED_DEADLINE 调度策略,那么优先级就等于-1,这可不在正常的0~139范围内,可见SCHED_DEADLINE 调度策略的优先级是极高的。
如果进程采用 SCHED_FIFO 或者 SCHED_RR 调度策略,那么优先级就等于 99 - rt_priority,rt_priority 的范围是0~99。当rt_priority 的越大,优先级数值越小,优先级就越高。这也就是为什么动态优先级 rt_priority 越大,优先级越大。
如果是采用 SCHED_NORMAL、SCHED_BATCH、SCHED_IDLE 调度策略,那么就采用 __normal_prio 来计算,其定义如下
static inline int __normal_prio(struct task_struct *p)
{
/* 直接返回static_prio */
return p->static_prio;
}
将上述的关系整理下表:
优先级和调度策略都存在于 task_struct 中,它们都是描述进程的信息,它们具体有什么用,我们下面将会介绍。
文章参考:https://blog.csdn.net/weixin_42462202/article/details/102887008?spm=1001.2014.3001.5502