Chinaunix首页 | 论坛 | 博客
  • 博客访问: 333068
  • 博文数量: 102
  • 博客积分: 2510
  • 博客等级: 少校
  • 技术积分: 1146
  • 用 户 组: 普通用户
  • 注册时间: 2010-01-21 22:33
文章分类

全部博文(102)

文章存档

2011年(8)

2010年(94)

我的朋友

分类: LINUX

2010-01-24 21:50:20

组调度

先说一下提纲

1 关于group schedule的文档
2 关于组调度的认识
3 一些与组调度相关的数组结构
4 组调度结构图
5 具体的操作函数 
6 后记


1   按照文档的解释,完全公平调度并不仅仅针对单一进程,也应该对组调度。例如有两 个用户wxc1,wxc2,以用户userid来分,调度器分配给两 个组的时间是公平的,wxc1 and wxc2各有50%的CPU,,组内也实现公平调度。

几个宏:
CONFIG_GROUP_SCHED :打开组调度

CONFIG_RT_GROUP_SCHED 实时进程组调度

CONFIG_FAIR_GROUP_SCHED 普通进程。
组的划分:
1)Based on user id (CONFIG_USER_SCHED)
2)Based on "cgroup" pseudo filesystem (CONFIG_CGROUP_SCHED)
后面这一种还不太熟悉,参照eric xiao 网友的blog,在对cgroup文件系统进程操作时,会去调用一个接口,里面的函数完成对组调度的操作。
cgroup这部分后面再学习,暂时还不了解。

2 关于组调度的认识
1)组调度是分层次的。
2)一个组里面有数个进程,这些进程的调度作为一层。
3)所有的组都是连在一起的,他们之前有父子兄弟关系。为首的是root-task-group
4)另一层是组调度,即把一个组作为一个调度实 体,仍然用schedule_entity这个结构体来表示。
5)一个task_group在每个cpu上都关联着一个cfs_rq,一个schedule_entity,注意,这两 个东东是与之前单个进程调度里提到的cfs_rq & schedule_entity有分别的。。它们代表得是组。跟之前单个进程不一样。
6)每个cpu的run queue里有个链表,把所有在这个cpu上属于某些组的cfs_rq链在了一起。
7) 组与组之间的父子关系,同时代表着组在某个cpu上的调度实体之间的父子关系。假如说wxc1是wxc2组的parent,那个wxc1 在cpu0上的调度实体schedu_entity 是wxc2在cpu0上调度实体的父亲,这个一会在结构图中可以看到。
8) 每个调度实体都会指向一个它它所属的cfs_rq,同时还有一个它所在的cfs_rq.对于代表组的调度实体来说,它的cfs_rq是其parent所在组的cfs_rq,而它自己的cfs_rq,用my_q指向,是它所在的组的cfs_rq

3 一些与组调度相关的数组结构
1) 结构体组:
/* task group related information */
struct task_group {
#ifdef CONFIG_CGROUP_SCHED
    struct cgroup_subsys_state css;
#endif

#ifdef CONFIG_FAIR_GROUP_SCHED
    /* schedulable entities of this group on each cpu */
    struct sched_entity **se;每个cpu上都有一个调度实体,代表这个组,不是单个task的调度实体
    /* runqueue "owned" by this group on each cpu */
    struct cfs_rq **cfs_rq;每个cpu上都有一个cfs_rq,属于这个组的
    unsigned long shares;
#endif

#ifdef CONFIG_RT_GROUP_SCHED
    struct sched_rt_entity **rt_se;
    struct rt_rq **rt_rq;

    struct rt_bandwidth rt_bandwidth;
#endif

    struct rcu_head rcu;
    struct list_head list;

    struct task_group *parent;  这三个代表着组与组之间的亲戚
    struct list_head siblings;
    struct list_head children;
};


2) struct rq{
...
struct list_head leaf_cfs_rq_list; 用来链接在此cpu上所有组的cfs_rq
...
}
 3) 
struct cfs_rq {

...
#ifdef CONFIG_FAIR_GROUP_SCHED
    //Note here: cfs has rq pointer when group schedule
    struct rq *rq;    /* cpu runqueue to which this cfs_rq is attached */   指向一个run queue

    /*
     * leaf cfs_rqs are those that hold tasks (lowest schedulable entity in
     * a hierarchy). Non-leaf lrqs hold other higher schedulable entities
     * (like users, containers etc.)
     *
     * leaf_cfs_rq_list ties together list of leaf cfs_rq's in a cpu. This
     * list is used during load balance.
     */
    struct list_head leaf_cfs_rq_list;             链表,把一个runqueue上的所有cfs_rq链接在一起
    struct task_group *tg;    /* group that "owns" this runqueue */    哪个group来的?
...
}
4) struct sched_entity {
。。。
#ifdef CONFIG_FAIR_GROUP_SCHED
    struct sched_entity    *parent;    也有父亲了~以前是野孩子   其父亲一般是其所在group的父亲的在同样cpu上的调度实体
    /* rq on which this entity is (to be) queued: */
    struct cfs_rq        *cfs_rq;  这两 个cfs_rq挺绕的:cfs_rq这个指针指向的是parent->my_q
    /* rq "owned" by this entity/group: */
    struct cfs_rq        *my_q;自己的cfs_rq
#endif
};

