zoukankan      html  css  js  c++  java
  • 调度器4—CFS的buddy

    一、buddy简介

    buddy 是 cfs_rq 中的三个 sched_entity,在cfs线程间抢占,线程主动放弃cpu,对某些线程进行特殊照顾扮演重要角色。

    1. buddy 成员位置

    //fair.c
    struct cfs_rq {
        ...
        struct sched_entity    *next; //和last差不多,只不过优先级没有next高
        struct sched_entity    *last; //优先调度
        struct sched_entity    *skip; //用于跳过一些任务
        ...
    };

    2. buddy 相关函数

    static void set_next_buddy(struct sched_entity *se)
    {
        ...
        cfs_rq_of(se)->next = se;
        ...
    }
    static void set_last_buddy(struct sched_entity *se)
    {
        ...
        cfs_rq_of(se)->last = se;
        ...
    }
    static void set_skip_buddy(struct sched_entity *se)
    {
        ...
        cfs_rq_of(se)->skip = se;
        ...
    }
    
    static void clear_buddies(struct cfs_rq *cfs_rq, struct sched_entity *se)
    {
        ...
        if (cfs_rq->last == se)
            cfs_rq_of(se)->last = NULL;
        if (cfs_rq->next == se)
            cfs_rq_of(se)->next = NULL;
        if (cfs_rq->skip == se)
            cfs_rq_of(se)->skip = NULL;
        ...
    }

    clear_buddies是是所有buddy共用的,相当于只清理自己这一个调度实体,其调用流程:

     

    二、next buddy分析

    1. 设置位置

    static void dequeue_task_fair(struct rq *rq, struct task_struct *p, int flags) //fair.c
    {
        struct cfs_rq *cfs_rq;
        struct sched_entity *se = &p->se;
        int task_sleep = flags & DEQUEUE_SLEEP;
        se = parent_entity(se); //没有使能组调度,这里返回NULL
        ...
        /*若没有开启对CFS的带宽控制的话,传参flags中有 DEQUEUE_SLEEP 标志就会设置,若没有使能CFS组调度,恒不会设置*/
        if (task_sleep && se && !throttled_hierarchy(cfs_rq))
            set_next_buddy(se);
        ...
    }

    (1) dequeue_task_fair

    DEQUEUE_SLEEP传参时机:

    a. __schedule --> deactivate_task(rq, prev, DEQUEUE_SLEEP | DEQUEUE_NOCLOCK) --> dequeue_task //正常进程切换的时候,flag中会包含DEQUEUE_SLEEP,任务切换时偏向于将由于sleep而休眠退出的任务设置到next buddy上。

    b. throttle_cfs_rq --> dequeue_entity(qcfs_rq, se, DEQUEUE_SLEEP); //throttle cfs时也会传DEQUEUE_SLEEP

    c. dequeue_task_fair --> flags |= DEQUEUE_SLEEP //若是使能了组调度,一个任务组中只有第一个dequeue的任务可能不传DEQUEUE_SLEEP标志,其它的都会传这个标志,单只可能对第一个任务设置next buddy.

    dequeue_task_fair调用路径:

     

    (2) check_preempt_wakeup

    static void check_preempt_wakeup(struct rq *rq, struct task_struct *p, int wake_flags) //fair.c
    {
        struct sched_entity *se = &curr->se, *pse = &p->se;
        struct cfs_rq *cfs_rq = task_cfs_rq(curr); //因为有组调度,这个和rq->cfs还不一定是同一个
        int scale = cfs_rq->nr_running >= sched_nr_latency; //8个runnable的线程
        int next_buddy_marked = 0;
    
        ...
        /* 如果开启了 NEXT_BUDDY feature,且此cfs_rq上有超过8个线程待运行,且不是fork线程后进行唤醒的,则将新换新的进程设置到next buddy
        fork系统调用 -> _do_fork -> wake_up_new_task -> check_preempt_curr(rq, p, WF_FORK);
        */
        if (sched_feat(NEXT_BUDDY) && scale && !(wake_flags & WF_FORK)) {
            set_next_buddy(pse);
            next_buddy_marked = 1;
        }
        ...
        if (wakeup_preempt_entity(se, pse) == 1) { //1.当前进程的虚拟时间比新进程多运行2ms在新进程上对应的虚拟时间
            /* Bias pick_next to pick the sched entity that is triggering this preemption.*/
            //和前面的设置是互斥的
            if (!next_buddy_marked)
                set_next_buddy(pse);
            goto preempt; //2.触发抢占
        }
        ...
    preempt:
        /* 设置 TIF_NEED_RESCHED 标志,在下一个抢占点到来时进行抢占 */
        resched_curr(rq);
    
        ...
    
        //LAST_BUDDY默认设置的
        //函数的最后,此cfs_rq上有大于8个runnable的任务且是任务(非任务组)
        if (sched_feat(LAST_BUDDY) && scale && entity_is_task(se))
            set_last_buddy(se);
    }

    check_preempt_wakeup函数是next buddy主要作用的位置,注意,第一个位置判断了 sched_feat(NEXT_BUDDY),第二个使用位置没有判断,第一个位置可以通过关闭feature关掉(一般是关掉的),第二个位置关不掉。

    check_preempt_wakeup的调用路径:

     

    (3) yield_to_task_fair

    static bool yield_to_task_fair(struct rq *rq, struct task_struct *p, bool preempt)
    {
        struct sched_entity *se = &p->se;
        set_next_buddy(se);
        ...
    }

    yield_to_task_fair的调用路径:

    注:yield_task 和 yield_to_task 不是同一个,它两个都是 fair_sched_class 调度类的两个回调函数,但是前者将当前task设置为cfs_rq->skip,后者将p设置到cfs_rq->next.

    2. 使用位置

    (1) pick_next_entity

    static struct sched_entity * pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
    {
        struct sched_entity *left = __pick_first_entity(cfs_rq);
        struct sched_entity *se;
    
        /*若cfs_rq上没有ready的任务,或则curr的虚拟时间比rbtree最左侧的还小*/
        if (!left || (curr && entity_before(curr, left)))
            left = curr;
    
        se = left; /* ideally we run the leftmost entity */
    
        /* Avoid running the skip buddy, if running something else can be done without getting too unfair. */
        /* 如果有其它可运行的,就避免运行这个skip buddy */
        if (cfs_rq->skip == se) {
            struct sched_entity *second;
            if (se == curr) {
                second = __pick_first_entity(cfs_rq); //重新选一个来运行
            } else {
                second = __pick_next_entity(se); // se是最左侧的,这里选第二左的
                /* 如果虚拟时间第二小的不存在 或 curr比选出来的这个虚拟时间还小,那么继续选curr */
                if (!second || (curr && entity_before(curr, second)))
                    second = curr;
            }
    
            /* 只有当选出来的second的虚拟时间比left还小2ms对应的虚拟时间以上,才取second*/
            if (second && wakeup_preempt_entity(second, left) < 1)
                se = second;
        }
    
        /*
         * Prefer last buddy, try to return the CPU to a preempted task.
         */
        /* 若是cfs_rq->last也比left的虚拟时间也小这么多,优先选cfs_rq->last */
        if (cfs_rq->last && wakeup_preempt_entity(cfs_rq->last, left) < 1)
            se = cfs_rq->last;
    
        /*
         * Someone really wants this to run. If it's not unfair, run it.
         */
        /* 若是next的虚拟时间比rbtree最左侧的的任务还小,那么就选next,这个是最高优先级*/
        if (cfs_rq->next && wakeup_preempt_entity(cfs_rq->next, left) < 1)
            se = cfs_rq->next;  //这里选择了next_buddy
    
        /*将此se从cfs_rq中清理掉,无论是否选择它,都将其清理掉,因此一次设置只起一次作用*/
        clear_buddies(cfs_rq, se);
    
        return se;
    }

    注:在 pick_next_entity 中会先使用,然后 clear_buddies,也就是说这里的 yield_task_fair 只能选中它时放弃一次cpu,下次再选中它就正常执行了。

    pick_next_entity 的调用路径:

     (2) task_hot

    static int task_hot(struct task_struct *p, struct lb_env *env)
    {
        s64 delta;
        ...
        /*
         * Buddy candidates are cache hot:
         */
        /* CACHE_HOT_BUDDY默认为true */
        if (sched_feat(CACHE_HOT_BUDDY) && env->dst_rq->nr_running &&
                (&p->se == cfs_rq_of(&p->se)->next || &p->se == cfs_rq_of(&p->se)->last))
            return 1;
    
        //默认0.5ms
        if (sysctl_sched_migration_cost == -1)
            return 1;
        if (sysctl_sched_migration_cost == 0)
            return 0;
    
        //上次开始运行到现在的时间差值小于0.5ms就认为还是task_hot的,返回真
        delta = rq_clock_task(env->src_rq) - p->se.exec_start;
        //若delta小于,那就返回1,否则返回0
        return delta < (s64)sysctl_sched_migration_cost;
    }

    task_hot 的调用路径:

    迁移时,尽量跳过 task_hot 的任务,也就是会尽量跳过设置在 cfs_rq->next 和 cfs_rq->last 的任务。

    三、last buddy

    1. 设置位置

    (1) check_preempt_wakeup

    见上面对next buddy的分析

    2. 使用位置

    (1) pick_next_entity

    见上面对next buddy的分析

    (2) task_hot

    见上面对next buddy的分析

    注:last buddy 和next buddy作用类似,只是优先级没有next buddy高。

    四、skip buddy

    1. 设置位置

    (1) yield_task_fair

    static void yield_task_fair(struct rq *rq)
    {
        struct sched_entity *se = &curr->se;
        ...
        set_skip_buddy(se);
    }

    yield_task_fair 的调用流程:

     2. 使用位置

    (1) pick_next_entity

    见上面对next buddy的分析

    注:Linux5.4



  • 相关阅读:
    在Centos 7下编译openwrt+njit-client
    开博随笔
    Chapter 6. Statements
    Chapter 4. Arrays and Pointers
    Chapter 3. Library Types
    Chapter 2.  Variables and Basic Types
    关于stm32不常用的中断,如何添加, 比如timer10 timer11等
    keil 报错 expected an identifier
    案例分析 串口的地不要接到电源上 会烧掉
    案例分析 CAN OPEN 调试记录 进度
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15302500.html
Copyright © 2011-2022 走看看