cgroups分析与应用连载(二)

Cpu子系统代码分析

文章欢迎转载,转载请参见文章末尾处要求

CFS进程组调度分析

原理

CFS是基于单个进程进行公平调度,如果进程A,B,C,D按照CFS的调度原则,在相同优先级的情况下,每个进程可以获得25%的cpu时间。如果这四个进程中,只有进程A属于“eric”用户,而B,C,D属于“other”用户。就造成了用户“eric”只占用了25%的cpu时间,而“other”却占据了75%的cpu时间。在多用户系统上,这样的调度算法并不是用户所期望的。于是提出了分组调度原理。

Cfs分组可以对cfs组权重(shares值)进行设置,cfs组各任务(含child组)的累计时间不超过parent组的权重。

在两个 cgroup 中都将cpu.shares 设定为 1 的任务将有相同的 CPU 时间,但在 cgroup 中将 cpu.shares 设定为 2的任务可使用的 CPU 时间是在 cgroup 中将 cpu.shares 设定为 1 的任务可使用的 CPU 时间的两倍。

static struct cftype cpu_files[] = {

#ifdef CONFIG_FAIR_GROUP_SCHED

         {

                   .name= "shares",

                   .read_u64= cpu_shares_read_u64,

                   .write_u64= cpu_shares_write_u64,

         },

#endif

 

数据结构

task_group {

/*内嵌的css结构,可用于从css(cgroup_subsys_state)到task_group的纽带*/

   structcgroup_subsys_state css;

 

#ifdef CONFIG_FAIR_GROUP_SCHED

   /* schedulableentities of this group on each cpu */

/*每个cpu都对应一个sched_entity*/

   struct sched_entity**se;

   /* runqueue"owned" by this group on each cpu */

/*每个cpu都对一个cfs_rq*/

   struct cfs_rq**cfs_rq;

/*调度组的shares值*/

   unsigned longshares;

#endif

  /*用于链接到task_groups链表*/

   struct list_headlist;

 

/*指向上层task_group,根节点的task_group是最终的parent */

   struct task_group*parent;

/*链入到上层task_group的children链表*/

   struct list_headsiblings;

  /*用来链接下层的task_group */

   struct list_headchildren;

 

}

 

 

调度实体:

struct sched_entity {

         struct load_weight  load;                   /*for load-balancing */

         struct rb_node                   run_node;

         struct list_head        group_node;

         unsigned int              on_rq;

… …

#ifdef CONFIG_FAIR_GROUP_SCHED

         struct sched_entity *parent;

         /* rq on which thisentity is (to be) queued: */

         struct cfs_rq             *cfs_rq;

         /* rq"owned" by this entity/group: */