4 组调度结构图
请参照附件,手工画得,因为不会用做图软件   汗~

右上角部分:两 个组之前是父子关系  每个组旁边的三角形是代表组的调度实体

5 具体的操作函数

先看两 个组:
1) /* Default task group.
 *    Every task in system belong to this group at bootup.
 */
struct task_group init_task_group;
2) /*
 * Root task group.
 *     Every UID task group (including init_task_group aka UID-0) will
 *     be a child to this group.
 */
init_task_group是系统初始化时进程的所在组。
root_task_group 是所有group 的祖宗。
在sched_init()里面,有对它的初始化过程。
sched_init(){
.....
#ifdef CONFIG_FAIR_GROUP_SCHED
        init_task_group.se = (struct sched_entity **)ptr;
        ptr += nr_cpu_ids * sizeof(void **);请留意这儿,  ptr加一个偏移,这段空间内存放着在所有cpu上的代表组的调度实体的指针。

        init_task_group.cfs_rq = (struct cfs_rq **)ptr;
        ptr += nr_cpu_ids * sizeof(void **);

#ifdef CONFIG_USER_SCHED
        root_task_group.se = (struct sched_entity **)ptr;
        ptr += nr_cpu_ids * sizeof(void **);

        root_task_group.cfs_rq = (struct cfs_rq **)ptr;
        ptr += nr_cpu_ids * sizeof(void **);
#endif /* CONFIG_USER_SCHED */
...
}
由于page_alloc还没有建立,所以用alloc_bootmem(),上面的过程建立了一个task-group里面的**se,**cfs_rq部分。。。即两 个指针数组。
留意这句:
    init_task_group.parent = &root_task_group;
可见init_task_group的父亲也是root_task_group.

接着往下,有一个对每个cpu的注册过程,这个过程和一个组创建并注册的过程类似,简单分析下。
这个过程只简我感兴趣的分析:
for_each_possible_cpu(i) {
        struct rq *rq;

        rq = cpu_rq(i);
        spin_lock_init(&rq->lock);
        rq->nr_running = 0;
        init_cfs_rq(&rq->cfs, rq);初始化运行列队自己的cfs_rq,不是组的哦~
        init_rt_rq(&rq->rt, rq);
#ifdef CONFIG_FAIR_GROUP_SCHED
        init_task_group.shares = init_task_group_load;
        INIT_LIST_HEAD(&rq->leaf_cfs_rq_list);
#ifdef CONFIG_CGROUP_SCHED
。。。

        init_tg_cfs_entry(&init_task_group, &rq->cfs, NULL, i, 1, NULL);   这个函数很重要。。。把initi_task_group,和 运行列队的cfs_rq绑定。
#elif defined CONFIG_USER_SCHED
        root_task_group.shares = NICE_0_LOAD;
        init_tg_cfs_entry(&root_task_group, &rq->cfs, NULL, i, 0, NULL);同理。
。。。


    init_tg_cfs_entry(&init_task_group,
                &per_cpu(init_cfs_rq, i),
                &per_cpu(init_sched_entity, i), i, 1,
                root_task_group.se);
。。

}

init_tg_cfs_entry()这个函数做了许多好事,提出表扬。
急不可奈地先看看这厮到底做了啥捏:

#ifdef CONFIG_FAIR_GROUP_SCHED
static void init_tg_cfs_entry(struct task_group *tg, struct cfs_rq *cfs_rq,
                struct sched_entity *se, int cpu, int add,
                struct sched_entity *parent)   这几个参数分别是:要初始化的组 + 要初始化的cfs_rq + 要初始化的se + CPU + 是否把cfs挂在rq上 + 代表组的父亲的调度实体
{
    struct rq *rq = cpu_rq(cpu);
    tg->cfs_rq[cpu] = cfs_rq;先把指针真满
    init_cfs_rq(cfs_rq, rq);把它和运行队列关链一下。 下面我们会分析这个初始化做什么事情
    cfs_rq->tg = tg;从这两 句我感觉到,指针真是个好东西啊:随意抽像出来一个玩意儿,就可以做为一个接口,把两 个东西绑在一起,让他们有关系。。。这里它就绑定了组和rq
    if (add)
        list_add(&cfs_rq->leaf_cfs_rq_list, &rq->leaf_cfs_rq_list);
    tg->se[cpu] = se;也把se放进指针数组
    /* se could be NULL for init_task_group */
    if (!se)
        return;

