linux cfs调度器_理论模型

参考资料:《调度器笔记》Kevin.Liu

                 《Linux kernel development》

                 《深入Linux内核架构》

                   version: 2.6.32.9

 

    下文中对于红黑树或链表组织的就绪队列,统称为用队列组织的就绪队列。                                               
    linux中用struct rq将处于ready状态的进程组织在一起。
    struct rq结构体包含cfs和rt成员,分别表示两个就绪队列:cfs就绪队列用于组织就绪的普通进程(这个队列上的进程用完全公平调度器进行调度);rt就绪队列用于组织就绪的实时进程(该队列上的进程用实时调度器调度)。
    在多核cpu系统中,每个cpu对应一个struct rq结构体实例。

    核心调度器分为:
    1、周期性调度器  schedule_tick();
         周期性调度器不负责进程的切换,只是定时更新调度相关的统计信息,以备主调度器使用。
    2、主调度器      schedule()
         主调度器的工作是完成进程的切换,将CPU的使用权从一个进程切换到另一个进程。

    调度器类:
    linux内核把用于处理普通进程调度的函数用struct sched_class结构体实例fair_sched_class组织起来;
    把用于处理实时进程调度的函数用struct sched_class结构体实例rt_sched_class组织起来;
    把用于处理idle进程调度的函数用struct sched_class结构体实例idle_sched_class组织起来。

linux中如何决定就绪进程在就绪队列中先后顺序的模型建立衍化过程:
A. runtime公平模型
    CPU的总时间按就绪进程数目等分给每个进程,每个进程在就绪队列中的先后顺序由它已享用的(runtime)决定,已享用CPU时间短的进程排在队列的最前面,反之排在队列的后面,调度器每次都选则队列的最前面的进程运行。
    漏洞:原有A、B、C三个进程在服务器上运行的runtime各自都等于1年,这时进程D被创建,那么接下来的一年内,只进程D在运行,其它进程如同dead一样……

B. min_runtime公平模型
    假设系统中有A、B、C三个进程,其已运行时间为:

  

---------------------|--------|----------|---------
    进程              |   A    |     B    |    C
---------------------|--------|----------|---------
    runtime(ms)      |   100  |    150   |   200
---------------------|--------|----------|---------


    什么是“公平”???
    操作系统应该在“当前”,将时间公平分配给“当前”系统中的每个进程,“当前”意味着:
        a)进程A、B、C在系统中经历了100 + 150 + 200 = 450ms,它们应公平享用这段时间,即每个进程应当执行150ms。
        b)D进程被创建了,那么从现在起操作系统应该将CPU时间公平分配给这四个进程(从进程创建之时起,它就应该受到“不计其它进程的历史”的待遇,调度器对所有此后运行的进程一视同仁)。

    如果将新建进程D的runtime设置为A、B、C中runtime最大的值显然对进程D不公,如果设置为它们中的最小值,那又对进程A不公。我们将进程D的runtime设置为A、B、C中最小的那个值,这就是在runtime公平模型上改进之后得到的min_runtime公平模型。

 C. weight优先级模型
    不同进程具有不同的重要性,重要的进程尽量被分配多的CPU时间,不重要的进程应该分配少的CPU时间。
    为了达到这个目的,我们引入一个权重(weight)参数,即每个进程有一个权重值,进程得到的CPU时间和这个权重值成正比。
    假设进程A、B的权重分别是1,2,这就意味着:A进程执行了Nms后以及B进程执行2Nms后它们应当具有相同的先后顺序,即它们的runtime值基本相同。由于runtime表示进程已经运行的时间,显然和上述表述矛盾,因此我们引入另一个参数vruntime(虚拟运行时间)代替它(vruntime仅仅是一个数值,用来作为对进程进行排序的参考,不用来反映进程真实执行时间).
    每个进程有一个vruntime值,调度器总是选择vruntime值最小的进程使用CPU资源,并且vruntime增长的速度和weight值成反比.
    设单核处理器上有新建的A、B两个进程:
    a) 初始的时候两个进程都没有运行,runtime都等于0

