kernel调度(1)----基本知识介绍

调度简介

最近在学习linux 内核调度相关的知识,也看了好多文章,内核代码,下面就学习的内容做个梳理,有助于自己的记忆,可能有的地方自己理解不对,希望多多指教。下面就从几个基本概念开始。

什么是调度

有过点操作系统知识的人都知道,调度的工作主要就是完成操作系统内进程切换的过程,让多个进程多个任务可以轮流的使用cpu的资源。那么在这个过程中涉及到很多调度相关的概念。下面就从这些简单的概念开始

调度的对象–线程

进程和线程的概念这里就不再详细的说,大家都知道。其实在内核里面,对于调度来说是不分进程还是线程概念的,调度的对象就是一个task,对应的结构就是task_strct(如下图所示),这个数据结构很大很大,包含了很多进程相关的信息。
在这里插入图片描述
其实在结构内和调度相关的几个成员如下:

struct task_struct {
 ......
 const struct sched_class *sched_class;
 struct sched_entity  se;
 struct sched_rt_entity  rt;
 ......
 struct sched_dl_entity  dl;
 ......
 unsigned int   policy;
 int  on_rq;
 int prio;
 int static_prio;
 int normal_prio;
 unsigned int rt_priority;
 ......
}

每个成员干啥的主要如下:
sched_class:表示调度器类,内核由于进程类型不同,实时进程和普通进程等,针对不同的进程采用不同的调度策略,和在就绪队列的链表存储上就做了区分,不同类型的进程,以单独的链表存储,又都在每个cpu对应的rq上。所以这里就有了调度器类的概念。而每种调度器如何调度(也就是调度算法)都在上面提到的主调度器和周期性调度器中有体现。
se/rt/dl:分别对应不同调度器类对应的调度实体,se是cfs调度器的调度实体,rt是RT调度器类对应的调度实体,dl是DL调度器类对应的调度实体。这里提到调度实体,什么是调度实体,后面会讲解。
policy:表示这个进程所采用的调度策略,其实也就表示了是什么进程。在内核中的取值如下:

#define SCHED_NORMAL        0//按照优先级进行调度(有些地方也说是CFS调度器)
#define SCHED_FIFO        1//先进先出的调度算法
#define SCHED_RR        2//时间片轮转的调度算法
#define SCHED_BATCH        3//用于非交互的处理机消耗型的进程
#define SCHED_IDLE        5//系统负载很低时的调度算法

也就是内核中对应的调度策略。
on_rq:表示这个task所在的rq队列。
prio:进程的优先级,是进程最终调度的时候使用的优先级。
在这里插入图片描述
优先级的范围是0-139,其中0-99是属于实时进程的优先级,100-139是普通进程的优先级,数值越小,优先级越高。
static_prio:静态进程优先级,在进程创建的时候赋值(个人理解和nice值有一定的关系)。可以通过nice()接口修改。
normal_prio:根据静态优先级和调度策略计算出来的优先级,子进程会继承父进程的这个值。
对于普通进程,normal_prio=static_prio,对于实时进程normal_prio=MAX_RT_PRIO(100内核里的宏)-1-rt_prio。
rt_priority:实时进程的优先级,只用于实时进程,数值越大,优先级越高。
疑问:(其实这里有个问题没搞懂明白,pro 是最终用于调度的优先级,那么他和下面的static_prio /normal_prio/rt_prio是啥关系呢?,回头需要研究一下,有了解的可以评论区回答。)

调度器

