Skip to main content
 首页 » 操作系统

Linux mtk task_turbo 阅读笔记

2022年07月19日75sharpest

基于MTK linux-4.14,后续新版本内核已经废弃task turbo。

1. 代码位置:

drivers/misc/mediatek/task_turbo/task_turbo.c 
drivers/misc/mediatek/include/mt-plat/turbo_common.h

2. 导出接口

/sys/module/task_turbo/parameters # ls -l 
-rw-rw-r-- 1 system system 4096 2021-01-02 09:39 feats 
-rw-r--r-- 1 root   root   4096 2021-01-02 09:39 turbo_pid 
-rw-r--r-- 1 root   root   4096 2021-01-02 08:35 unset_turbo_pid

接口说明:
(1) feats 对应操作函数集为 task_turbo_feats_param_ops。文件取值是下面字段的组成的位掩码,每个bit是一个子feature,由于子feature之间存在依赖并不是所有掩码都能设置成功,实测只能设置7和15,区别如下,只有使能了SUB_FEAT_FLAVOR_BIGCORE 才会设置 p->cpu_prefer 。若往里面设置0,会清空数组 turbo_pid[8] 里面所有的pid,并且unset数组里面所有pid对应的任务。

static uint32_t latency_turbo = SUB_FEAT_LOCK | SUB_FEAT_BINDER | SUB_FEAT_SCHED; 
static uint32_t launch_turbo =  SUB_FEAT_LOCK | SUB_FEAT_BINDER | SUB_FEAT_SCHED | SUB_FEAT_FLAVOR_BIGCORE; 
 
//turbo_common.h 
enum { 
    SUB_FEAT_LOCK        = 1U << 0, 
    SUB_FEAT_BINDER        = 1U << 1, 
    SUB_FEAT_SCHED        = 1U << 2, 
    SUB_FEAT_FLAVOR_BIGCORE = 1U << 3, //这个是更偏向于大核? 
}; 
 
//判断使能的是哪个feature的 
inline bool latency_turbo_enable(void) 
{ 
    return task_turbo_feats == latency_turbo; 
} 
inline bool launch_turbo_enable(void) 
{ 
    return task_turbo_feats == launch_turbo; 
}

(2) turbo_pid 对应操作函数集为 turbo_pid_param_ops。存储写入的pid,使用一个一维8个元素的数组来存储,最大应该支持同时boost 8个任务。先存到数组turbo_pid[8]中,保证不重复,若数组存满了,就直接返回false退出设置。然后将任务的p->turbo=1,p->cpu_prefer=1(perfer big=1,mid=2)。设置完后立即调用了一次 set_user_nice 触发设置生效。

(3) unset_turbo_pid 对应操作函数集为 unset_turbo_pid_param_ops。清除对某个pid对应任务的设置,先从 turbo_pid[8] 数组中清除掉这个pid,然后对这个任务执行复位操作:p->turbo=0,p->cpu_prefer=0。设置完后立即调用了一次 set_user_nice 触发设置生效。

只支持对cfs任务的设置生效,应该在配置关键函数中判断非 fair_policy(task->policy) 的话就直接退出了。设置前需要先将 feats 文件设置好,因为在pid的设置路径中判断了若 feats 没有设置就直接退出了。从 is_turbo_task() 判断中可以看出,不仅支持直接设置还支持继承设置,p->inherit_types 不为0就表示是继承的,也会被 boost.

3. 由配置可以看出,下面三个成员是关键角色。

p->turbo 
p->cpu_prefer 
p->inherit_types

4. binder继承

//task_turbo.h 
enum { 
    START_INHERIT   = -1, 
    RWSEM_INHERIT   = 0, 
    BINDER_INHERIT, 
    END_INHERIT, 
};

看来目前只支持 rwsem 和 binder 两种继承。经过 binder继承的是不会写入到 turbo_pid[8] 数组中的。

(1) binder中的继承和取消继承:

static bool binder_proc_transaction(struct binder_transaction *t, struct binder_proc *proc, struct binder_thread *thread) 
{ 
    if (thread) { 
        if (binder_start_turbo_inherit(t->from ? t->from->task : NULL, thread->task)) 
            t->inherit_task = thread->task; //binder_transaction中还建了一个结构 
    } 
} 
 
