进程调度子系统(1)调度器数据结构

调度器的任务,在进程间共享cpu时间,创造并行执行的错觉。该任务分为两个不同的部分:一个涉及调度策略,另一个涉及上下文切换。

1.相关数据结构

1.1 task_struct相关成员

1042 struct task_struct {

1058         int on_rq; 
1059 
1060         int prio, static_prio, normal_prio;//优先级

1061         unsigned int rt_priority;
1062         const struct sched_class *sched_class;//调度类
1063         struct sched_entity se; //调度实体
1064         struct sched_rt_entity rt;

1078         unsigned int policy;   //调度策略
1079         int nr_cpus_allowed;   
1080         cpumask_t cpus_allowed;  //cpu位图

}

1.2 优先级表示

用户进程优先级表示:prio和normal_prio为动态优先级,static_prio为静态优先级。

static_prio是进程创建时分配的优先级,如果不人为的更改(nice和sched_setscheduler系统调用),那么在这个进程运行期间不会发生变化。

normal_prio是基于static_prio和调度策略计算出的优先级。

prio是调度器类考虑的优先级,某些情况下需要暂时提高进程的优先级(实时互斥量),因此有此变量,对于优先级未动态提高的进程来说这三个值是相等的。

以上三个优先级值越小,代表进程的优先级有高(-20~19)。实时进程优先级表示:
rt_policy表示实时进程的优先级(0~99),该值与prio,normal_prio和static_prio不同,值越大代表实时进程的优先级越高。内核如何处理这些优先级之间的关系呢?其实,内核使用0~139表示内部优先级,值越低优先级越高。其中0~99为实时进程,100~139为非实时进程。

/include/linux/sched/rt.h

 17 #define MAX_USER_RT_PRIO        100
 18 #define MAX_RT_PRIO             MAX_USER_RT_PRIO
 19 
 20 #define MAX_PRIO                (MAX_RT_PRIO + 40)
 21 #define DEFAULT_PRIO            (MAX_RT_PRIO + 20)

静态优先级转换

kernel/sched.c

 90 #define NICE_TO_PRIO(nice)      (MAX_RT_PRIO + (nice) + 20)
 91 #define PRIO_TO_NICE(prio)      ((prio) - MAX_RT_PRIO - 20)
 92 #define TASK_NICE(p)            PRIO_TO_NICE((p)->static_prio)
三种优先级的计算:

  当static_prio分配好后,prio和normal_prio计算方法实现如下:
 首先,大家都知道进程创建过程中do_fork会调用wake_up_new_task,在该函数中会调用static inteffective_prio(struct task_struct *p)函数。
 
void fastcall wake_up_new_task(struct task_struct *p, unsigned longclone_flags)
    {
          unsigned long flags;
           struct rq *rq;
       ...
          p->prio =effective_prio(p);  
       ...
    }

   

1953 static int effective_prio(struct task_struct *p)
1954 {
1955         p->normal_prio = normal_prio(p);
1956         /*
              *如果是实时进程或是已经提高到了实时优先级,保持优先级不变。否则,返回普通优先级
1960          */
1961         if (!rt_prio(p->prio))
1962                 return p->normal_prio;//非实时进程返回普通优先级
1963         return p->prio; //实时情况,保持优先级不变
1964 }

1923 static inline int __normal_prio(struct task_struct *p)
1924 {
1925         return p->static_prio;//返回静态优先级
1926 }
1927 

1935 static inline int normal_prio(struct task_struct *p)
1936 {
1937         int prio;
1938 
1939         if (task_has_rt_policy(p)) //是否是实时进程
1940                 prio = MAX_RT_PRIO-1 - p->rt_priority; //实时优先级数值转换为内核表示
1941         else
1942                 prio = __normal_prio(p); //普通进程
1943         return prio;
1944 }
可见,对于普通进程的prio,normal_prio和static_prio是一样的,但是也有特殊情况,当使用实时互斥量时普通进程的prio会暂时发生变化。
注意,子进程分支时,静态优先级继承自父进程,prio为父进程的normal_prio。这样确保实时互斥量引起的优先级提高不会传递给子进程。

