一、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