    if (!parent)   这儿有意思:没有父亲的话,se指向的cfs_rq就是当前运行队列的cfs_rq,这个条件在初始化“根组”和初”始化组“时成立。
        se->cfs_rq = &rq->cfs;
    else
        se->cfs_rq = parent->my_q;  否则呢,就指向父亲所拥有的cfs_rq

    se->my_q = cfs_rq;然后把自己所在组的cfs_rq作为自己的队列
    se->load.weight = tg->shares;   共享组的load值。
    se->load.inv_weight = 0;
    se->parent = parent;  亲吻下自己的父母
}
#endif

看看上面那个如何把cfs_rq和运行队列关联?
static void init_cfs_rq(struct cfs_rq *cfs_rq, struct rq *rq)
{
    cfs_rq->tasks_timeline = RB_ROOT;  初始化下自己的红黑结点
    INIT_LIST_HEAD(&cfs_rq->tasks);
#ifdef CONFIG_FAIR_GROUP_SCHED
    cfs_rq->rq = rq;  我能找到你,你也能找到我,这样才是稳固的关系。。。。
#endif
    cfs_rq->min_vruntime = (u64)(-(1LL << 20));  默认一个cfs_rq的最左结点的值
}

上面这些分析大体代表了“根组”和“初始化组”在系统初始化的过程中相关流程。

来看一个组怎么被创建的吧。
/* allocate runqueue etc for a new task group */
struct task_group *sched_create_group(struct task_group *parent)  parent这个参数一般在创建的时候会指定,比如是上一个进程组,没有的话可能是前面提到的那两 个组
{
    struct task_group *tg;
    unsigned long flags;
    int i;

    tg = kzalloc(sizeof(*tg), GFP_KERNEL);   申请组结构体的内存空间
    if (!tg)
        return ERR_PTR(-ENOMEM);

    if (!alloc_fair_sched_group(tg, parent))   恩,这个比较好玩儿,申请那个指针数组,一会儿看,
        goto err;

    if (!alloc_rt_sched_group(tg, parent))
        goto err;

    spin_lock_irqsave(&task_group_lock, flags);
    for_each_possible_cpu(i) {  把申请到的组向每一个cpu注册
        register_fair_sched_group(tg, i);  
        register_rt_sched_group(tg, i);
    }
    list_add_rcu(&tg->list, &task_groups);

    WARN_ON(!parent); /* root should already exist */

    tg->parent = parent;  再亲吻一下自己的父母
    INIT_LIST_HEAD(&tg->children);
    list_add_rcu(&tg->siblings, &parent->children);
    spin_unlock_irqrestore(&task_group_lock, flags);

    return tg;

err:
    free_sched_group(tg);
    return ERR_PTR(-ENOMEM);
}

这个流程不审很清晰的。。。。申请空间-->申请指针数组-->向cpu的运行队列注册

看看这两 个函数做什么。
static
int alloc_fair_sched_group(struct task_group *tg, struct task_group *parent)
{
    struct cfs_rq *cfs_rq;
    struct sched_entity *se, *parent_se;
    struct rq *rq;
    int i;

    tg->cfs_rq = kzalloc(sizeof(cfs_rq) * nr_cpu_ids, GFP_KERNEL);
    if (!tg->cfs_rq)
        goto err;
    tg->se = kzalloc(sizeof(se) * nr_cpu_ids, GFP_KERNEL);
    if (!tg->se)
        goto err;
上面两 步不难,申请指向每个cpu的cfs_rq 和 se数组

    tg->shares = NICE_0_LOAD;

    for_each_possible_cpu(i) {  向每一个cpu注册下申请的cfs_rq 和 se结构体。
        rq = cpu_rq(i);

        cfs_rq = kmalloc_node(sizeof(struct cfs_rq),
                GFP_KERNEL|__GFP_ZERO, cpu_to_node(i));
        if (!cfs_rq)
            goto err;

        se = kmalloc_node(sizeof(struct sched_entity),
                GFP_KERNEL|__GFP_ZERO, cpu_to_node(i));
        if (!se)
            goto err;

        parent_se = parent ? parent->se : NULL;   判断当前组有父亲么?有的话其父亲在此cpu上的调度实体即为它的调度实体的父亲。。真拗口
        init_tg_cfs_entry(tg, cfs_rq, se, i, 0, parent_se);这个是之前我们分析过的,注意其参数
    }

    return 1;

 err:
    return 0;
}
上面这个申请函数过程还是蛮顺利的。
下面看看这个注册更简单,它就是把申请到的cfs_rq挂到运行队列的链表上。。。