1.3 调度类

调度类提供了调度器和各个调度方法之间的关联,对于不同的调度策略内核提供了不同的调度类,调度类采用平坦结构按进程的重要性顺序连接。实时调度类在完全公平调度类之前,最后是空闲进程调度类(这个层次结构在编译时建立,没有运行时动态增加的机制)。

调度器类提供了对进程的管理

kernel/sched/sched.h

994 struct sched_class {
995         const struct sched_class *next;
996 
997         void (*enqueue_task) (struct rq *rq, struct task_struct *p, int flags);
998         void (*dequeue_task) (struct rq *rq, struct task_struct *p, int flags);
999         void (*yield_task) (struct rq *rq);
1000         bool (*yield_to_task) (struct rq *rq, struct task_struct *p, bool preempt);
1001 
1002         void (*check_preempt_curr) (struct rq *rq, struct task_struct *p, int flags);
1003 
1004         struct task_struct * (*pick_next_task) (struct rq *rq);
1005         void (*put_prev_task) (struct rq *rq, struct task_struct *p);
1006 
             .....//SMP相关函数
1022 
1023         void (*set_curr_task) (struct rq *rq);
1024         void (*task_tick) (struct rq *rq, struct task_struct *p, int queued);
1025         void (*task_fork) (struct task_struct *p);
1026 
1027         void (*switched_from) (struct rq *this_rq, struct task_struct *task);
1028         void (*switched_to) (struct rq *this_rq, struct task_struct *task);
1029         void (*prio_changed) (struct rq *this_rq, struct task_struct *task,
1030                              int oldprio);
1031 
1032         unsigned int (*get_rr_interval) (struct rq *rq,
1033                                          struct task_struct *task);
1034 
            .....//组调度相关
1038 };
具体的函数后面分析

1.4 调度实体

调度器不限于调度进程,还可以处理更大的实体(实现组调度)。这个一般性要求调度器不直接操作进程,而是处理可调度实体。最简单的情况下,单个进程可以看作一个可调度实体(内嵌 在task_struct结构)。

985 struct sched_entity {
986         struct load_weight      load;           /* for load-balancing */
987         struct rb_node          run_node;  //红黑树节点

989         unsigned int            on_rq;  //当前实体是否在接续队列上
990         /*完全公平调度器所需的时间*/
991         u64                     exec_start;  //进程开始执行的时间
992         u64                     sum_exec_runtime;//进程在cpu上运行的时间综合
993         u64                     vruntime;  //虚拟时间,用于在红黑树排队(完全公平调度需要)
994         u64                     prev_sum_exec_runtime; //进程上次在cpu上运行的时间综合
995 
996         u64                     nr_migrations;
997 
          ...// 统计信息
1001 
          ... //组调度相关
1009 
          ...//SMP相关
1014 };

struct sched_entity中含有struct rb_node的实例,structrb_node是红黑树(就绪队列的内核组织结构)的节点类型,这样在红黑树中也是通过container_of机制找到structsched_entity实体的。

1.5 就绪队列

核心调度器用于管理活动进程的主要就数据结构,各cpu有自身的就绪队列,每个活动进程只能出现在一个就绪队列中(在多个cpu上运行同一进程不可能)。

注意,进程不是由就绪队列成员直接管理的,这是调度器类的责任,因此在就绪队列中嵌入了特定于调度器类的子就绪队列

