zoukankan      html  css  js  c++  java
  • 调度器13—p->on_rq 和 se->on_rq 分析

    基于MTK linux-4.14

    看代码过程中发现 put_prev_entity() 中判断 prev 的 se->on_rq 为真还执行 enqueue 操作,感到疑惑,追踪一下代码进行分析。

    1. 相关代码段

    __schedule(bool preempt)
    {
        /* 非抢占且非running状态,表示 prev 任务自己因为休眠主动放弃cpu的 */
        if (!preempt && prev->state) {
            deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK); //只有非抢占状态下才有执行,若是被抢占的,没有执行
                dequeue_entity() {
                    if (se != cfs_rq->curr) //此调用路径不成立,正在running的任务是已经dequeue的,在选prev时pick_next_task_fair()中已经dequeue过了。
                        __dequeue_entity(cfs_rq, se);
                    se->on_rq = 0; //也就是说睡眠导致被切换的任务其 se->on_rq 才会被设置为0 ######
                }
            prev->on_rq = 0; //也就是说睡眠导致被切换的任务其 task->on_rq 才会被设置为0 ######
        }
        ...
        next = pick_next_task(rq, prev, &rf);
        
        /* 下面就直接 switch 了,没有on_rq相关内容了 */
        if (likely(prev != next)) {
            ...
            rq->curr = next; //switch 的前一时刻才对 rq->curr 进行更新 ######
            rq = context_switch(rq, prev, next, &rf);
        }
    }

    dequeue_entity 中将 se->on_rq = 0,其常规调用路径如下,而抢占切换路劲下是没有对prev任务调用 deactivate_task 的。

    __schedule
        deactivate_task
            dequeue_task //调度类的这个回调
                dequeue_task_fair //几乎是唯一调用路径
                    dequeue_entity
                        se->on_rq = 0;

    若是在CFS任务之间切换,pick_next_task 调用的就是 pick_next_task_fair:

    static struct task_struct *pick_next_task_fair(struct rq *rq, struct task_struct *prev, struct rq_flags *rf)
    {
        struct sched_entity *curr = cfs_rq->curr;
    
        /*
         * 由于我们在没有执行 put_prev_entity() 的情况下到达这里(这个函数的下面才执行),
         * 我们还需要考虑 cfs_rq->curr。 如果它仍然是一个runnable的实体,update_curr()
         * 将更新它的 vruntime,否则忘记我们见过它。
         */
        if (curr) {
            if (curr->on_rq) //此调度实体还在cfs_rq队列上,此路径下,对应的是prev->se,由上可知若是被抢占的才为真。
                update_curr(cfs_rq);
            else
                curr = NULL;
            ...
        }
        ...
        se = pick_next_entity(cfs_rq, curr); //只是选出来一个se,并没有执行任何dequeue的操作
        p = task_of(se);
        
        if (prev != p) {
            struct sched_entity *pse = &prev->se;
            ...
            put_prev_entity(cfs_rq, pse); //这里面将cfs_rq->curr = NULL
            set_next_entity(cfs_rq, se); //这里面将cfs_rq->curr = se
        }
        ...
    }
    
    //将prev任务放回队列
    static void put_prev_entity(struct cfs_rq *cfs_rq, struct sched_entity *prev)
    {
        if (prev->on_rq) //为真表示是被抢占的
            update_curr(cfs_rq); //此路径下应该是和上面update_curr重复了。。。
        ...
        /* prev若是被抢占的,条件才成立。只有被抢占的,才需要执行挂回去的操作(若是sleep触发的切换就不用挂回去了)*/
        if (prev->on_rq) { //se是on_rq状态了还要 enqueue!
            /* Put 'current' back into the tree. */
            __enqueue_entity(cfs_rq, prev); //将被抢占的任务重新放回cfs队列。放回去之后其on_rq状态与其实际就在cfs_rq上就匹配上了
            ...
        }
        cfs_rq->curr = NULL; //更新cfs_rq->curr
    }
    
    //从cfs_rq中选出一个任务来运行
    static void set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se) //fair.c
    {
        /* 'current' is not kept within the tree. 
         * 这个注释应该就是说这个判断是为了避免对curr重复dequeue。
         */
        if (se->on_rq) {
            ...
            __dequeue_entity(cfs_rq, se); //虽然dequeue了,但是 se->on_rq 并没有清0 ######
        }
        cfs_rq->curr = se; //更新cfs_rq->curr
        ...
    }

    2. se->on_rq分析结论

    准确来说,当任务被执行 set_next_entity() 选出去运行,其 se->on_rq 就不能正确表示其在 cfs_rq 上挂载状态了,直到其由于休眠被执行 deactivate_task() 而被切走,或由于被抢占执行 put_prev_entity() 将其重新放回队列中,其 se->on_rq 状态才与其是否的确挂在 cfs_rq 上相吻合。可以理解为 se->on_rq 表示 running+runnable 的调度实体。


    3. 对于任务的 p->on_rq,选出来作为 next 时默认是不为0的,因为在任务入队列的路径中都是有赋值的:

    (1) 被唤醒

        try_to_wake_up //core.c
            //p->on_rq == 0 的情况
            ttwu_queue //core.c
                ttwu_do_activate //core.c
                    ttwu_activate //core.c
                        enqueue_task
                        p->on_rq = TASK_ON_RQ_QUEUED;
            //p->on_rq !=0 的情况
            if (p->on_rq && ttwu_remote(p, wake_flags)) //ttwu_remote的唯一调用位置,作用主要是将p->state=TASK_RUNNING
                goto stat; //只是统计一些信息就成功返回了

    看 ttwu_remote() 函数的注释,它也是将 p->on_rq !=0 当做是被抢占的情况了.

    (2) 迁移过来

    attach_one_task //fair.c
    attach_tasks //fair.c
        attach_task
            activate_task
                enqueue_entity
            p->on_rq = TASK_ON_RQ_QUEUED;

    (3) 被抢占而插入队列

    (4) 还有一种情况,当任务被迁移走时,其 p->on_rq 也是不等于0的

    active_load_balance_cpu_stop //fair.c
        detach_one_task //fair.c
    load_balance //fair.c
        detach_tasks //fair.c
            detach_task
                p->on_rq = TASK_ON_RQ_MIGRATING;
                deactivate_task
                    dequeue_entity

    4. p->on_rq分析结论

    对于 p->on_rq,若是由于睡眠被切走的才为0,其它情况下都不为0。应该是主要用来判断其是否是由于睡眠而被切走的,只有在睡眠状态下为0。一部分内核代码中通过判断 p->on_rq 不等于0来选择 runnable 的任务。也有一部分代码和 p->state 配合使用来断言特定状态下的任务,例如:

    void set_task_cpu(struct task_struct *p, unsigned int new_cpu) //kernel/sched/core.c
    {
        /*
         * We should never call set_task_cpu() on a blocked task,
         * ttwu() will sort out the placement.
         */
        WARN_ON_ONCE(p->state != TASK_RUNNING && p->state != TASK_WAKING && !p->on_rq);
        ...
    }

    5. 若是没有使能 CONFIG_FAIR_GROUP_SCHED,那么 cfs_rq->curr 基本上等同于 rq->curr。

  • 相关阅读:
    常用变量的获取
    批出里中常用参数的含义
    利用批处理命令复制指定文件到指定目录下
    跟后台打印程序系统服务通讯时出现错误。请打开服务管理单元,确认后台打印程序服务是否在运行。
    系统日志报错i8042prt无法加载
    删除指定路径下指定天数之前(以文件名中包含的日期字符串为准)的文件:字符串截取
    删除指定路径下指定天数之前(以文件的最后修改日期为准)的文件:BAT + VBS
    Linux学习笔记
    Docker
    Python学习笔记
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15459829.html
Copyright © 2011-2022 走看看