mtk task_turbo 阅读笔记

基于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)) //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。

 

posted on 2022-06-03 20:55  Hello-World3  阅读(538)  评论(0编辑  收藏  举报

导航