struct rq {

411         unsigned int nr_running; //可运行进程数

416         #define CPU_LOAD_IDX_MAX 5
417         unsigned long cpu_load[CPU_LOAD_IDX_MAX]; 
418         unsigned long last_load_update_tick;


426         int skip_clock_update;
427 
428         /* capture load from *all* tasks on this cpu: */
429         struct load_weight load;  //该cpu队列上的权重
430         unsigned long nr_load_updates;
431         u64 nr_switches;
432 
433         struct cfs_rq cfs;  //完全公平调度子队列
434         struct rt_rq rt;
435 
           ...//组调度相关
452 
453         struct task_struct *curr, *idle, *stop; //进程结构指针
454         unsigned long next_balance;
455         struct mm_struct *prev_mm;   //上一个内存上下文
456 
457         u64 clock;       //就绪队列自身时钟(实际的时钟滴答)     
458         u64 clock_task;
459 
460         atomic_t nr_iowait;
461 
            ...//SMP 及 一些统计信息相关
534 };

1.1.6 负荷权重的计算
进程的重要性不但由优先级指定,还需要考虑保存在task_struct->se.load的负荷权重。

linux/sched.h

932 struct load_weight {
933         unsigned long weight;
934         u32 inv_weight; //被负荷权重除的结果
935 };


kernel/sched.c

//动态优先级和权重转换表

955 static const int prio_to_weight[40] = {
956  /* -20 */     88761,     71755,     56483,     46273,     36291,
957  /* -15 */     29154,     23254,     18705,     14949,     11916,
958  /* -10 */      9548,      7620,      6100,      4904,      3906,
959  /*  -5 */      3121,      2501,      1991,      1586,      1277,
960  /*   0 */      1024,       820,       655,       526,       423,
961  /*   5 */       335,       272,       215,       172,       137,
962  /*  10 */       110,        87,        70,        56,        45,
963  /*  15 */        36,        29,        23,        18,        15,
964 };

可见优先级越高,权重越大,实时优先级是普通优先级的两倍,idle进程优先级最小,默认权重是优先级0的权重。

权重和vrruntimr时间的关系:

    ideal_time = sum_runtime*se.weight/cfs_rq.weight
    1) vruntime = delta* NICE_0_LOAD/se.weight  //(if curr.nice != NICE_0_LOAD)
    2)vruntime = delta;(if curr.nice = NICE_0_LOAD)

所以,从上面来看,优先级越高,权重越大,所得到的虚拟时间越少,最后越靠近就绪队列的红黑中的左边,否则,就越靠右边。
内核利用set_load_weight根据进程类型和静态优先级计算权重。
kernel/sched/sched.h

 940 #define WEIGHT_IDLEPRIO                3

       #define WMULT_IDLEPRIO         1431655765

 60 # define SCHED_LOAD_RESOLUTION  10
 61 # define scale_load(w)          ((w) << SCHED_LOAD_RESOLUTION)
 62 # define scale_load_down(w)     ((w) >> SCHED_LOAD_RESOLUTION)
 63 #else
 64 # define SCHED_LOAD_RESOLUTION  0
 65 # define scale_load(w)          (w)
 66 # define scale_load_down(w)     (w)
 67 #endif
 68
 69 #define SCHED_LOAD_SHIFT        (10 + SCHED_LOAD_RESOLUTION)
 70 #define SCHED_LOAD_SCALE        (1L << SCHED_LOAD_SHIFT)
 71
 72 #define NICE_0_LOAD             SCHED_LOAD_SCALE
 73 #define NICE_0_SHIFT            SCHED_LOAD_SHIFT

kernel/core.c

745 static void set_load_weight(struct task_struct *p)
746 {
747         int prio = p->static_prio - MAX_RT_PRIO;
748         struct load_weight *load = &p->se.load;
749
750         /*
751          * SCHED_IDLE tasks get minimal weight:
752          */
753         if (p->policy == SCHED_IDLE) {
754                 load->weight = scale_load(WEIGHT_IDLEPRIO);
755                 load->inv_weight = WMULT_IDLEPRIO;
756                 return;
757         }
758
759         load->weight = scale_load(prio_to_weight[prio]);
760         load->inv_weight = prio_to_wmult[prio];
761 }