调度器就是完成任务调度的主题。任务切换的主要工作就是由调度器完成的。在内核里面其实由两个调度器,分别为主调度器核周期性调度器。其实这两个调度器就是指两个函数。主调度器schedule()和周期性调度器scheduler_tick()。其实任务切换的过程都是由这两个函数完成的,schedule()函数是主调度器,大多数场景是任务(task)主动去调用,完成进程切换。其实进程切换的过程,也是一个很复杂的过程,主要包含硬件寄存器上下文的切换和软件上下文的切换。这里不做详细的介绍,后面会单独写一篇上下文切换都做了什么的文章来介绍。而周期性调度器scheduler_tick()是以定时器中断的方式定时触发的,也就是每隔一段时间每个cpu都会触发这个中断,触发这个函数,来检查当前任务时间片是否用完,或者需要调度,如果需要调度就完成调度的工作。其实这个函数还做了很多关于进程切换的统计信息,包括时间信息,任务负载等信息。这里不做详细的介绍。
在这里插入图片描述
正如上图所示,主调度器和周期性调度器一起完成了内核里面进程切换的工作。让操作系统里面的每一个任务看似公平的在使用cpu的资源。给人的感觉就是每一个任务都在并行的运行。其实在单核处理器上,都知道每一个task在宏观上是并行的,但是其实微观上都是串行执行的,只有在smp多核处理器上才可以称的上微观上并行处理。

调度器类

内核中调度器类主要有以下下五种,分别为
stop_sched_class/
dl_sched_class/
rt_sched_class/
fair_sched_class/
idle_sched_class

其优先级依次降低,在调度器里面进行任务切换的时候,首先会在rq里面按照这个优先级遍历对应的任务链表,看是否有就绪的任务等待运行,有的话会优先先择高优先级调度器类里面的任务运行。例如:rt_sched_class对应的就绪任务链表里的任务,这个任务肯定要比fair_sched_class对应的就绪任务链表里的任务优先运行。

调度策略

内核里面主要有的调度策略如下:
SCHED_DEADLINE
SCHED_RR
SCHED_FIFO
SCHED_NORMAL
SCHED_BATCH
SCHED_IDLE
每种调度策略都对应这一种调度算法,
SCHED_DEADLINE:对应着deadline调度算法,算法的精确名称是Earliest DeadlineFirst,详细的算法原理这里先不做详细的介绍了。其实每一种调度算法都可以作为一篇文章来详细的讲解一下。
SCHED_RR:对应着Roound-Robin算法
SCHED_FIFO:FIFO算法
这两种是属于实时进程的调度算法,这两种调度算法也是有区别的。
SCHED_NORMAL
SCHED_BATCH
这两个对应这cfs调度算法。
SCHED_IDLE没有算法,表示没有进程调度的时候就会调用idle线程。

调度器类和调度策略的对应关系

下图表示了调度器类和调度策略之间的关系,以及每种调度测率对用的调度算法。
在这里插入图片描述
调度器类,调度策略,优先级可以概括如下图的关系:
在这里插入图片描述

调度实体

谈到调度实体其实具体就是指
struct sched_entity se/
struct sched_rt_entity rt/
struct sched_dl_entity dl
每一个进程的结构task_struct结构内都包含这三种类型的调度实体,为什么会有三种类型的调度实体呢?其实就是为了方便每种调度算法在选择进程的时候用的计算的key和计算方式不同。当然不同的进程,对应不同的调度器类,也对应这不同的调度算法,不同的调度算法就意味着不同的计算key和计算方式。

cfs 调度实体sched_entity se

下面来看看c普通cfs 调度算法用的se的代码结构:

struct sched_entity {
  struct load_weight____load;
  struct rb_node______run_node;
  struct list_head____group_node;
  unsigned int______on_rq;
  u64_______exec_start;
  u64_______sum_exec_runtime;
  u64_______vruntime;
  u64_______prev_sum_exec_runtime;
  u64_______nr_migrations;
  struct sched_statistics___statistics;
   #ifdef CONFIG_FAIR_GROUP_SCHED
   int_______depth;
   struct sched_entity___*parent;
   struct cfs_rq_____*cfs_rq;
   struct cfs_rq_____*my_q;
   unsigned long_____runnable_weight;
   #endif
   #ifdef CONFIG_SMP
   struct sched_avg____avg;
   #endif
};
其中涉及到的结构类型如下
struct load_weight { 
unsigned long_____weight;
u32_______inv_weight;
}struct rb_node {
 unsigned long  __rb_parent_color;
 struct rb_node *rb_right;
 struct rb_node *rb_left; 
 } __attribute__((aligned(sizeof(long))));
