- Overview
本章内容主要回答如下的问题:
选核的策略是什么?
- 选核的策略
选核策略主要是在select_task_rq_dl中进行的。如下的函数所示:static int select_task_rq_dl(struct task_struct *p, int cpu, int sd_flag, int flags) { struct task_struct *curr; struct rq *rq; if (sd_flag != SD_BALANCE_WAKE) goto out; rq = cpu_rq(cpu); rcu_read_lock(); curr = READ_ONCE(rq->curr); /* unlocked access */ /* * If we are dealing with a -deadline task, we must * decide where to wake it up. * If it has a later deadline and the current task * on this rq can't move (provided the waking task * can!) we prefer to send it somewhere else. On the * other hand, if it has a shorter deadline, we * try to make it stay here, it might be important. */ if (unlikely(dl_task(curr)) &&//当前cpu上面run的是dl task (curr->nr_cpus_allowed < 2 || //当前task只能跑在一个cpu上面 !dl_entity_preempt(&p->dl, &curr->dl)) &&//当前cpu上面的dl task不可以被抢占 (p->nr_cpus_allowed > 1)) { //来做选核的task可以跑在多个核上面 int target = find_later_rq(p);//选择新的cpu出来 if (target != -1 && (dl_time_before(p->dl.deadline, cpu_rq(target)->dl.earliest_dl.curr) || (cpu_rq(target)->dl.dl_nr_running == 0))) cpu = target; } rcu_read_unlock(); out: return cpu; }
这个funtion表明正常情况task应该选择之前的cpu。但是因为如下的条件满足才会将这个task安排到其他cpu上面。
a. 原本cpu上的task也是deadline
b.原本cpu上的task只能跑在一个核上面,或者这个task不能被来选核的task抢占
c.来选核的task可以跑在多个核上面。
上面这些条件反映的思想是跑在其他cpu上面,比当前cpu更划算。因为,不用牺牲当前cpu的task的执行时间。
那么其他cpu是如何选出来的呢?关键的function为:static int find_later_rq(struct task_struct *task) { struct sched_domain *sd; struct cpumask *later_mask = this_cpu_cpumask_var_ptr(local_cpu_mask_dl); int this_cpu = smp_processor_id();//当前cpu int cpu = task_cpu(task); /* Make sure the mask is initialized first */ if (unlikely(!later_mask)) return -1; if (task->nr_cpus_allowed == 1) return -1; /* * We have to consider system topology and task affinity * first, then we can look for a suitable CPU. */ if (!cpudl_find(&task_rq(task)->rd->cpudl, task, later_mask))//寻找可选的cpu return -1; /* * If we are here, some targets have been found, including * the most suitable which is, among the runqueues where the * current tasks have later deadlines than the task's one, the * rq with the latest possible one. * * Now we check how well this matches with task's * affinity and system topology. * * The last CPU where the task run is our first * guess, since it is most likely cache-hot there. */ if (cpumask_test_cpu(cpu, later_mask))//如果task原来所在的cpu在其中的话,就选其原来所在的cpu return cpu; /* * Check if this_cpu is to be skipped (i.e., it is * not in the mask) or not. */ if (!cpumask_test_cpu(this_cpu, later_mask))//判断当前cpu是否可以skip this_cpu = -1; rcu_read_lock(); for_each_domain(cpu, sd) { if (sd->flags & SD_WAKE_AFFINE) {//如果所处的schedule_domain设置了affine属性,则尝试选择当前cpu,这比较符合sd_wake_affine的本义。sd_wake_affine本意就是Wake task to waking CPU int best_cpu; /* * If possible, preempting this_cpu is * cheaper than migrating. */ if (this_cpu != -1 && cpumask_test_cpu(this_cpu, sched_domain_span(sd))) {//奇怪,什么情况会打到当前cpu不在所属的domain中的现象呢? rcu_read_unlock(); return this_cpu; } best_cpu = cpumask_first_and(later_mask, sched_domain_span(sd)); //最后的选择是sched_domin与候选cpu中的交集的第1个cpu。 /* * Last chance: if a CPU being in both later_mask * and current sd span is valid, that becomes our * choice. Of course, the latest possible CPU is * already under consideration through later_mask. */ if (best_cpu < nr_cpu_ids) { rcu_read_unlock(); return best_cpu; } } } rcu_read_unlock(); /* * At this point, all our guesses failed, we just return * 'something', and let the caller sort the things out. */ if (this_cpu != -1) //还没选到就选择当前cpu return this_cpu; cpu = cpumask_any(later_mask);//随便找一个cpu吧。 if (cpu < nr_cpu_ids) return cpu; return -1; }
候选cpu是怎么选出来的呢?/* * cpudl_find - find the best (later-dl) CPU in the system * @cp: the cpudl max-heap context * @p: the task * @later_mask: a mask to fill in with the selected CPUs (or NULL) * * Returns: int - CPUs were found */ int cpudl_find(struct cpudl *cp, struct task_struct *p, struct cpumask *later_mask) { const struct sched_dl_entity *dl_se = &p->dl; if (later_mask && cpumask_and(later_mask, cp->free_cpus, p->cpus_ptr)) { //优先找free的cpu出来。 return 1; } else { //如果没有free cpu出来 int best_cpu = cpudl_maximum(cp); //寻找每颗cpu上最早deadline值最大的那颗cpu,即所有cpu中最早deadline最晚的cpu WARN_ON(best_cpu != -1 && !cpu_present(best_cpu)); if (cpumask_test_cpu(best_cpu, p->cpus_ptr) && dl_time_before(dl_se->deadline, cp->elements[0].dl)) { //se要抢别人的cpu,要满足EDF原则,即最早deadline 优先的原则。 if (later_mask) cpumask_set_cpu(best_cpu, later_mask); return 1; } } return 0; }
所以,候选的CPU主要有两种可能性:
a.空闲的CPU
b.如果没有空闲,所有CPU 中deadline最晚且比当前task的deadline还晚的CPU,这样也是为了满足EDF的原则。
那么如何记录free CPU,如何快速找到最早deadline且最晚的CPU的呢?
主要是cpudeadline.c文件中进行的。这里使用到了堆化技术。关于堆化技术参考:https://blog.csdn.net/qq508618087/article/details/53362999
根据堆的性质, 只要保证部分有序即可, 即根节点大于左右节点的值. 将数组抽象为一个完全二叉树, 所以只要从最后一个非叶子节点向前遍历每一个节点即可. 如果当前节点比左右子树节点都大, 则已经是一个最大堆, 否则将当前节点与左右节点较大的一个交换, 并且交换过之后依然要递归的查看子节点是否满足堆的性质, 不满足再往下调整. 如此即可完成数组的堆化.
————————————————
版权声明:本文为CSDN博主「小榕流光」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq508618087/article/details/53362999
一开始的时候:
init_rootdomain函数中初始化了一个类似全局变量的成员变量:struct root_domain { ...... struct cpudl cpudl; } static int init_rootdomain(struct root_domain *rd) { ...... if (cpudl_init(&rd->cpudl) != 0) goto free_rto_mask; } /* * cpudl_init - initialize the cpudl structure * @cp: the cpudl max-heap context */ int cpudl_init(struct cpudl *cp) { int i; raw_spin_lock_init(&cp->lock); cp->size = 0; cp->elements = kcalloc(nr_cpu_ids, sizeof(struct cpudl_item), GFP_KERNEL); //有多少个CPU就多少个元素.而elements就是后面管理堆化的数组 if (!cp->elements) return -ENOMEM; if (!zalloc_cpumask_var(&cp->free_cpus, GFP_KERNEL)) { kfree(cp->elements); return -ENOMEM; } for_each_possible_cpu(i) cp->elements[i].idx = IDX_INVALID; return 0; } cpudl的结构体如下所示: struct cpudl_item { u64 dl; int cpu; int idx; }; struct cpudl { raw_spinlock_t lock; int size; cpumask_var_t free_cpus; struct cpudl_item *elements; };
怎样确保来设定的deadline就是这个CPU最早deadline的呢?
猜测也应该在enqueue与dequeue的设定.事实上确实如此:
void cpudl_set(struct cpudl *cp, int cpu, u64 dl) { int old_idx; unsigned long flags; WARN_ON(!cpu_present(cpu)); raw_spin_lock_irqsave(&cp->lock, flags); old_idx = cp->elements[cpu].idx; if (old_idx == IDX_INVALID) { //加入一个新元素 int new_idx = cp->size++; cp->elements[new_idx].dl = dl; cp->elements[new_idx].cpu = cpu; cp->elements[cpu].idx = new_idx; cpudl_heapify_up(cp, new_idx); //堆化操作. cpumask_clear_cpu(cpu, cp->free_cpus); } else { cp->elements[old_idx].dl = dl; //修改一个元素 cpudl_heapify(cp, old_idx); //修改之后,仍然保持数组堆的性质. } 关于堆化可以参考:https://blog.csdn.net/qq508618087/article/details/53362999
根据堆的性质,第1个元素是整个数组中元素值最大的元素,所以:
static inline int cpudl_maximum(struct cpudl *cp) { return cp->elements[0].cpu; }
综上,deadline是通过堆化管理来提供所有cpu中最早deadline最晚CPU的.
-
总结
deadline为一个task选核时,优先选择这个task之前所在的cpu,但是如果之前所在的cpu,因为某些条件不能给当前task使用时,会再优先选择free的cpu,如果没有free cpu则选择一个最早deadline最晚的cpu并且deadline比较自己还晚的cpu进行抢。如果没有满足条件的cpu,就还是放到当前的CPU当中,只不过会进行排队。
Scheduler学习之四:dl_sched_class之选核策略
最新推荐文章于 2023-03-14 22:00:00 发布