         struct cfs_rq             *my_q;

#endif

};

被调度的对象有task_group和task两种,需要调度实体这一个抽象的结构来代表它们。如果调度实体代表task_group,则它的my_q字段指向这个调度组对应的运行队列;否则my_q字段为NULL。

在调度实体中与my_q相对的是X_rq(具体是针对普通进程的cfs_rq和针对实时进程的rt_rq),前者指向这个组自己的运行队列,里面会放入它的子节点;后者指向这个组的父节点的运行队列,也就是这个调度实体应该被放入的运行队列。

Parent用来将父子se关联起来,对于入队出队等场合有重要作用。


组调度实现
初始化

shares值其实就是对应se的weight值(普通任务中weight反映的是非实时任务优先级分配权重,见下表),cfs公平调度总是选择运行队列里vruntime最小的进程起来运行,其中vruntime +=  delta*NICE_0_LOAD/ se.weight,与weight成反比。因此,如果A组的shares值是B组shares的两倍的话, A组占有CPU时间是B组的二倍。

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

 

shares值的初始化:

sched_init->

init_task_group.shares =init_task_group_load;

# define INIT_TASK_GROUP_LOAD         NICE_0_LOAD

shares值的读写操作,代码如下

static intcpu_shares_write_u64(struct cgroup *cgrp, struct cftype *cftype,

                                     u64shareval)

{

         returnsched_group_set_shares(cgroup_tg(cgrp), shareval);

}

 

static u64 cpu_shares_read_u64(structcgroup *cgrp, struct cftype *cft)

{

         structtask_group *tg = cgroup_tg(cgrp);

 

         return(u64) tg->shares;

}

读就是返回了task_group的shares值。

写操作先调用cgroup_tg()将cgroup转换成task_group之后,再将用sched_group_set():

int sched_group_set_shares(structtask_group *tg, unsigned long shares)

{

         inti;

         unsignedlong flags;

 

         /*

          * We can't change the weight of the rootcgroup。

          */

/*设置的是最上层的task_group*/

         if(!tg->se[0])

                   return-EINVAL;

/*最小值为2*/

         if(shares < MIN_SHARES)

                   shares= MIN_SHARES;

         elseif (shares > MAX_SHARES)

                   shares= MAX_SHARES;

 

         mutex_lock(&shares_mutex);

/*要更改的值与之前值相同*/

         if(tg->shares == shares)

                   gotodone;

 

         spin_lock_irqsave(&task_group_lock,flags);

/*将当前的task_group中cpu运行队列的leaf_cfs_rq_list上断开*/

         for_each_possible_cpu(i)

                   unregister_fair_sched_group(tg,i);

/*将task_group从父层中断开*/

         list_del_rcu(&tg->siblings);

         spin_unlock_irqrestore(&task_group_lock,flags);

 

         /*wait for any ongoing reference to this group to finish */

         synchronize_sched();

 

         /*

          * Now we are free to modify the group's shareon each cpu

          * w/o tripping rebalance_share orload_balance_fair。

          */

         tg->shares = shares;

         for_each_possible_cpu(i){

                   /*

                    * force a rebalance

                    */

/*设置cfs_rq->share,强制进行一次SMP负载均衡操作*/

                   cfs_rq_set_shares(tg->cfs_rq[i],0);

/*更改task_group->se[]的share值*/

                   set_se_shares(tg->se[i],shares);

         }

 

         /*

          * Enable load balance activity on this group,by inserting it back on

          * each cpu's rq->leaf_cfs_rq_list。

          */

         spin_lock_irqsave(&task_group_lock,flags);

/*将task_group链入到cpu运行队列的leaf_cfs_rq_list*/

         for_each_possible_cpu(i)

                   register_fair_sched_group(tg,i);

/*添加到上层children链表*/

         list_add_rcu(&tg->siblings,&tg->parent->children);

         spin_unlock_irqrestore(&task_group_lock,flags);

done:

         mutex_unlock(&shares_mutex);

         return0;

}

这个函数的核心操作是在set_se_shares()中,如下所示:

static void __set_se_shares(structsched_entity *se, unsigned long shares)

{

         structcfs_rq *cfs_rq = se->cfs_rq;

         inton_rq;

 

         on_rq= se->on_rq;

         if(on_rq)

                   dequeue_entity(cfs_rq,se, 0);

 

         se->load.weight= shares;

         se->load.inv_weight= 0;

 

         if(on_rq)

                   enqueue_entity(cfs_rq,se, 0);

}

 

static void set_se_shares(structsched_entity *se, unsigned long shares)

{

         structcfs_rq *cfs_rq = se->cfs_rq;

         structrq *rq = cfs_rq->rq;

         unsignedlong flags;

 

         spin_lock_irqsave(&rq->lock,flags);

         __set_se_shares(se,shares);

         spin_unlock_irqrestore(&rq->lock,flags);

}

 

调度流程的修改

Pick任务流程修改

schedule

->pick_next_task

-> pick_next_task_fair

pick_next_task_fair()操作:

static struct task_struct*pick_next_task_fair(struct rq *rq)

{

         structtask_struct *p;

         structcfs_rq *cfs_rq = &rq->cfs;

         structsched_entity *se;

 

         if(unlikely(!cfs_rq->nr_running))

                   returnNULL;

 /*从最高层依次往下取到最合适的进程se

    *每一层的调度跟单个se的调度是一样的

    */

         do{

                   se= pick_next_entity(cfs_rq);

                   set_next_entity(cfs_rq,se);

                   cfs_rq= group_cfs_rq(se);

         }while (cfs_rq);

 

         p= task_of(se);

         hrtick_start_fair(rq,p);

 