static void binder_transaction(struct binder_proc *proc, struct binder_thread *thread, 
    struct binder_transaction_data *tr, int reply, binder_size_t extra_buffers_size) 
{ 
    ... 
    t->inherit_task = NULL; 
    ... 
    if (reply) { 
        if (thread->task && in_reply_to->inherit_task == thread->task) { 
            binder_stop_turbo_inherit(thread->task); //停止继承 
            in_reply_to->inherit_task = NULL; 
        } 
    } 
    ... 
    if (in_reply_to) { 
        if (thread->task && in_reply_to->inherit_task == thread->task) { 
            binder_stop_turbo_inherit(thread->task); 
            in_reply_to->inherit_task = NULL; 
        } 
    } 
    ... 
 
} 
 
static int binder_thread_read(struct binder_proc *proc, struct binder_thread *thread, 
    binder_uintptr_t binder_buffer, size_t size, binder_size_t *consumed, int non_block) 
{ 
    ... 
    if (wait_for_proc_work) { 
        binder_stop_turbo_inherit(current); 
    } 
    ... 
    if (t_from) { 
        if (binder_start_turbo_inherit(t_from->task, thread->task)) 
            t->inherit_task = thread->task; 
    } 
    ... 
}

5. rwsem 继承

//继承 
void up_write(struct rw_semaphore *sem) //kernel/locking/rwsem.c 
{ 
    ... 
    rwsem_stop_turbo_inherit(sem); 
    ... 
} 
void downgrade_write(struct rw_semaphore *sem) 
{ 
    ... 
    rwsem_stop_turbo_inherit(sem); 
    ... 
} 
 
void rwsem_stop_turbo_inherit(struct rw_semaphore *sem) 
{ 
    unsigned long flags; 
 
    raw_spin_lock_irqsave(&sem->wait_lock, flags); 
    if (sem->turbo_owner == current) { 
        stop_turbo_inherit(current, RWSEM_INHERIT); //只是减去这个rwsem的值,只有task->inherit_types减为0了才会unset sched tunning. 
        sem->turbo_owner = NULL; 
        trace_turbo_inherit_end(current); 
    } 
    raw_spin_unlock_irqrestore(&sem->wait_lock, flags); 
} 
 
//取消继承 
static inline struct rw_semaphore __sched * __rwsem_down_read_failed_common(struct rw_semaphore *sem, int state) //rwsem-xadd.c 
{ 
#ifdef CONFIG_MTK_TASK_TURBO 
    rwsem_list_add(waiter.task, &waiter.list, &sem->wait_list); //调用的是turbo_task.c中的 
#else 
    list_add_tail(&waiter.list, &sem->wait_list); 
#endif 
    ... 
    if (waiter.task) 
        rwsem_start_turbo_inherit(sem); 
    ... 
} 
 
void rwsem_start_turbo_inherit(struct rw_semaphore *sem) 
{ 
    bool should_inherit; 
    struct task_struct *owner; 
    struct task_struct *cur = current; 
 
    owner = READ_ONCE(sem->owner); 
    should_inherit = should_set_inherit_turbo(cur); 
    if (should_inherit) { 
        if (rwsem_owner_is_writer(owner) && !is_turbo_task(owner) && !sem->turbo_owner) { 
            start_turbo_inherit(owner, RWSEM_INHERIT, cur->inherit_cnt); 
            sem->turbo_owner = owner; 
            trace_turbo_inherit_start(cur, owner); 
        } 
    } 
} 
 
static inline struct rw_semaphore * __rwsem_down_write_failed_common(struct rw_semaphore *sem, int state) //rwsem-xadd.c 
{ 
    ... 
    rwsem_list_add(waiter.task, &waiter.list, &sem->wait_list); //将add_tail改为这个 
    ... 
    if (waiting) { 
        ... 
    } else { 
        rwsem_start_turbo_inherit(sem); 
    } 
}

经过 rwsem 继承的也不会写入到 turbo_pid[8] 数组中的。


6. futex.c 中的优化