复制代码
-----------------|-------------|-----------
      进程        |   A         |    B      
-----------------|-------------|-----------
    weight       |   1         |    2           
-----------------|-------------|-----------
  runtime(ms)    |   0         |    0             
-----------------|-------------|-----------
    vruntime     |   0         |    0
-----------------|-------------|-----------
按vruntime进行排序               |    A B
-------------------------------|-----------
复制代码

 

    b) 调度器选择A,它运行了4ms:

复制代码
  ----------------|----------|-----------
      进程         |   A      |    B      
  ----------------|----------|-----------
      weight      |   1      |    2           
  ----------------|----------|-----------
      runtime(ms) |   4      |    0             
  ----------------|----------|-----------
      vruntime    |   4      |    0
  ----------------|----------|-----------
    按vruntime进行排序         |    B A
  ---------------------------|-----------
复制代码

 

    c) B的vruntime最小,选择B运行,运行了4ms:

复制代码
 -----------------|----------|-----------
     进程          |   A      |    B      
 -----------------|----------|-----------
     weight       |   1      |    2           
 -----------------|----------|-----------
   runtime(ms)    |   4      |    4             
 -----------------|----------|-----------
    vruntime      |   4      |    2
 -----------------|----------|-----------
    按vruntime进行排序         |    B A
 ----------------------------|-----------
复制代码

 

    d) B的vruntime最小,选择B运行,运行了4ms:

复制代码
  ----------------|----------|-----------
      进程         |   A      |    B      
  ----------------|----------|-----------
     weight       |   1      |    2           
  ----------------|----------|-----------
    runtime(ms)   |   4      |    8             
  ----------------|----------|-----------
      vruntime    |   4      |    4
  ----------------|----------|-----------
      按vruntime进行排序       |    A B
  ---------------------------|-----------
复制代码


    e) vruntime相同,但是A在前,选择A运行,运行了4ms:

复制代码
---------------|---------|-----------
      进程      |   A     |    B      
---------------|---------|-----------
     weight    |   1     |    2         
---------------|---------|-----------
   runtime(ms) |   8     |    8             
---------------|---------|-----------
   vruntime    |   8     |    4
---------------|---------|-----------
  按vruntime进行排序       |    B A
-------------------------|-----------
复制代码


            
    f) B的vruntime最小,选择B运行,运行了4ms:
          

复制代码
  ----------------|---------|-----------
        进程       |   A     |    B      
  ----------------|---------|----------
      weight      |   1     |    2           
  ----------------|---------|-----------
      runtime(ms) |   8     |    12             
  ----------------|---------|-----------
      vruntime    |   8     |    6
  ----------------|---------|-----------
       按vruntime进行排序     |    B A
  --------------------------|-----------
复制代码



D、period模型
    早期调度器使用了时间片模型,例如每当4ms后(或着进程还未执行完4ms,就有特殊情况产生了,比如进程要睡眠),主调度器
schedule就会重新选择一个vruntime值最小的进程来执行。
    但是现在我们不用时间片的概念了,那么主调度器schedule应该在什么时候启动并选择一个新的进程执行呢???
    
    引入period参数
    系统设定一个period值(它表示一段时间),每个进程对应一个ideal_runtime值(称为理想欲运行时间),每个进程的ideal_runtime值的设定方式:所有可运行进程的ideal_runtime值的和等于period,每个进程的ideal_runtime值的大小与它的权重weight成正比
    该模型规定:每个进程每次获得CPU使用权,最多执行它对应的ideal_runtime这样长的时间。
    注意:CPU并没有把时间分成长度为period的时间段,系统仅仅限定了每个进程每次执行时间不能超过它对应的ideal_time指定的时间长度。
    如果period=20ms,当前系统中只有A、B、C、D四个进程,它们的weight分别为:1、2、3、4。那么A的ideal_runtime = 2ms,B,C,D的ideal_runtime依次为4ms,6ms, 8ms。

    a) 初始情况如下:
           

复制代码
--------------------|-----------|----------|----------|----------
       进程          |    A      |    B     |    C     |    D
