在分析 Android 性能问题,尤其是涉及 摆核场景时,我们经常会遇到一种现象:
一个处于 running 状态 的任务会因为被标记为 "misfit" 而被系统主动迁移到大核上。
这种是最常见的情形,也有misfit task 发起和大核task swap 这种特殊情形(MTK 平台,下面会细说)
对于misfit,我们自然会产生这样的疑问:
- 什么样的任务会被标记为 misfit?
- misfit 任务的迁移目标 CPU 是如何选择的?
先罗列出来一些关键函数
更新"misfit" 状态
kernel/sched/fair.c
c
inline void update_misfit_status(struct task_struct *p, struct rq *rq)
{
int cpu = cpu_of(rq);
bool need_update = true;
trace_android_rvh_update_misfit_status(p, rq, &need_update);
if (!sched_asym_cpucap_active() || !need_update)
return;
/*
* Affinity allows us to go somewhere higher? Or are we on biggest
* available CPU already? Or do we fit into this CPU ?
*/
if (!p || (p->nr_cpus_allowed == 1) ||
(arch_scale_cpu_capacity(cpu) == p->max_allowed_capacity) ||
task_fits_cpu(p, cpu)) {
rq->misfit_task_load = 0;
return;
}
/*
* Make sure that misfit_task_load will not be null even if
* task_h_load() returns 0.
*/
rq->misfit_task_load = max_t(unsigned long, task_h_load(p), 1);
}
EXPORT_SYMBOL_GPL(update_misfit_status);
转换为白话解释:
js
update_misfit_status(p, rq)
│
├── 判断是否需要更新:
│ └── 如果调度器未启用异构 CPU 支持(sched_asym_cpucap_active() 为 false)
│ └── 或 need_update == false(给厂商用的钩子),直接返回。
│
├── 检查任务是否"fit"当前 CPU,满足以下任一条件则视为"fit":
│ ├── 任务为空(NULL)
│ ├── 任务只能运行在一个 CPU 上(p->nr_cpus_allowed == 1)
│ ├── 当前 CPU 已是该任务能跑的最大性能核(arch_scale_cpu_capacity(cpu) == p->max_allowed_capacity)
│ └── 任务适合当前 CPU(task_fits_cpu(p, cpu) 为 true)
│ └── 若满足上述任一条件,清除 misfit 状态:rq->misfit_task_load = 0 并返回。
│
└── 否则说明当前任务"misfit" 当前 CPU(即"跑错核" 或者说"跑不动"),设置 misfit 状态:
└── rq->misfit_task_load = max(task_h_load(p), 1),即即使负载为 0,也至少标记为 1。
注:
rq->misfit_task_load 这一用于判断当前cpu 是否存在misfit task 的函数,在很多场景判断下都会用到。
task_h_load(p) 函数用于计算任务 p 在其 CFS 运行队列中的加权负载
c
static unsigned long task_h_load(struct task_struct *p)
{
struct cfs_rq *cfs_rq = task_cfs_rq(p);
update_cfs_rq_h_load(cfs_rq);
return div64_ul(p->se.avg.load_avg * cfs_rq->h_load,
cfs_rq_load_avg(cfs_rq) + 1);
}
更新"misfit" 状态的场景
可以搜"update_misfit_status" 用到的地方,一个是tick,这点很容易理解,还有一个就是非常熟悉的pick_next_task_fair 函数了。
MTK 针对misfit 的客制化
tick → hook_scheduler_tick → check_for_migration
check_for_migration
c
void check_for_migration(struct task_struct *p)
{
int new_cpu = -1, better_idle_cpu = -1;
int cpu = task_cpu(p);
struct rq *rq = cpu_rq(cpu);
irq_log_store();
if (rq->misfit_task_load) {
struct em_perf_domain *pd;
struct cpufreq_policy *policy;
int opp_curr = 0, thre = 0, thre_idx = 0;
if (rq->curr->__state != TASK_RUNNING ||
rq->curr->nr_cpus_allowed == 1)
return;
pd = em_cpu_get(cpu);
if (!pd)
return;
thre_idx = (pd->nr_perf_states >> 3) - 1;
if (thre_idx >= 0)
thre = pd->table[thre_idx].frequency;
policy = cpufreq_cpu_get(cpu);
irq_log_store();
if (policy) {
opp_curr = policy->cur;
cpufreq_cpu_put(policy);
}
if (opp_curr <= thre) {
irq_log_store();
return;
}
raw_spin_lock(&migration_lock);
irq_log_store();
raw_spin_lock(&p->pi_lock);
irq_log_store();
new_cpu = p->sched_class->select_task_rq(p, cpu, WF_TTWU);
irq_log_store();
raw_spin_unlock(&p->pi_lock);
if ((new_cpu < 0) || new_cpu >= MAX_NR_CPUS ||
(cpu_cap_ceiling(new_cpu) <= cpu_cap_ceiling(cpu)))
better_idle_cpu = select_bigger_idle_cpu(p);
if (better_idle_cpu >= 0)
new_cpu = better_idle_cpu;
if (new_cpu < 0) {
raw_spin_unlock(&migration_lock);
irq_log_store();
return;
}
irq_log_store();
if ((better_idle_cpu >= 0) ||
(new_cpu < MAX_NR_CPUS && new_cpu >= 0 &&
(cpu_cap_ceiling(new_cpu) > cpu_cap_ceiling(cpu)))) {
raw_spin_unlock(&migration_lock);
migrate_running_task(new_cpu, p, rq, MIGR_TICK_PULL_MISFIT_RUNNING);
irq_log_store();
} else {
#if IS_ENABLED(CONFIG_MTK_SCHED_BIG_TASK_ROTATE)
int thre_rot = 0, thre_rot_idx = 0;
thre_rot_idx = (pd->nr_perf_states >> 1) - 1;
if (thre_rot_idx >= 0)
thre_rot = pd->table[thre_rot_idx].frequency;
if (opp_curr > thre_rot) {
task_check_for_rotation(rq);
irq_log_store();
}
#endif
raw_spin_unlock(&migration_lock);
}
}
irq_log_store();
}
转换为白话解释:
js
check_for_migration(p)
│
│ 只有rq->misfit_task_load 为真,才会往下走,说明当前队列存在misfit 的情况
│ │
│ ├── 当前任务没有running,或者有限制到只能运行在一个 CPU 上,就不用迁移
│ │
│ ├── 获取当前 CPU 所属的能耗性能域(pd)
│ │ └── 如果拿不到 pd,说明调度域异常,也直接 return
│ │
│ ├── 计算"低频阈值"(thre),用于判断当前 CPU 频率是否太低
│ │ └── thresh_idx = pd->nr_perf_states >> 3(等于除8)
│ │
│ ├── 获取当前 CPU 实际运行频率(opp_curr),和 thre 比一下
│ │ └── 如果当前频率 opp_curr <= thre,说明是因为频率低所以不fit,任务迁移意义不大 → return
│ │
│ ├── 获取 p 的目标 CPU:调用 select_task_rq() 让调度类决定迁到哪个 CPU(new_cpu)
│ │
│ ├── 如果 select_task_rq 给的目标 CPU 不行(new_cpu < 0,或new cpu 的ceiling没提升)
│ │ └── 那么久尝试找一个性能更强的空闲 CPU(select_bigger_idle_cpu)
│ │
│ ├── 如果到这里还找不到合适的 CPU,一般很难发生,说明选核也没选到,选更大的idle cpu 也没有,直接return
│ │
│ ├── 如果new CPU 比当前的强,或者找到了 idle CPU:
│ │ └── 执行 migrate_running_task() 做实际迁移(此时任务是 running 状态)
│ │
│ └── 否则,进入"旋转尝试"逻辑(下面会细说task_check_for_rotation):
│ └── 如果当前 CPU 频率 > 另一个更高的旋转阈值 thre_rot
│ └── 尝试 task_check_for_rotation()
task_check_for_rotation
c
void task_check_for_rotation(struct rq *src_rq)
{
u64 wc, wait, max_wait = 0, run, max_run = 0;
int deserved_cpu = nr_cpu_ids, dst_cpu = nr_cpu_ids;
int i, src_cpu = cpu_of(src_rq);
struct rq *dst_rq;
struct task_rotate_work *wr = NULL;
struct root_domain *rd = cpu_rq(smp_processor_id())->rd;
int force = 0;
struct cpumask src_eff;
struct cpumask dst_eff;
bool src_ls;
bool dst_ls;
irq_log_store();
if (!big_task_rotation_enable) {
irq_log_store();
return;
}
if (is_max_capacity_cpu(src_cpu)) {
irq_log_store();
return;
}
if (cpu_paused(src_cpu)) {
irq_log_store();
return;
}
if (!(rd->android_vendor_data1[0])) {
irq_log_store();
return;
}
irq_log_store();
wc = ktime_get_raw_ns();
irq_log_store();
for_each_possible_cpu(i) {
struct rq *rq = cpu_rq(i);
struct rot_task_struct *rts;
if (cpu_paused(i))
continue;
if (!is_min_capacity_cpu(i))
continue;
if (!READ_ONCE(rq->misfit_task_load) ||
(READ_ONCE(rq->curr->policy) != SCHED_NORMAL))
continue;
if (is_reserved(i))
continue;
compute_effective_softmask(rq->curr, &dst_ls, &dst_eff);
if (dst_ls && !cpumask_test_cpu(src_cpu, &dst_eff))
continue;
rts = &((struct mtk_task *) rq->curr->android_vendor_data1)->rot_task;
wait = wc - READ_ONCE(rts->ktime_ns);
if (wait > max_wait) {
max_wait = wait;
deserved_cpu = i;
}
}
irq_log_store();
if (deserved_cpu != src_cpu) {
irq_log_store();
return;
}
for_each_possible_cpu(i) {
struct rq *rq = cpu_rq(i);
struct rot_task_struct *rts;
if (cpu_paused(i))
continue;
if (capacity_orig_of(i) <= capacity_orig_of(src_cpu))
continue;
if (READ_ONCE(rq->curr->policy) != SCHED_NORMAL)
continue;
if (READ_ONCE(rq->nr_running) > 1)
continue;
if (is_reserved(i))
continue;
compute_effective_softmask(rq->curr, &dst_ls, &dst_eff);
if (dst_ls && !cpumask_test_cpu(src_cpu, &dst_eff))
continue;
rts = &((struct mtk_task *) rq->curr->android_vendor_data1)->rot_task;
run = wc - READ_ONCE(rts->ktime_ns);
if (run < TASK_ROTATION_THRESHOLD_NS)
continue;
if (run > max_run) {
max_run = run;
dst_cpu = i;
}
}
irq_log_store();
if (dst_cpu == nr_cpu_ids) {
irq_log_store();
return;
}
dst_rq = cpu_rq(dst_cpu);
irq_log_store();
double_rq_lock(src_rq, dst_rq);
irq_log_store();
if (dst_rq->curr->policy == SCHED_NORMAL) {
if (!cpumask_test_cpu(dst_cpu,
src_rq->curr->cpus_ptr) ||
!cpumask_test_cpu(src_cpu,
dst_rq->curr->cpus_ptr)) {
double_rq_unlock(src_rq, dst_rq);
irq_log_store();
return;
}
if (cpu_paused(src_cpu) || cpu_paused(dst_cpu)) {
double_rq_unlock(src_rq, dst_rq);
irq_log_store();
return;
}
compute_effective_softmask(dst_rq->curr, &dst_ls, &dst_eff);
if (dst_ls && !cpumask_test_cpu(src_cpu, &dst_eff)) {
double_rq_unlock(src_rq, dst_rq);
return;
}
compute_effective_softmask(src_rq->curr, &src_ls, &src_eff);
if (src_ls && !cpumask_test_cpu(dst_cpu, &src_eff)) {
double_rq_unlock(src_rq, dst_rq);
return;
}
if (!src_rq->active_balance && !dst_rq->active_balance) {
src_rq->active_balance = 1;
dst_rq->active_balance = 1;
irq_log_store();
get_task_struct(src_rq->curr);
irq_log_store();
get_task_struct(dst_rq->curr);
irq_log_store();
wr = &per_cpu(task_rotate_works, src_cpu);
wr->src_task = src_rq->curr;
wr->dst_task = dst_rq->curr;
wr->src_cpu = src_rq->cpu;
wr->dst_cpu = dst_rq->cpu;
force = 1;
}
}
irq_log_store();
double_rq_unlock(src_rq, dst_rq);
irq_log_store();
if (force) {
queue_work_on(src_cpu, system_highpri_wq, &wr->w);
irq_log_store();
trace_sched_big_task_rotation(wr->src_cpu, wr->dst_cpu,
wr->src_task->pid, wr->dst_task->pid,
false);
}
irq_log_store();
}
白话解释:
js
task_check_for_rotation(src_rq)
│
│ ├── big_task_rotation_enable 这个mtk feature 如果没开就 return
│ ├── 当前 CPU 已经是最大性能核就 return
│ ├── 当前 CPU 被 pause 了 → return
│ └── "互换任务之前,先看看两边的任务能不能跑到对方 CPU 上(根据cpumask判断),一方不允许,就return。
│
├── 开始进入正文,Step1:先遍历所有 CPU,尝试找一个"饿的最久的小核任务"
│ ├── 筛选条件:是小核 + misfit + SCHED_NORMAL + 不是保留核
│ ├── 判断它等了多久(wait = 当前时间 - 它的 ktime_ns),这里是mtk 客制化得出的数据
│ └── 挑出"等待时间最长"的小核任务所在的 CPU → 作为deserved_cpu
│
├── 上面一轮的小核遍历下来,如果得出的deserved_cpu 并非是src_cpu
├── 说明小核上还有"饿的"比自己更久的,就说明机会应该先轮到别人,return
│
├── Step2:再次遍历所有 CPU,尝试找一个"可以和p 互换的大核task"
│ ├── 筛选条件:更大的cluster 上的cpu(就是大核) + 当前任务是 SCHED_NORMAL + 只运行一个任务
│ ├── 判断这个任务已经运行了多久(run = 当前时间 - ktime_ns)
│ └── 找出 run 时间最长的目标 CPU → dst_cpu
│
├── 上面的遍历如果没找到合适的大核目标 CPU → return
│
├── 锁住 src_rq 和 dst_rq,准备开始换任务
│ ├── 确保两个任务的 cpu 亲和性没问题(互相都能跑到对方的 CPU)
│ ├── 检查是否被 pause 了
│ ├── 如果两个 CPU 都没有 active_balance,就可以开始换
│ │ ├── 标记 active_balance = 1,这个是防止并发
│ │ ├── 增加任务引用计数(get_task_struct)
│ │ └── 设置好 task_rotate_work 的数据结构
│
├── 解锁 runqueue,最后:
│ └── 如果符合条件,就把任务 rotate 互换(queue 到 workqueue 上处理)
│ └── 一般Systrace 中也可以直接看出来,就是后面先跟一个kwork然后紧跟着一个migration 线程
真正执行swap 的函数
c
static void task_rotate_work_func(struct work_struct *work)
{
struct task_rotate_work *wr = container_of(work,
struct task_rotate_work, w);
int ret = -1;
struct rq *src_rq, *dst_rq;
irq_log_store();
ret = migrate_swap(wr->src_task, wr->dst_task,
task_cpu(wr->dst_task), task_cpu(wr->src_task));
irq_log_store();
if == 0) {
trace_sched_big_task_rotation(wr->src_cpu, wr->dst_cpu,
wr->src_task->pid,
wr->dst_task->pid,
true);
}
irq_log_store();
put_task_struct(wr->src_task);
irq_log_store();
put_task_struct(wr->dst_task);
irq_log_store();
src_rq = cpu_rq(wr->src_cpu);
dst_rq = cpu_rq(wr->dst_cpu);
irq_log_store();
local_irq_disable();
double_rq_lock(src_rq, dst_rq);
irq_log_store();
src_rq->active_balance = 0;
dst_rq->active_balance = 0;
irq_log_store();
double_rq_unlock(src_rq, dst_rq);
local_irq_enable();
irq_log_store();
}
解释:
js
task_rotate_work_func(work)
│
├── 通过 container_of() 拿到 task_rotate_work 指针
│ └── 里面包含了两个准备swap 的task:src_task 和 dst_task
│
├── 调用 migrate_swap()
│ └── 尝试把 src_task 和 dst_task 两个任务的运行 CPU 互换
│
├── migrate_swap 成功(返回 0):
这里涉及到一个关键函数migrate_swap,忽略部分调用过程,最后会调用到关键函数 move_queued_task_locked
,这个函数作用是将任务从一个 CPU 的 runqueue
迁移到另一个 CPU 的 runqueue
js
static inline
void move_queued_task_locked(struct rq *src_rq, struct rq *dst_rq, struct task_struct *task)
{
lockdep_assert_rq_held(src_rq);
lockdep_assert_rq_held(dst_rq);
deactivate_task(src_rq, task, 0);
set_task_cpu(task, dst_rq->cpu);
activate_task(dst_rq, task, 0);
}