static inline void __queue_me(struct futex_q *q, struct futex_hash_bucket *hb) //futex.c 
{ 
#ifdef CONFIG_MTK_TASK_TURBO 
    futex_plist_add(q, hb); 
#else 
    plist_add(&q->list, &hb->chain); 
#endif 
}

用户空间锁虽然没有继承,但是应该是加了优先唤醒优化。

7. cgroup中也有设置

cgroup1_base_files.write //回调,对应文件"cgroup.procs" 
    cgroup1_procs_write 
        __cgroup1_procs_write(of, buf, nbytes, off, true); //cgroup_v1.c 
 
cgroup1_base_files.write //回调,对应文件"tasks" 
    cgroup1_tasks_write 
        __cgroup1_procs_write(of, buf, nbytes, off, false); //cgroup_v1.c 
 
 
static ssize_t __cgroup1_procs_write(struct kernfs_open_file *of, char *buf, size_t nbytes, loff_t off, bool threadgroup) 
{ 
    ret = cgroup_attach_task(cgrp, task, threadgroup); //成功返回0 
    if (!ret) 
        cgroup_set_turbo_task(task); 
} 
 
void cgroup_set_turbo_task(struct task_struct *p) 
{ 
    /* if group stune of top-app */ 
    if (get_st_group_id(p) == TOP_APP_GROUP_ID) { //对应/dev/stune/top-app 
        if (!cgroup_check_set_turbo(p)) //p不是turbo线程并且p是render线程并且p是主线程才执行,,主线程不一定是ui线程啊!############## 
            return; 
        add_turbo_list(p); 
    } else { /* other group */ 
        if (p->turbo) 
            remove_turbo_list(p); 
    } 
} 
 
static inline bool cgroup_check_set_turbo(struct task_struct *p) 
{ 
    if (p->turbo) 
        return false; 
    /* set critical tasks for UI or UX to turbo */ 
    return (p->render || (p == p->group_leader && p->real_parent->pid != 1)); 
}

render线程或进程的主线程 attach 到 top-app 分组时就将其设置为 turbo task.

8. p->render 的赋值路径

SYSCALL_DEFINE5(prctl ...) //kernel/sys.c  prctl系统调用设置任务comm的时候更改 
    case PR_SET_NAME: 
    sys_set_turbo_task(me); //task_turbo.c 名字是"RenderThread",并且launch_turbo_enable是使能的,并且是top-app分组中的任务才设置p->render,设置后立即触发调度 
        p->render = 1;

9. 起作用的位置:

(1) core.c 中检索 turbo 得到的,没啥作用,就是设置优先级时触发重新调度。还是要看下面成员的使用
p->turbo //主要用来标记的
p->inherit_types //和 binder/sem 中的继承有关
p->cpu_prefer //在最终的选核结果上控制从哪个cluster再选一次
p->render //设置任务comm路径中经过判断设置下来的,见上面

(2) p->turbo 和 p->inherit_types
主要是上面看到的地方使用了,一些binder继承,rwsem继承,futex持锁优先唤醒等。主要是在 is_turbo_task() 中一起判断的,这个函数除了继承使用外,还有:

a. 不限制turbo_task对L1和L2 cache的使用,默认是限制bg任务对cache的使用的。

static inline bool is_important(struct task_struct *task) 
{ 
    int grp_id = get_stune_id(task); 
 
#ifdef CONFIG_MTK_TASK_TURBO 
    if (ctl_turbo_group && is_turbo_task(task)) 
        return true; 
#endif 
    if (ctl_suppress_group & (1 << grp_id)) 
        return false; 
    return true; 
} 
调用路径: 
__schedule //core.c 
    context_switch 
        prepare_task_switch 
            hook_ca_context_switch //cache_ctrl.c 
                restrict_next_task 
                    audit_next_task //判断下面函数返回true直接返回false 
                        is_important

b. 创建子进程,若是 turbo_task 子进程不继承 cpu_prefer 属性

