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) 编辑 收藏 举报