--------------------|-----------|----------|----------|----------
     weight         |    1      |    2     |    3     |    4
--------------------|-----------|----------|----------|----------
ideal_runtime(ms)   |    2      |    4     |    6     |    8
--------------------|-----------|----------|----------|----------
    runtime(ms)     |    0      |    0     |    0     |    0
--------------------|-----------|----------|----------|----------
     vruntime       |    0      |    0     |    0     |    0
--------------------|-----------|----------|----------|----------
  按vruntime进行排序  |    A B C D
--------------------|--------------------------------------------
复制代码

 


    b) 和前一个模型一样,vruntime的行走速度和权重值成反比,设定权重权为1的A进程的vruntime和实际runtime行走速度相同。A先执行,它执行了2ms,此时:

复制代码
--------------------|-----------|----------|----------|----------
         进程        |    A      |    B     |    C     |    D
--------------------|-----------|----------|----------|----------
       weight       |    1      |    2     |    3     |    4
--------------------|-----------|----------|----------|----------
ideal_runtime(ms)   |    2      |    4     |    6     |    8
--------------------|-----------|----------|----------|----------
   runtime(ms)      |    2      |    0     |    0     |    0
--------------------|-----------|----------|----------|----------
    vruntime        |    2      |    0     |    0     |    0
--------------------|-----------|----------|----------|----------
  按vruntime进行排序  |    B C D A
--------------------|--------------------------------------------
复制代码


    c) B的vruntime值最小,选择B运行,假设B运行了3ms:
           

复制代码
--------------------|-----------|----------|----------|----------
         进程        |    A      |    B     |    C     |    D
--------------------|-----------|----------|----------|----------
        weight      |    1      |    2     |    3     |    4
--------------------|-----------|----------|----------|----------
ideal_runtime(ms)   |    2      |    4     |    6     |    8
--------------------|-----------|----------|----------|----------
    runtime(ms)     |    2      |    3     |    0     |    0
--------------------|-----------|----------|----------|----------
     vruntime       |    2      |    1.5   |    0     |    0
--------------------|-----------|----------|----------|----------
  按vruntime进行排序  |    C D B A
--------------------|--------------------------------------------
复制代码


    d) C的vruntime值最小,选择C运行,假设C运行了3ms:
          

复制代码
 --------------------|-----------|----------|----------|----------
         进程         |    A      |    B     |    C     |    D
 --------------------|-----------|----------|----------|----------
       weight        |    1      |    2     |    3     |    4
 --------------------|-----------|----------|----------|----------
 ideal_runtime(ms)   |    2      |    4     |    6     |    8
 --------------------|-----------|----------|----------|----------
    runtime(ms)      |    2      |    3     |    3     |    0
 --------------------|-----------|----------|----------|----------
      vruntime       |    2      |    1.5   |    1     |    0
 --------------------|-----------|----------|----------|----------
  按vruntime进行排序   |    D C B A
 --------------------|--------------------------------------------
复制代码


    e) D的vruntime值最小,选择D运行,假设D运行了8ms:
          

复制代码
 --------------------|-----------|----------|----------|----------
        进程          |    A      |    B     |    C     |    D
 --------------------|-----------|----------|----------|----------
       weight        |    1      |    2     |    3     |    4
 --------------------|-----------|----------|----------|----------
 ideal_runtime(ms)   |    2      |    4     |    6     |    8
 --------------------|-----------|----------|----------|----------
    runtime(ms)      |    2      |    3     |    3     |    8
 --------------------|-----------|----------|----------|----------
     vruntime        |    2      |    1.5   |    1     |    2
 --------------------|-----------|----------|----------|----------
  按vruntime进行排序   |    C B A D
 --------------------|--------------------------------------------
复制代码


    f) 进程D运行的时间等于它的ideal_runtime,调度器被激活,重新选择一个进程运行,接着C进程被选中执行。
       关键在于: C可以运行多长时间???

       根据ideal_runtime的定义,它只是要求,每个进程每次占用CPU的资源不超过它对应的ideal_runtime,上次进程C被调度的时候它只执行了3ms,没有超过它的ideal_runtime(6ms);但是,这次它又可以获得CPU的使用权了,是新的一次调度了,与之前无关。因此,进程C最多可以运行6ms,那么接下来进程C可以连续运行6ms:
          