这样一个组的创建过程分析完了。


最后再分析一个进程在不同组之间移动的情况:

/* change task's runqueue when it moves between groups.
 *    The caller of this function should have put the task in its new group
 *    by now. This function just updates tsk->se.cfs_rq and tsk->se.parent to
 *    reflect its new group.
 */
void sched_move_task(struct task_struct *tsk)
{
    int on_rq, running;
    unsigned long flags;
    struct rq *rq;

    rq = task_rq_lock(tsk, &flags);   如果是单进程调度,返回的是运行队列的cfs_rq结构体,否则返回调度实体指向的cfs_rq

    update_rq_clock(rq);

    running = task_current(rq, tsk);
    on_rq = tsk->se.on_rq;

    if (on_rq)
        dequeue_task(rq, tsk, 0);  如果还在列上,出列吧,要移走了嘛
    if (unlikely(running))不希望它还在运行,如果是的话,就让它停止? 或者重新入列?
        tsk->sched_class->put_prev_task(rq, ts)

    set_task_rq(tsk, task_cpu(tsk));  设置新的运行队列  下面有分析

#ifdef CONFIG_FAIR_GROUP_SCHED
    if (tsk->sched_class->moved_group)
        tsk->sched_class->moved_group(tsk); 下面有分析这个函数
#endif

    if (unlikely(running))
        tsk->sched_class->set_curr_task(rq);  试着让它运行 。。。这个函数的逻辑也没看懂
    if (on_rq)
        enqueue_task(rq, tsk, 0);  正式入列    下面分析它

    task_rq_unlock(rq, &flags);
}
#endif 

这个流程的逻辑我是比较晕的,我搞不清楚为什么会这么做?
当一个进程从一个组移到另一个组的时候,这些操作具体的逻辑我不太懂,,需要再看下组调度理论性的东西。

set_task_rq将重新设置一个task的运行列队:
/* Change a task's cfs_rq and parent entity if it moves across CPUs/groups */
static inline void set_task_rq(struct task_struct *p, unsigned int cpu)   这儿设置的可是一个进程哦,一个进程的调度实体。。。与代表组的调度实体要分清楚
{
#ifdef CONFIG_FAIR_GROUP_SCHED
    p->se.cfs_rq = task_group(p)->cfs_rq[cpu];
    p->se.parent = task_group(p)->se[cpu];
#endif

#ifdef CONFIG_RT_GROUP_SCHED
    p->rt.rt_rq  = task_group(p)->rt_rq[cpu];
    p->rt.parent = task_group(p)->rt_se[cpu];
#endif
}

这样我们大体有个概念了。。。在组高度中,一个组内的进程的父亲都是代表这个组的调度实体。它们指向的cfs_rq就是当前组所在这个cpu上拥有的cfs_rq

下面这个函数守成了进程移到新的组的珠一些信息的更新
#ifdef CONFIG_FAIR_GROUP_SCHED
static void moved_group_fair(struct task_struct *p)
{
    struct cfs_rq *cfs_rq = task_cfs_rq(p);

    update_curr(cfs_rq);
    place_entity(cfs_rq, &p->se, 1);
}
#endif

最后我们看这个入列的函数吧:
static void enqueue_task_fair(struct rq *rq, struct task_struct *p, int wakeup)
{
    struct cfs_rq *cfs_rq;
    struct sched_entity *se = &p->se;

    for_each_sched_entity(se) {这个for还是很有趣
        if (se->on_rq)
            break;
        cfs_rq = cfs_rq_of(se);  取得其cfs_rq  非组调度时,返回当前运行队列的cfs_rq,组调度时,返回的是se将要调度到的cfs_rq
        enqueue_entity(cfs_rq, se, wakeup);  入列了  可能要初始化时间啊之类的
        wakeup = 1;
    }

    hrtick_update(rq);
}
看看for循环:
#define for_each_sched_entity(se) \
        for (; se; se = se->parent)
这个在入列出列时经常遇到。如果打开了组调度,就是上面这样。
举例,组A里的一个进程a要加入到组B在运行列队cpu0上的cfs_rq。这时进程的se->cfs_rq应该指向的时在cpu0上的组B的cfs_rq。
首先执行一些出队入队操作。当这个环节完了后,还在对组A这个调度实体进行更新,这样往上遍历,把所有组A的父亲们遍历一次,都执行了入列操作。

为什么这么做呢?  考虑中~

后记
上面这些是我看组调度中想到的一部分。。。。后面看完cgroup调度,应该会更有意思的。

                                     (end)
阅读(1561) | 评论(1) | 转发(0) |
给主人留下些什么吧!~~

ruffer2013-04-11 02:53:04

遍历一遍是因为 维护cfs队列的需要,如果这个task对应的task group 也还没有on_rq,需要插入队列了,