你好,欢迎来到潮汕IT智库!
您的位置:首页 > IT资讯> 热点新闻 热点新闻
图解Linux进程调度
2022-07-19 15:24:16 作者: (评论0条)

优先级与调度策略:

在内核中,肯定不能对所有的进程一视同仁,有的进程需要优先运行,有的进程需要运行更长的时间

为了更好地实现进程调度,每个进程都有自己的优先级和调度策略

所谓优先级,就是表示这个进程的重要性,优先级高的自然会被更好的对待

那调度策略又是什么呢?想一想,进程调度其实是一个非常复杂的问题,想使用一种算法来实现良好的进程调度是不可能的,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_FIFO
  • SCHED_RR
  • SCHED_DEADLINE

属于普通进程的调度策略是:

  • SCHED_NORMAL
  • SCHED_BATCH
  • SCHED_IDLE

下面我来跟你详细讲解每个调度策略代表什么:

  • 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的优先级表示普通进程的优先级,数值越小表示优先级越高:

1.png

首先我们讲static_prio,进程启动的时候会设置好静态优先级。如果需要修改,可以通过nice系统调用来设置,nice的范围是-2019,最终映射到优先级为100139的部分,如下所示:

2.png

内核中定义如下:


/* 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;
}

将上述的关系整理下表:

3.png

优先级和调度策略都存在于 task_struct 中,它们都是描述进程的信息,它们具体有什么用,我们下面将会介绍。

文章参考:https://blog.csdn.net/weixin_42462202/article/details/102887008?spm=1001.2014.3001.5502

相关文章
红帽RHEL将成为微软官方WSL发行版...
请求都有并发数的限制...
Vue 3 编译器...
C++ 的两个派系之争...