复制代码
 --------------------|-----------|----------|----------|----------
         进程         |    A      |    B     |    C     |    D
 --------------------|-----------|----------|----------|----------
      weight         |    1      |    2     |    3     |    4
 --------------------|-----------|----------|----------|----------
 ideal_runtime(ms)   |    2      |    4     |    6     |    8
 --------------------|-----------|----------|----------|----------
     runtime(ms)     |    2      |    3     |    9     |    8
 --------------------|-----------|----------|----------|----------
       vruntime      |    2      |    1.5   |    3     |    2
 --------------------|-----------|----------|----------|----------
  按vruntime进行排序   |    B A D C
 --------------------|--------------------------------------------
复制代码


        不要错误地认为:系统将CPU时间划分成一段一段的,每片长度为period,并且将它们按权重分配给每个进程,并且规定它们在该period内最多执行ideal_runtime限定的时间,进入下一个period时间段后,系统又重新为各个进程分配ideal_runtime;     因为CPU并没有把时间分成长度为period的时间段,系统仅仅限定了每个进程每次执行时不能超过它对应的ideal_time指定的时间长度。

        该机制的作用是:每个进程在就绪队列中等地的时间不会超过period,因为每个进程获得CPU使用权后,如果它执行的时间等于它的ideal_runtime,那么它的vruntime基本上就比其它所有进程的vruntime值高了,自然会排到队列的后面。

上述基于weight优先级模型和period模型的调度器所实现的效果, 每个进程每次调度运行的时间不在受4ms(例如)的限制了,而是可以运行“任意”长时间:
    a) 每个进程每次获得CPU使用权最多可以执行与它对应的ideal_runtime那么长的时间。
    b) 如果每个进程每次获得CPU使用权时它都执行了它对应的ideal_runtime那么长的时间,整个就绪队列的顺序保持不变。
    c) 如果某个进程某几次获得CPU使用权时运行的时间小于它ideal_time指定的时间(即它被调度时没有享用完它可以享用的最大
时间),按照vruntime进行排序的机制会使得它尽量排在队列的前面,让它尽快把没有享用完的CPU时间弥补起来。


period抽象模型基本上就是对内核cfs调度机制的一个抽象没有考虑睡眠,抢占等细节):
        a) 每个进程有一个权重值(weight),值越大,表示该进程越优先。
        b) 每个进程还对应一个vruntime(虚拟时间),它是根据进程实际运行的时间runtime计算出来的。vruntime值不能反映进程执行的真实时间,只是用来作为系统判断接下来应该选择哪个进程使用CPU的依据————调度器总是选择vruntime值最小的进程执行。
        c) vruntime行走的速度和进程的weight成反比。
        d) 为了保证在某段时间(period)内每个进程至少能执行一次,操作系统引入了ideal_runtime的概念,规定每次获得CPU使用权时,执行时间不能超过它对应的ideal_runtime值。达到该值就会激活调度器,让调度器再选择一个vruntime值最小的进程执行。
        e) 每个进程的ideal_runtime长度与它的weight成正比,如果有N个进程,那么:
                                           task[i]->weight
           task[i]->ideal_time = -------------------- * period
                                         sum_weight(task, N)   



调度器真实模型的主要成员变量及与抽象模型的对应关系