int sched_fork(unsigned long clone_flags, struct task_struct *p) //core.c 
{ 
    ... 
    p->prio = current->normal_prio; 
#ifdef CONFIG_MTK_TASK_TURBO 
    if (unlikely(is_turbo_task(current))) 
        set_user_nice(p, current->nice_backup); //不污染子进程的prio 
#endif 
    ... 
#ifdef CONFIG_MTK_SCHED_BOOST 
    p->cpu_prefer = current->cpu_prefer; 
#ifdef CONFIG_MTK_TASK_TURBO 
    if (unlikely(is_turbo_task(current))) 
        p->cpu_prefer = 0; // SCHED_PREFER_NONE,RenderThread若是创建子线程了,子线程不继承 
#endif 
#endif 
    ... 
}

(3) p->cpu_prefer

a. 由 sched_fork() 可知,fork新进程时 p->cpu_prefer 会被继承,但是 turbo_task 的不会继承。

b. fbt_cpu.c 中 fbt_set_task_policy(0 也会设置 p->cpu_prefer

fbt_set_min_cap_locked 
    fbt_set_task_policy(fl, llf_task_policy, FPSGO_PREFER_LITTLE, 0) 
 
c. 在 select_task_prefer_cpu 中会影响从哪个cluster开始选核,是在之前选核的基础上选核的 
    fair_sched_class.select_task_rq 
        select_task_rq_fair //fair.c 
            select_task_prefer_cpu_fair //fair.c 待加上对中核的支持 
            select_task_rq_rt //rt.c MTK CONFIG_MTK_SCHED_BOOST 加的,直接在结果上更改,是否合理还有待考证 
                select_task_prefer_cpu(struct task_struct *p, int new_cpu) //ched_ctl.c

int sched_boost_type = SCHED_NO_BOOST;  对应设置文件:/sys/devices/system/cpu/sched/sched_boost,取值范围与设置路径:

//取值范围: 
enum { 
    SCHED_NO_BOOST = 0, 
    SCHED_ALL_BOOST, 
    SCHED_FG_BOOST, 
    SCHED_UNKNOWN_BOOST 
}; 
//设置路径: 
/sys/devices/system/cpu/sched/sched_boost 对应的设置文件 
    store_sched_boost //sched_ctl.c 
        set_sched_boost 
            sched_boost_type = val;

结论:一是perfer小核的关键任务取消perfer小核的特性。二是在通过 sched_boost 文件进行boost的情况下,前台和top-app分组运行3ms就改为从中核开始选核,其它分组直接从小核开始选核。

d. 另一个设置接口,这里面没有检查 feats 文件是否设置了。

echo 1540 2 > /sys/devices/system/cpu/sched/cpu_prefer 
store_cpu_prefer 
    sched_set_cpuprefer 中trace: 
        sh-21986 [000] .... 155968.765448: sched_set_cpuprefer: pid=1540 comm=system_server cpu_prefer=2

e. MTK有两个feature都使用到了p->cpu_prefer变量,分别为:CONFIG_MTK_TASK_TURBO、CONFIG_MTK_SCHED_BOOST

f. p->cpu_prefer 的设置总结

/sys/module/task_turbo/parameters/turbo_pid //设置前要先设置好同目录下的feats文件,CONFIG_MTK_TASK_TURBO的接口
/sys/devices/system/cpu/sched/cpu_prefer //CONFIG_MTK_SCHED_BOOST的接口,设置不检查feats文件,直接设置,只是单纯的设置p->cpu_prefer,不涉及task_turbo的其它feature

注:sched_ctl.c导出,echo <pid> <n> > cpu_prefer,n取值none=0,big=1,lit=2,med=3,直接设置到p->perfer_cpu,cfs和rt选核路径中在选核结果上select_task_prefer_cpu()中使用perfer_cpu属性重新选核.

h. p->cpu_prefer 的清0总结
/sys/module/task_turbo/parameters/unset_turbo_pid //只复位一个任务的设置,CONFIG_MTK_TASK_TURBO的接口
/sys/module/task_turbo/parameters/feats //写为0复位turbo_pid[8]中所有的任务,CONFIG_MTK_TASK_TURBO的接口

10. task_turbo 相关trace:

sys_set_turbo_task //设置p->render 
    set_turbo_task 
    add_turbo_list 
        trace_turbo_set 
 
sched_set_cpuprefer 
    trace_sched_set_cpuprefer(p); //trace选核倾向的cluster 
 
set_user_nice 
    trace_sched_set_user_nice 
 
set_scheduler_tuning 
    trace_sched_turbo_nice_set 
 
rwsem_start_turbo_inherit 
binder_start_turbo_inherit 
    trace_turbo_inherit_start(from, to); 
 
rwsem_stop_turbo_inherit 
    trace_turbo_inherit_end(current);

11. 总结
整个 task_turbo feature 只是对上大核比较激进,并且有binder、resem继承,cgroup top-app分组名为 RenderThread 的线程进行设置。只是迁核,没有涉及对task的util的更新,这块可以做一下,尤其是针对 RenderThread 的,方便做一些。


12. 实测

(1) 测试 CONFIG_MTK_TASK_TURBO

/sys/module/task_turbo/parameters # ps -AT | grep system_server 
system        1565  1565   795 23058820 491980 SyS_epoll_wait     0 S system_server 
 
/sys/module/task_turbo/parameters # echo 15 > feats //必须先执行 
/sys/module/task_turbo/parameters # echo 1565 > turbo_pid 
/sys/module/task_turbo/parameters # echo 1565 > unset_turbo_pid //抓trace后清理设置,若将feats文件设置为0清理全部

task_turbo 选到 cpu7 上,需满足 cpu7 是online的、非isolated状态的、是在任务的cpus_allow里面的、此时idle不idle都行。一旦cpu7没选上,task_turbo 的 cpu_prefer 特性就不参与选核了。

结果:1565 虽然跑的少,每次运行时间短,但是运行在大核上。

结论:跑在大核上,符合预期。

(2) 测试 CONFIG_MTK_SCHED_BOOST

/sys/devices/system/cpu/sched # ps -AT | grep system_server 
system        1540  1540   809 23600516 456832 SyS_epoll_wait     0 S system_server 
 
/sys/devices/system/cpu/sched # echo 1540 1 > cpu_prefer //cpu prefer大核

结果:
a. system_server 虽然跑的很少,每次跑的也很短,但是每次都能跑在大核上,符合预期。
b. 写3从中核开始选的一致性要差一些,但是绝大多数情况下也是在中核上。

清理设置:

/sys/devices/system/cpu/sched # echo 1540 0 > cpu_prefer

结论:从哪个cluster开始选核,就大概率会选择上哪个cluster的CPU

(3) sched_boost 情况下验证自己加的过滤策略

/sys/devices/system/cpu/sched # echo 1 > sched_boost //使能自己对前后台区分的过滤 
/sys/devices/system/cpu/sched # let i=0; while true; do if [ i -lt 100 ]; then let i=i+1; else let i=0; sleep 0.01; fi; done & 
[1] 16018 
# echo 16018 > /dev/stune/top-app/tasks 
# echo 15 > /sys/module/task_turbo/parameters/feats       //必须先执行 
# echo 16018 > /sys/module/task_turbo/parameters/turbo_pid 
/dev/stune/top-app # let i=0; while true; do if [ i -lt 100 ]; then let i=i+1; else let i=0; sleep 0.01; fi; done & 
[2] 31467 
/dev/stune/top-app # echo 31467 > /sys/module/task_turbo/parameters/turbo_pid 
/dev/stune/top-app # echo 31467 > /dev/stune/background/tasks

实验结果:16018 跑大核CPU7,31467 跑小核,符合预期。只有使能了 sched_boost,自己加的这个过滤才生效,到时候功耗差的话,可以全局打开。

(4) 补充实验:
既然是死循环计数,就可以用来验证算力,可以将大核和小核都定到最大频点对比一下
16018: 1.7ms,频点2G
31467: 4ms,频点3G
结论:同频点下算力差:4/1.7/(3/2) = 1.57 倍,也即是同频点下大核比小核快1.57倍。看 cpu_capacity 文件的值再这算也行。

13. task_prefer_match/task_prefer_fit 中是否也要加入对中核支持的判断 //从15的分析中可以看出,还是需要加的!

(1) task_prefer_match 返回值差异的影响

static void select_task_prefer_cpu_fair(struct task_struct *p, int *result) //eas_plus.c 
{ 
    int task_prefer; 
    int cpu, new_cpu; 
 
    task_prefer = cpu_prefer(p); //赋值了又没有使用,like a shit 
 
    cpu = (*result & LB_CPU_MASK); //从掩码中取出cpu的值 
 
    new_cpu = select_task_prefer_cpu(p, cpu); //在之前选核的基础上再选一次 
 
    if ((new_cpu >= 0)  && (new_cpu != cpu)) { 
        if (task_prefer_match(p, cpu)) //SFL_TODO: 待加上对中核的支持 
            *result = new_cpu | LB_THERMAL; 
        else 
            *result = new_cpu | LB_HINT; 
    } 
} 
//调用路径: 
select_task_rq_fair 
    select_task_prefer_cpu_fair

影响上只是 select_task_rq_fair 中 trace_sched_select_task_rq 的 result 的掩码不同而已,没有什么影响。

(2) task_match_on_dst_cpu

(2) task_match_on_dst_cpu 
 
inline int task_match_on_dst_cpu(struct task_struct *p, int src_cpu, int target_cpu) //eas_plus.c 
{ 
    struct task_struct *target_tsk; 
    struct rq *rq = cpu_rq(target_cpu); 
 
#ifdef CONFIG_MTK_SCHED_BOOST 
    if (task_prefer_match(p, src_cpu)) 
        return 0; 
 
    target_tsk = rq->curr; 
    if (task_prefer_fit(target_tsk, target_cpu)) 
        return 0; 
#endif 
 
    return 1; 
}

调用路径:没有任何调用

结论:不需要加对中核的判断,因为没有任何实质的影响。

14. #define hmp_cpu_domain(cpu) (per_cpu(hmp_cpu_domain, (cpu))) 这个domain, 从 hmp_cpu_is_slowest(cpu) 和 hmp_cpu_is_fastest(cpu) 可以看出, 链表头是算力最大的cluster,尾是最低算力的cluster。

15. sched_boost_type 的作用

(1) sched_boost_type 设置位置

int sched_boost_type = SCHED_NO_BOOST; // 对应设置文件:/sys/devices/system/cpu/sched/sched_boost 
//取值范围: 
enum { 
    SCHED_NO_BOOST = 0, 
    SCHED_ALL_BOOST, 
    SCHED_FG_BOOST, 
    SCHED_UNKNOWN_BOOST 
}; 
设置路径: 
/sys/devices/system/cpu/sched/sched_boost 对应的设置文件 
    store_sched_boost //sched_ctl.c 
        set_sched_boost 
            sched_boost_type = val;

(2) sched_boost_type 使用位置

int cpu_prefer(struct task_struct *p) 
{ 
    if (sched_boost_type == SCHED_ALL_BOOST) 
        return SCHED_PREFER_BIG; 
    ... 
} 
调用路径: 
hmp_force_up_migration //hmp.c 检查需要迁移到大核的任务 
hmp_force_migration //看是否有任务要pull过来 
    hmp_get_heaviest_task //对 prefer_little 的 se 做一定的过滤 
        task_prefer_little 
hmp_force_down_migration 
    hmp_get_lightest_task //对 prefer_big 的 se 做一定的过滤 
        task_prefer_big 
    task_match_on_dst_cpu //fit返回0,否则返回1 
    load_balance //最忙的cpu上正在运行的任务fit最忙的cpu,就退出 
        task_prefer_fit 
    select_task_prefer_cpu_fair //选核路径中只是影响一个打印标志,没啥实际作用 
    task_match_on_dst_cpu //满足直接返回0,但是这个函数在内核中没人调用,所以也没啥实际作用 
        task_prefer_match 
hmp_slowest_idle_prefer_pull //eas_plus.c 
hmp_fastest_idle_prefer_pull //eas_plus.c 
    get_idle_prefer_task //eas_plus.c //若判断不match的话直接返回 
        task_prefer_match_on_cpu 
        select_task_prefer_cpu //在选核的结果上再次从大核开始选核心,但是有可以将其改为从中核开始选 
            cpu_prefer

结论:好像是迁移过程中更倾向于大核。之前的笔记:0关闭,1大核优先,所有task优先先用大核,2表示top-app,foreground优先用大核,对应PERF_RES_SCHED_BOOST。


本文参考链接:https://www.cnblogs.com/hellokitty2/p/16340660.html