借两个图看一下总体结构

                                                                                                                                             图 调度子系统数据结构



                                                                                                            

                                                                                                                                             图 调度器框架                 



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
目录 1 进程的组织 5 1.1 进程相关数据结构 5 1.1.1 进程的基本信息 6 1.1.2 进程状态 10 1.1.3 TASK_RUNNING状态的进程链表 11 1.1.4 进程间关系 12 1.2 Linux的线程——轻量级进程 15 1.3 进程的创建——do_fork()函数详解 19 1.4 执行进程间切换 33 1.4.1 进程切换之前的工作 33 1.4.2 进程切换实务 —— switch_to宏 37 1.4.3 __switch_to函数 39 1.5 fork与vfock系统调用的区别 42 1.6 内核线程 46 1.7 挂起状态进程的组织 49 1.7.1 等待队列头 49 1.7.2 等待队列的操作 50 1.7.3 进程资源限制 55 1.8 系统调用execve() 56 1.8.1 拷贝用户态参数 57 1.8.2 重要的数据结构 61 1.8.3 search_binary_handler函数 66 1.8.4 目标文件的装载和投入运行 69 1.8.5 库函数 92 2 中断控制 94 2.1 中断的分类 94 2.2 中断的硬件环境 95 2.2.1 外部中断请求IRQ 95 2.2.2 中断描述符表 96 2.2.3 中断和异常的硬件处理 97 2.3 中断描述符表 99 2.3.1 中断门、陷阱门及系统门 99 2.3.2 IDT的初步初始化 100 2.4 异常处理 101 2.5 中断处理 106 2.5.1 中断向量 107 2.5.2 IRQ数据结构 108 2.5.3 do_IRQ()函数 113 2.5.4 中断服务例程 115 2.5.5 IRQ线的动态分配 116 2.6 下半部分 117 2.6.1 软中断 118 2.6.2 tasklet 121 2.6.3 工作队列 122 2.7定时中断 124 2.7.1 时钟与定时 124 2.7.2 定时中断相关的数据结构 127 2.7.3 定时中断的上半部分 129 3 进程调度 138 3.1 进程调度的概念 138 3.2 进程调度数据结构和优先级 141 3.2.1 进程的优先级 141 3.2.2 数据结构 145 3.3 调度程序所使用的函数 151 3.3.1 scheduler_tick函数 151 3.3.2 try_to_wake_up函数 156 3.3.3 recalc_task_prio函数 160 3.4 schedule()函数 163 3.4.1 直接调用 163 3.4.2 延迟调用 164 3.4.3 进程切换之前所做的工作 168 3.4.4 完成进程切换时所执行的操作 171 3.4.5 进程切换后所执行的操作 173 3.5 多处理运行队列的平衡 175 3.5.1 调度域 176 3.5.2 rebalance_tick()函数 178 3.5.3 load_balance()函数 180 3.5.4 move_tasks()函数 183 3.6 进程退出 187 3.6.1 进程终止 187 3.6.2 进程删除 189 4 进程的并发性体现 191 4.1 内核抢占 193 4.1.1 内核抢占概念 193 4.1.2 同步技术总揽 196 4.2 每CPU变量 197 4.3 原子操作 199 4.4 优化屏障和内存壁垒 203 4.4.1 优化屏障 204 4.4.2 内存壁垒 204 4.5 自旋锁 206 4.6 读写自旋锁 211 4.6.1 为读获取和释放一个锁 213 4.6.2 为写获取或释放一个锁 214 4.7 顺序锁 215 4.8 RCU机制 217 4.9 信号量 219 4.9.1 获取和释放信号量 221 4.9.2 读/写信号量 224 4.9.3 补充信号量 225 4.10 禁止本地中断 226 4.10.1 禁止本地中断 227 4.10.2 禁止下半部(可延迟函数) 229 4.11 一些避免竞争条件的实例 231 4.11.1 引用计数 231 4.11.2 大内核锁 231 4.11.3 内存描述符读/写信号量 232 4.11.4 slab高速缓存链表的信号量 233 4.11.5 索引节点的信号量 233 4.12 内核同步与互斥的总结 233

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值