         returnp;

}

其实它就是一个层次选取操作,组的se操作和单个任务的se的调度完全是一样的。

选择原则就是vruntime最小的任务运行,vruntime的维护就是下一节调度控制流程的内容。

假设最高层有二个组,3个进程,分组是组A,组B,进程p1,p2,p3,组A中又有二个进程,pa1,pa2。首先,在最顶层选取se,也就是在组A,组B,p1,p2,p3中选择,如果选择的是进程,假设是p1,那么就调度这个进程。如果选择的是组,假设是组A,那么再到组A中选,也就是说到pa1,pa2中选,依次选出一个合适的。

     cfs_rq为空表示选中了group里最终的任务,选中退出。

cfs组调度控制流程

1.在TICK中断调用

sched_class->task_tick_fair

在tick时钟中断,会调用task_tick_fair():

static void task_tick_fair(struct rq*rq, struct task_struct *curr, int queued)

{

         structcfs_rq *cfs_rq;

         structsched_entity *se = &curr->se;

 

         for_each_sched_entity(se){

                   cfs_rq= cfs_rq_of(se);

                   entity_tick(cfs_rq,se, queued);

         }

}

这个函数也很简单,就是从下到上依次更新各层的vruntime值。

#define for_each_sched_entity(se) \

                   for(; se; se = se->parent)

 

2.入队操作

调用链

sched_class->enqueue_task

->enqueue_task_fair

代码如下:

static void

enqueue_task_fair(struct rq *rq,struct task_struct *p, int wakeup, bool head)

{

         structcfs_rq *cfs_rq;

         structsched_entity *se = &p->se;

         intflags = 0;

 

         if(wakeup)

                   flags|= ENQUEUE_WAKEUP;

         if(p->state == TASK_WAKING)

                   flags|= ENQUEUE_MIGRATE;

 

         for_each_sched_entity(se){

                   if(se->on_rq)

                            break;

                   cfs_rq= cfs_rq_of(se);

                   enqueue_entity(cfs_rq,se, flags);

                   flags= ENQUEUE_WAKEUP;

         }

 

         hrtick_update(rq);

}

此时的for_each_sched_entity由下层开始依次入队,enqueue_entity会依次更新se的虚拟运行时间vruntime等。

从enqueue_task()的代码,我们可以看到,其实,每一层task_group都是它上层task_group.cfs_rq中的一个se,在同一层的cfs_rq中,不管它是task_group,还是task,它的调度是没有什么分别的。

 

3.出列操作

CFS中:

sched_class-> dequeue_task

-> dequeue_task_fair(),代码如下:

static void dequeue_task_fair(structrq *rq, struct task_struct *p, int sleep)

{

         structcfs_rq *cfs_rq;

         structsched_entity *se = &p->se;

 

         for_each_sched_entity(se){

                   cfs_rq= cfs_rq_of(se);

                   dequeue_entity(cfs_rq,se, sleep);/* 依次更新虚拟运行时间vruntime等操作*/

                   /*Don't dequeue parent if it has other entities besides us */

                   if(cfs_rq->load.weight)

                            break;

                   sleep= 1;

         }

 

         hrtick_update(rq);

}

 

从上面可以看出,进程出列,除了在本层cfs_rq中出列之外,还有可能将上层se出列,从代码中可以看到,是在cfs_rq->load.weight为0的时候。如果cfs_rq中有进程入队,其值增加,反之,其值减小。所在cfs_rq->load.weight为0表示本层没有需要调度的se了(本层所有进程都出列以及子层group中的进程也全部出列了)。

 

实时任务组调度分析

原理

实时进程是对CPU有着实时性要求的进程,它的优先级是跟具体任务相关的,完全由用户来定义的。调度器总是会选择优先级最高的实时进程来运行。

发展到组调度,组的优先级就被定义为“组内最高优先级的进程所拥有的优先级”。比如组内有三个优先级分别为10、20、30的进程,则组的优先级就是10(数值越小优先级越大)。

其实这样还是选择优先级最高的任务运行,其意义在于分组之后,可以对每个组的运行时间进行限制,可以有效弥补简单的FIFO和RR调度带来的缺失。

常见的使用场景,如含有图像、语音、网络等同时处理的在线影音等。

该功能通过/proc文件系统提供接口,接口如下:

/proc/sys/kernel/sched_rt_period_us:

  控制周期:以微秒为单位,该参数设置了定期进行检查的控制周期,缺省值为1000000微秒,即1秒。

/proc/sys/kernel/sched_rt_runtime_us:

运行时间:该接口设置实时任务在sched_rt_period_us周期里可以运行的时间,也以微秒为单位,缺省值为950000微秒,即0.95秒,当设置为负数,表示运行带宽控制功能被关闭,即实时任务可以独占CPU的运行。

同时每个task_group都有自己的sched_rt_runtime_us和sched_rt_period_us,保证自己组内的进程在以sched_rt_period_us为周期的时间内,最多只能运行sched_rt_runtime_us这么多时间。CPU占有比为sched_rt_runtime_us/sched_rt_period_us。

  对于根节点的task_group,它的sched_rt_runtime_us和sched_rt_period_us就等于上面两个proc文件中的值(见第一章节cpu)。而对于一个task_group节点来说,假设它下面有n个调度子组和m个TASK_RUNNING状态的进程,它的CPU占有比为A、这n个子组的CPU占有比为B,则B必须小于等于A,而A-B剩下的CPU时间将分给那m个TASK_RUNNING状态的进程。(这里讨论的是CPU占有比,因为每个调度组可能有着不同的周期值。)

数据结构

task_group结构体:

struct task_group {

 

#ifdef CONFIG_RT_GROUP_SCHED

         struct sched_rt_entity**rt_se;

         struct rt_rq **rt_rq;

 

         struct rt_bandwidthrt_bandwidth;

#endif

  /*用于链接到task_groups链表*/

   struct list_headlist;

 

/*指向上层task_group,根节点的task_group是最终的parent */

   struct task_group*parent;

/*链入到上层task_group的children链表*/

   struct list_headsiblings;

  /*用来链接下层的task_group */

   struct list_headchildren;

 

 

}

1.rt_se :tg所属的se;

2.rt_rq :tg所属的rt_rq;

3.rt_bandwidth :rt_bandwidth实时频宽控制结构体。

其中:实时带宽控制

struct rt_bandwidth {

         spinlock_t                  rt_runtime_lock;

         ktime_t                       rt_period;

         u64                     rt_runtime;

         struct hrtimer           rt_period_timer;

 };

1.rt_runtime_lock :主要用于本数据结构中其他数据的一个操作保护,保证各操作的原子性;

2.rt_period:tg任务控制周期参数;

3.rt_runtime:tg任务的运行时间额度;

4.rt_period_timer:为一个高精度定时器,其运行周期为rt_period。

 

struct rt_rq{

#if defined CONFIG_SMP || defined CONFIG_RT_GROUP_SCHED

         struct {

                   int curr; /*highest queued rt task prio */

#ifdef CONFIG_SMP

                   int next; /*next highest */

#endif

         } highest_prio;

#endif

         int rt_throttled;

         u64 rt_time;

         u64 rt_runtime;

 

}

highest_prio运行队列中最高与次高优先级

rt_throttled:该参数为实时任务运行队列的控制开关,当该参数为0时,表示实时任务运行额度没有超额;当该参数为1时,表示当前实时任务的运行已经超额,此时,系统调度器选择不了实时任务,只能选择非实时任务运行;

rt_time:该参数表示了实时任务队列上的各实时任务在控制周期里运行的累积时间;

rt_runtime:该参数表示了实时任务队列上的各实时任务在控制周期内可用的运行时间;

 

sched_rt_entity{

#ifdef CONFIG_RT_GROUP_SCHED

         struct sched_rt_entity    *parent;

         /* rq on which thisentity is (to be) queued: */

         struct rt_rq               *rt_rq;

         /* rq"owned" by this entity/group: */

         struct rt_rq               *my_q;

#endif

}

在调度实体中与my_q相对的是X_rq(具体是针对普通进程的cfs_rq和针对实时进程的rt_rq),前者指向这个组自己的运行队列,里面会放入它的子节点;后者指向这个组的父节点的运行队列,也就是这个调度实体应该被放入的运行队列。

至此调度实体和运行队列又组成了另一个树型结构,它的每一个非叶子节点都跟task_group的树型结构是相对应的,而叶子节点都对应到具体的task。

被调度的对象有task_group和task两种,需要调度实体这一个抽象的结构来代表它们。如果调度实体代表task_group,则它的my_q字段指向这个调度组对应的运行队列;否则my_q字段为NULL,调度实体代表task。就像非TASK_RUNNING状态的进程不会被放入运行队列一样,如果一个组中不存在TASK_RUNNING状态的进程,则这个组(对应的调度实体)也不会被放入它的上一级运行队列。

明确一点,只要调度组创建了,其对应的task_group就肯定存在于由task_group组成的树型结构中;而其对应的调度实体是否存在于由运行队列和调度实体组成的树型结构中,要取决于这个组中是否存在TASK_RUNNING状态的进程。

 

 

组调度实现
初始化

初始化tg任务的rt_b控制结构:

 

void init_rt_bandwidth(struct rt_bandwidth *rt_b, u64 period, u64runtime)

 

start_kernel()

|------>sched_init()

