CFS调度1-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共用的,相当于只清理自己这一个调度实体,其调用流程:

CFS调度1-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调用路径:

CFS调度1-buddy

 

(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的调用路径:

CFS调度1-buddy

 

(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的调用路径:

CFS调度1-buddy

注: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 的调用路径:

CFS调度1-buddy

 (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 的调用路径:

CFS调度1-buddy

迁移时,尽量跳过 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 的调用流程:

CFS调度1-buddy

 2. 使用位置

(1) pick_next_entity

见上面对next buddy的分析

 

 

注:Linux5.4

 



CFS调度1-buddy

上一篇:Android单张图片查看、单指移动、双指缩放、双击最大化或最小化


下一篇:WPF的TreeView添加鼠标双击事件MouseDoubleClick执行两次