I、cfs_rq结构体
    a) struct sched_entity *curr
        指向当前正在执行的可调度实体。调度器的调度单位不是进程,而是可调度的实体。每个task_struct都嵌入了一个可调度实体sched_entity,所以每个进程是一个可调度实体。可以将多个进程捆绑在一起作为一个调度单位(即调度实体)进行调度。因此,可调度实体可以是一个进程,也可以是多个进程构成的一个组。为了简化,暂时把curr理解为指向当前正在运行的进程。
    d) struct rboot tasks_timeline                                                                                 
        cfs就绪队列是用红黑树来组织的,tasks_timeline存放红黑树的根节点。
    c) struct rb_node *rb_leftmost
        指向红黑树最左边的那个节点。即,vruntime值最小的那个进程对应的节点。
    d) unsigned long nr_running
        可运行进程的总数,即队列进程的总数加上正在运行的那个进程。
    e) struct load_weight load
        struct load_weight {
            unsigned long weight, inv_weight;
        }
        load.weight保存着cfs_rq队列上所有进程(包括当前正子运行的进程)的权重值weight的总和。
    f) min_vruntime
        min_vruntime用来解决下列问题:
        min_runtime调度模型的遗留问题————原有系统上运行A、B、C三个进程,新进程D被创建时它的runtime应该如何设置???或者,睡眠的进程醒来后其runtime该如何设置???
        min_vruntime的值初始的时候为0, schedule_tick周期性调度器负责周期性地调用min_vruntime的值和可运行的进程中最小的vruntime值比较,如果该进程的vruntime值比min_vruntime大,那么就将min_vruntime值更新为较大值。min_vruntime的值只会随着时间的推移增加,不会减少。(注意:min_vruntime的值有可能比一些进程的vruntime大)。

II、sched_entity结构体
    a) unsigned int on_rq
        用来标识该实体上是否在就绪的队列上,如果在就绪队列上则该值非零。
    b) struct load_weight load
        load.weight表示该调度实体的权重
    c) u64 sum_exec_runtime
        表示进程已经执行的实际时间,对应抽象模型中的runtime
    d) u64 pre_sum_exec_runtime
        进程在切换经CPU时的sum_exec_runtime值
        sum_exec_runtime - pre_sum_exec_runtime等于进程获得CPU使用权后总的总时间。(ideal_runtime已被消耗了多少)
    e) u64 vruntime
        该进程已经运行的虚拟时间,对应抽象模型中的vruntime。
    f)u64 exec_start
         表示上次tick中断时设置的时间戳。

注意rq:clock表示本次tick发生时的时间戳。


抽象模型和真实模型的对应关系
    
    抽象模型                           真实模型                                                    说明
-----------------------------------------------------------------------------------------------
    task->runtime             task->se.sum_exec_runtime     每个进程对应一个可调度实体,

                                                                                               在task_struct的结构体,该实体

                                                                                               就是成员变量se。
-----------------------------------------------------------------------------------------------
    task->weight              task->se.load.weight
-----------------------------------------------------------------------------------------------
    task->vruntime           task->se.vruntime
-----------------------------------------------------------------------------------------------
    sum_weight(task,N)  cfs_rq->load.weight                    在抽象模型中,我们计算ideal_runtime

                                                                                               的时候需要求所有进程的权重值

                                                                                               的和,在实现的时候, 没有求和

                                                                                               的过程,而是把该值记录在就绪

                                                                                               队列 的load.weight中。向就绪队

                                                                                              列中添加新进程时,就加上新进

                                                                                              程的权重值,进程被移除就绪队

                                                                                             列时则减去被移除的进程的权重。
-----------------------------------------------------------------------------------------------
                                   cfs_rq->min_vruntime                   该值用来解决之前在抽象模型中遗留

                                                                                            的问题(新加入进程的runtime如

                                                                                            何处理,进程睡眠唤醒后的 runtime

                                                                                            如何处理……),因此没有抽象

                                                                                            模型中与之对应的值。
-----------------------------------------------------------------------------------------------
    task->ideal_runtime     sched_slice()函数                  每个进程的ideal_runtime并没有用

                                                                                            变量保存起来,而是在需要用

                                                                                            到时用函数sched_slice()计算

                                                                                            得到。
                                                                                                                 task[i]->se.load.weight
                                                                             task[i]->ideal_runtime = -------------------------  * period
                                                                                                                  cfs_rq->load.weight
-----------------------------------------------------------------------------------------------
    period                       __sched_period()函数              period也没有用变量来保存,用函

                                                                                           数计算得到。
                                                                   sysctl_sched_latency * (nr_running / sysctl_nr_latency)
-----------------------------------------------------------------------------------------------                  


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值