             |------>init_rt_bandwidth()

 

初始化使用系统默认值:

static inline u64global_rt_period(void)

{

         return(u64)sysctl_sched_rt_period * NSEC_PER_USEC;

}

 

static inline u64global_rt_runtime(void)

{

         if(sysctl_sched_rt_runtime < 0)

                   returnRUNTIME_INF;

 

         return(u64)sysctl_sched_rt_runtime * NSEC_PER_USEC;

}

 

void init_rt_bandwidth(structrt_bandwidth *rt_b, u64 period, u64 runtime)

{

         rt_b->rt_period= ns_to_ktime(period);

         rt_b->rt_runtime= runtime;

 

         spin_lock_init(&rt_b->rt_runtime_lock);

 

         hrtimer_init(&rt_b->rt_period_timer,

                            CLOCK_MONOTONIC,HRTIMER_MODE_REL);

         rt_b->rt_period_timer.function= sched_rt_period_timer;

}

tg任务控制定时器初始化:

static void start_rt_bandwidth(struct rt_bandwidth *rt_b)

sched_class->enqueue_task()

|-->enqueue_task()

|-->enqueue_task_rt()

|-->enqueue_rt_entity()

|-->__enqueue_rt_entity()

|-->inc_rt_tasks()

|-->inc_rt_group()

|-->start_rt_bandwidth()

从该调用点的调用链可以看出,在每一个实时任务进入运行队列时,都会去主动检查并启动实时任务运行控制的周期定时器。检查指定实时任务运行带宽控制变量的定时器是否为活动的;如果为活动的,直接退出;如果不为活动的,则将其启动;

rt_rq中rt_time初始化

kernel_init

sched_init_smp

update_runtime

enable_runtime

__enable_runtime

static void __enable_runtime(structrq *rq)

{

         structrt_rq *rt_rq;

 

         if(unlikely(!scheduler_running))

                   return;

 

         /*

          * Reset each runqueue's bandwidth settings

          */

         for_each_leaf_rt_rq(rt_rq,rq) {

                   structrt_bandwidth *rt_b = sched_rt_bandwidth(rt_rq);

 

                   spin_lock(&rt_b->rt_runtime_lock);

                   spin_lock(&rt_rq->rt_runtime_lock);

                   rt_rq->rt_runtime= rt_b->rt_runtime;

                   rt_rq->rt_time= 0;

                   rt_rq->rt_throttled= 0;

                   spin_unlock(&rt_rq->rt_runtime_lock);

                   spin_unlock(&rt_b->rt_runtime_lock);

         }

}

 

cgroup接口操作初始化:

cpu_rt_runtime_write

sched_group_set_rt_runtime

tg_set_bandwidth

中对tg任务的rt_b控制结构和rt_rq中rt_runtime初始化

 

cpu_rt_period_write_uint

sched_group_set_rt_period

tg_set_bandwidth

中对tg任务的rt_b控制结构和rt_rq中rt_period初始化

static struct cftype cpu_files[] = {

#ifdef CONFIG_RT_GROUP_SCHED

         {

                   .name= "rt_runtime_us",

                   .read_s64= cpu_rt_runtime_read,

                   .write_s64= cpu_rt_runtime_write,

         },

         {

                   .name= "rt_period_us",

                   .read_u64= cpu_rt_period_read_uint,

                   .write_u64= cpu_rt_period_write_uint,

         },

#endif

};

 

调度流程的改造

修改了_pick_next_task_rt

调用链:

sched_class -> pick_next_task ()

|--> pick_next_task_rt ()

|--> _pick_next_task_rt ()

 

static struct task_struct*_pick_next_task_rt(struct rq *rq)

{