struct sched_avg {
  u64_______last_update_time;
  u64_______load_sum;
  u64_______runnable_sum;
  _u32_______util_sum;
  u32_______period_contrib;
  unsigned long_____load_avg;
  unsigned long_____runnable_avg;
  unsigned long_____util_avg;struct util_est_____util_est;
}____cacheline_aligned;
 struct sched_statistics {
#ifdef CONFIG_SCHEDSTATS 
u64_______wait_start;
u64_______wait_max;
_u64_______wait_count;
u64_______wait_sum;
u64_______iowait_count;
u64_______iowait_sum;
u64_______sleep_start;
u64_______sleep_max;
s64_______sum_sleep_runtime;
u64_______block_start;
u64_______block_max;
_u64_______exec_max;
u64_______slice_max;
u64_______nr_migrations_cold;
u64_______nr_failed_migrations_affine;
u64_______nr_failed_migrations_running;
u64_______nr_failed_migrations_hot;
u64_______nr_forced_migrations;
u64_______nr_wakeups;
u64_______nr_wakeups_sync;
u64_______nr_wakeups_migrate;
u64_______nr_wakeups_local;
u64_______nr_wakeups_remote;
u64_______nr_wakeups_affine;
u64_______nr_wakeups_affine_attempts;
u64_______nr_wakeups_passive;
u64_______nr_wakeups_idle;

具体里面每一个变量是干啥用的,这里不做详细的介绍了。后期再讲解cfs调度算法的时候再详细的讲解。

rt 调度实体struct sched_rt_entity rt

下面是struct sched_rt_entity 的结构:

struct sched_rt_entity { 
struct list_head____run_list;
unsigned long_____timeout;
unsigned long_____watchdog_stamp;
unsigned int______time_slice;
unsigned short______on_rq;
unsigned short______on_list;
struct sched_rt_entity____*back;
#ifdef CONFIG_RT_GROUP_SCHED
struct sched_rt_entity____*parent;
struct rt_rq______*rt_rq;
struct rt_rq______*my_q;
#endif
}__randomize_layout; 

dl调度实体struct sched_dl_entity dl

struct sched_dl_entity结构如下:

在这里插入图片描述
在这里插入图片描述

runqueue 运行队列struct rq rq

在内核里面,每一个cpu都有一个自己的运行队列,里面存放,就绪的task,每个task以链表节点的形式存在。
每个运行队列中有三个调度队列,task 作为调度实体加入到各自的调度队列中。
这个rq 是每cpu变量,cpu在调度的时候会从自己的rq 里面获取task。

struct rq rq结构

如下图所示:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
从上图可知道,这个结构中包含很多变量,这里不详细介绍每一个变量是干啥用的了。
其实主要的就是里面的三种调度器类对应的运行队列。

struct rq {
 ......
 struct cfs_rq cfs;
 struct rt_rq rt;
 struct dl_rq dl;
 ......
}

三个调度队列:

struct cfs_rq cfs:CFS调度队列
struct rt_rq rt:RT调度队列
struct dl_rq dl:DL调度队列

下面详细的看一下每种调度队列的具体数据结构:

struct cfs_rq cfs

结构如下所示:
在这里插入图片描述
在这里插入图片描述

struct rt_rq rt

结构如下:
在这里插入图片描述

struct dl_rq dl

struct dl_rq结构如下:
在这里插入图片描述
在调度的时候,首先拿到自己的rq指针,然后按照调度器类的优先级依次遍历dl->rt->cfs的运行队列,然后根据不同的调度运行队列里面的调度算法选取se,然后根据se拿到task,最后做进程切换。
在这里插入图片描述

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值