• mtk task_turbo 阅读笔记 Hello


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

  • 相关阅读:
    [Linux] expect命令 (自动交互脚本)
    [MAC] 终端bash_profile配置不生效问题
    [IDEA] 开发常用插件
    [MAC] 环境常用工具
    [IDEA] 快捷键输出固定代码模板
    家庭网络-多无线路由器实现无缝漫游
    家庭网络-AP组网方案(POE供电)
    家庭网络-软路由搭建方案
    队列使用
    [多线程] 线程池的使用
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/16340660.html
Copyright © 2020-2023  润新知