         structsched_rt_entity *rt_se;

         structtask_struct *p;

         structrt_rq *rt_rq;

 

         rt_rq= &rq->rt;

 

         if(unlikely(!rt_rq->rt_nr_running))

                   returnNULL;

 

         if (rt_rq_throttled(rt_rq))

                   returnNULL;

 

         do {

                   rt_se =pick_next_rt_entity(rq, rt_rq);

                   BUG_ON(!rt_se);

                   rt_rq =group_rt_rq(rt_se);

         } while (rt_rq);

 

         p= rt_task_of(rt_se);

         p->se.exec_start= rq->clock_task;

 

         returnp;

}

这里判断rt_rq->rt_throttled是否被设置,如果被设置,则直接返回;否则,进行最高优先级实时任务的选择。

循环查找知道找到所调度任务的rt_se,这里rt_rq不为空表示是一个tg。

可见系统中每秒钟内实时进程的运行时间不超过0.95秒。如果实时进程实际对CPU的需求不足0.95秒(大于等于0秒、小于0.95秒),则剩下的时间都会分配给普通进程。而如果实时进程的对CPU的需求大于0.95秒,它也只能够运行0.95秒,剩下的0.05秒会分给其他普通进程。但是,如果这0.05秒内没有任何普通进程需要使用CPU(一直没有TASK_RUNNING状态的普通进程)呢?这种情况下既然普通进程对CPU没有需求,实时进程是否也不可以运行超过0.95秒。

rt组调度控制流程

Tg  throttled流程

设置点:

1. 实时任务退出运行队列时调用

sched_class -> dequeue_task ()

|--> dequeue_task_rt ()

|--> update_curr_rt ()

   |--> sched_rt_runtime_exceeded()

 

2. 在实时任务中进入调度器时被调用

sched_class -> put_prev_task ()

|--> put_prev_task_rt ()

|--> update_curr_rt ()

   |--> sched_rt_runtime_exceeded()

3. 在TICK中断里被调用

sched_class -> task_tick ()

|-->task_tick_rt ()

|--> update_curr_rt()

   |--> sched_rt_runtime_exceeded()

 

static int sched_rt_runtime_exceeded(struct rt_rq *rt_rq)

static intsched_rt_runtime_exceeded(struct rt_rq *rt_rq)

{

         u64runtime = sched_rt_runtime(rt_rq);

 

         if(rt_rq->rt_throttled)

                   returnrt_rq_throttled(rt_rq);

 

         if(sched_rt_runtime(rt_rq) >= sched_rt_period(rt_rq))

                   return0;

 

         balance_runtime(rt_rq);

         runtime= sched_rt_runtime(rt_rq);

         if(runtime == RUNTIME_INF)

                   return0;

 

         if (rt_rq->rt_time> runtime) {

                   rt_rq->rt_throttled= 1;

                   if(rt_rq_throttled(rt_rq)) {

                            sched_rt_rq_dequeue(rt_rq);

                            return1;

                   }

         }

 

         return0;

}

该函数的处理流程为:

1、  balance_runtime用于均衡。

2、rt_rq->rt_time(本周期已运行时间)是否大于rt_rq->rt_runtime(可运行时间),如果小于,直接返回0值;如果大于,进入下一步;

3、大于,则设置rt_rq-> rt_throttled,即意味着实时任务需要让出CPU,返回1值。

balance_runtime用于本cpu上的rt_rq->rt_time > rt_rq->rt_runtimedo_balance_runtime在是多核情况下对组任务限制的优化,后面有详细描述。

当本函数返回1值时,其上层调用函数,如update_curr_rt(),应该要设置调度标志,及时的启动调度。

 

static void update_curr_rt(struct rq*rq)

{

         structtask_struct *curr = rq->curr;

         structsched_rt_entity *rt_se = &curr->rt;

         structrt_rq *rt_rq = rt_rq_of_se(rt_se);

         u64delta_exec;

 

         if(!task_has_rt_policy(curr))

                   return;

 

         delta_exec= rq->clock_task - curr->se.exec_start;

         if(unlikely((s64)delta_exec < 0))

                   delta_exec= 0;

 

         schedstat_set(curr->se.exec_max,max(curr->se.exec_max, delta_exec));

 

         curr->se.sum_exec_runtime+= delta_exec;

         account_group_exec_runtime(curr,delta_exec);

 

         curr->se.exec_start= rq->clock_task;

         cpuacct_charge(curr,delta_exec);

 

         sched_rt_avg_update(rq,delta_exec);

 

         if(!rt_bandwidth_enabled())

                   return;

 

         for_each_sched_rt_entity(rt_se){

                   rt_rq= rt_rq_of_se(rt_se);

 

                   if(sched_rt_runtime(rt_rq) != RUNTIME_INF) {

                            spin_lock(&rt_rq->rt_runtime_lock);

                            rt_rq->rt_time += delta_exec;

                            if(sched_rt_runtime_exceeded(rt_rq))

                                     resched_task(curr);/*让出CPU*/

                            spin_unlock(&rt_rq->rt_runtime_lock);

                   }

         }

}

 

for_each_sched_rt_entity(rt_se) 用来由下向上更新含有rt_se的父节点的rt_rq->rt_time

rt_rq->rt_time += delta_exec;后进行超时判断。

 

 

 

Tg  去throttled流程:

控制周期定时器处理函数为:

static enum hrtimer_restart sched_rt_period_timer(struct hrtimer*timer)

该函数是在控制周期到的时候被调用

static enum hrtimer_restartsched_rt_period_timer(struct hrtimer *timer)

{

         structrt_bandwidth *rt_b =

                   container_of(timer,struct rt_bandwidth, rt_period_timer);

         ktime_tnow;

         intoverrun;

         intidle = 0;

 

         for(;;) {

                   now= hrtimer_cb_get_time(timer);

/*设置下一次到时时间,overrun表示到时间隔period 个数*/

                   overrun= hrtimer_forward(timer, now, rt_b->rt_period);

 

                   if(!overrun)

                            break;

 

                   idle= do_sched_rt_period_timer(rt_b, overrun);

         }

 

         returnidle ? HRTIMER_NORESTART : HRTIMER_RESTART;

}

该函数的处理流程为:

1.      检查当前实时任务的总运行时间rt_rq ->rt_time是否为0;

2.      如果为0,且当前rt_rq上的可运行实时任务个数也为0,则返回HRTIMER_NORESTART,即关闭本控制周期定时器(在以后有实时任务运行时,会自动再开启);否则,继续下一步;

3.      更新当前的rt_rq->rt_time(减去[rt_rq->rt_time, rt_rq->rt_runtime]两者之间的较小值),检查更新后的rt_rq->rt_time是否小于rt_rq->rt_runtime,且rt_rq->rt_throttled是否被设置;如果条件不满足,直接到下一步;如果满足,则将rt_rq->rt_throttled清0,并将设置调度标志,方便接下来可以切换到实时任务;

4.      返回HRTIMER_RESTART,让本定时器继续运行;

 

static intdo_sched_rt_period_timer(struct rt_bandwidth *rt_b, int overrun)

{

         inti, idle = 1;

         conststruct cpumask *span;

/*没有使能rt频宽控制或者频宽不受限,退出*/

         if(!rt_bandwidth_enabled() || rt_b->rt_runtime == RUNTIME_INF)

                   return1;

 

         span= sched_rt_period_mask();

         for_each_cpu(i,span) {

                   intenqueue = 0;

                   structrt_rq *rt_rq = sched_rt_period_rt_rq(rt_b, i);

                   structrq *rq = rq_of_rt_rq(rt_rq);

 

                   spin_lock(&rq->lock);

/*如果为0表示rt运行队列尚无任务运行*/

                   if(rt_rq->rt_time) {

                            u64runtime;

 

                            spin_lock(&rt_rq->rt_runtime_lock);

/*启动一次核间均衡,减少切换等开销*/

                            if(rt_rq->rt_throttled)

                                     balance_runtime(rt_rq);

/* rt_runtime 表示rt_rq 期望理想运行时间,rt_time表示实际运行时间*/

                            runtime= rt_rq->rt_runtime;

/*每period 可运行runtime ,故overrun 个period 可运行rt_time ,注意rt_rq->rt_time不可为负数*/

                            rt_rq->rt_time-= min(rt_rq->rt_time, overrun*runtime);

/*满足再次运行条件unthrottled!*/

                            if(rt_rq->rt_throttled && rt_rq->rt_time < runtime) {

/* rt_throttled= 1,rt_rq对应的调度实体再次进入运行队列*/      

                                     rt_rq->rt_throttled= 0;

                                     enqueue= 1;

                            }

/*无需重启定时器*/

                            if(rt_rq->rt_time || rt_rq->rt_nr_running)

                                     idle= 0;

                            spin_unlock(&rt_rq->rt_runtime_lock);

/*无需重启定时器*/

                   }else if (rt_rq->rt_nr_running)

                            idle= 0;

/*其他情况重启定时器*/

                   if(enqueue)

                            sched_rt_rq_enqueue(rt_rq);

                   spin_unlock(&rq->lock);

         }

 

         returnidle;

}

 

最后还有多CPU的问题,前面也提到,对于每一个task_group,它的调度实体和运行队列是每CPU维护一份的。而sched_rt_runtime_us和sched_rt_period_us是作用在调度实体上的,所以如果系统中有N个CPU,实时进程实际占有CPU的上限是N*sched_rt_runtime_us/sched_rt_period_us。也就是说,尽管默认情况下限制了每秒钟之内,实时进程只能运行0.50秒。但是对于某个实时进程来说,如果CPU有两个核,也还是能满足它100%占有CPU的需求的(比如执行死循环)。然后,按道理说,这个实时进程占有的100%的CPU应该是由两部分组成的(每个CPU占有一部分,但都不超过50%)。但是实际上,为了避免进程在CPU间的迁移导致上下文切换、缓存失效等一系列问题,一个CPU上的调度实体可以向另一个CPU上对应的调度实体借用时间。其结果就是,宏观上既满足了sched_rt_runtime_us的限制,又避免了进程的迁移。

对于static intdo_balance_runtime(struct rt_rq *rt_rq)

static int do_balance_runtime(structrt_rq *rt_rq)

{

         structrt_bandwidth *rt_b = sched_rt_bandwidth(rt_rq);

         structroot_domain *rd = cpu_rq(smp_processor_id())->rd;

         inti, weight, more = 0;

         u64rt_period;

 

         weight= cpumask_weight(rd->span);

 

         spin_lock(&rt_b->rt_runtime_lock);

         rt_period= ktime_to_ns(rt_b->rt_period);

         for_each_cpu(i,rd->span) {

/* sched_rt_period_rt_rq用来找到在另一个cpu上运行的同组任务*/

                   structrt_rq *iter = sched_rt_period_rt_rq(rt_b, i);

                   s64diff;

/*本运行队列,跳过*/

                   if(iter == rt_rq)

                            continue;

 

                   spin_lock(&iter->rt_runtime_lock);

                   /*

                    * Either all rqs have inf runtime and there'snothing to steal

                    * or __disable_runtime() below sets a specificrq to inf to

                    * indicate its been disabled and disalowstealing.

                    */

/*不受限任务无需借用时间*/

                   if(iter->rt_runtime == RUNTIME_INF)

                            gotonext;

 

                   /*

                    * From runqueues with spare time, take 1/npart of their

                    * spare time, but no more than our period。

                    */

/*最大能借用时间*/

                   diff= iter->rt_runtime - iter->rt_time;

                   if(diff > 0) {

                            diff= div_u64((u64)diff, weight);

/*修正后可借用时间*/

                            if(rt_rq->rt_runtime + diff > rt_period)

                                     diff= rt_period - rt_rq->rt_runtime;

                            iter->rt_runtime-= diff;

                            rt_rq->rt_runtime+= diff;

                            more= 1;

/*满足条件退出,不满足继续向其他cpu借用*/

                            if(rt_rq->rt_runtime == rt_period) {

                                     spin_unlock(&iter->rt_runtime_lock);

                                     break;

                            }

                   }

next:

                   spin_unlock(&iter->rt_runtime_lock);

         }

         spin_unlock(&rt_b->rt_runtime_lock);

 

         returnmore;

}

 

 

 原文链接:http://blog.csdn.net/u014358116/article/details/22754347

作者:爱海tatao 
[ 转载请保留原文出处、作者和链接。]

你可能感兴趣的:(cgroups分析与应用连载(二))