基于MTK Linux-5.10
一、相关文件接口
1. parameters文件接口
/sys/module/mtk_core_ctl/parameters # ls -l -rw------- 1 root root debug_enable //控制 core_ctl.c 中 core_ctl_debug() 的打印,TAG为"core_ctl" -rw-rw---- 1 system system policy_enable
(1) debug_enable
默认为false, 控制 core_ctl.c 中 core_ctl_debug() 的打印,TAG为"core_ctl"
(2) policy_enable
默认为false, 从 demand_eval() 来看,若 policy_enable 文件没有使能的话,那么 need_cpus 直接取 cluster->max_cpus,此时cpu核的isolate/ioslate只受用户空间通过core_ctl下面的文件节点和是否boost进行设置了。
2. core_ctl文件接口
/sys/devices/system/cpu/cpu0/core_ctl # ls -l -rw------- 1 root root core_ctl_boost -rw------- 1 root root enable -r-------- 1 root root global_state -rw-rw-r-- 1 root root max_cpus -rw-rw-r-- 1 root root min_cpus -rw------- 1 root root not_preferred -rw------- 1 root root offline_throttle_ms -r-------- 1 root root ppm_state //显示一个表 -r-------- 1 root root thermal_up_thres -rw------- 1 root root up_thres
(1) core_ctl_boost
对应 cluster_data::boost 成员,默认是false,设置为1是对所有cluster进行boost,在 demand_eval() 中判断,若是boot状态的话,need_cpus 直接取 cluster->max_cpus,也就是不再执行实际的isolate动作了,若是有isolate的cpu,需要unisolate。
(2) enable
对应 cluster_data::enable 成员,默认是true,在 demand_eval() 中判断,若是没有enable的话,need_cpus 直接取 cluster->max_cpus,也就是不再执行实际的isolate动作了,若是有isolate的cpu,需要unisolate。
(3) global_state
打印cluster中cpu的Active cpu个数,Need cpu个数,Paused cpu个数,以及cluster内各个cpu的 oneline、pause、busy、prefer 状态,见下面cat的内容。
(4) max_cpus
对应 cluster_data::max_cpus 成员,线程do_core_ctl()中在执行实际isolate/unisolate之前会先执行 apply_limits(cluster, cluster->need_cpus) 将 need_cpus 钳制在 cluster->min_cpus 和 cluster->max_cpus 之间,也就是说默认逻辑会尊重用户空间对cpu核数的限制,用户空间的限制优先级最高,高于 core_ctl_tick() 执行逻辑中预估的核数。但是通过 eas_ioctl 文件设置下来的不在尊重用户空间对cpu的限制了。
(5) min_cpus
对应 cluster_data::min_cpus 成员,各个cluster默认取值default_min_cpus[MAX_CLUSTERS] = {4, 2, 0}。设置后立即唤醒"core_ctl_v2/X"线程执行isolate/unisolate操作。
(6) not_preferred
对应 cluster_data::not_preferred 成员,如果标记了某些CPU是not_preferred,那么在 try_to_pause() 中 isolate CPU的时候就会优先isolate这些not_preferred的CPU,若是not_preferred的CPU都已经isolated了还没达到 active_cpus == need 这个条件,那么就继续isolate没有被标记为not_preferred的CPU。
二、core_ctl设置路径
1. scheduler_tick 周期更新cpu核数需求,触发isolate/unisolate
(1) 调用路径
scheduler_tick //core.c core_ctl_tick //core_ctl.c trace_android_vh_scheduler_tick(rq) 将per_cpu的4ms的窗口转化为全局4ms的窗口,每4ms实际调用一次 if (enable_policy) core_ctl_main_algo(); //通过一定算法更新 cluster->new_need_cpus apply_demand //core_ctl.c 对每一个cluster都调用 for_each_cluster(cluster, index) apply_demand(cluster) //core_ctl.c if (demand_eval(cluster)) wake_up_core_ctl_thread(cluster); //唤醒per-cluster的内核线程"core_ctl_v2/X" try_core_ctl //core_ctl.c per-cluster的内核线程"core_ctl_v2/X",内核优先级为0的RT线程,平时休眠,有core control需求时唤醒它 do_core_ctl
(2) 相关函数
static void __ref do_core_ctl(struct cluster_data *cluster) //core_ctl.c { ... //返回将 cluster->need_cpus 钳制在 cluster->min_cpus 和 cluster->max_cpus 之间的值 need = apply_limits(cluster, cluster->need_cpus); //need小于cluster->active_cpus 或 need大于cluster->active_cpus并且cluster->nr_paused_cpus不为0 if (adjustment_possible(cluster, need)) { if (cluster->active_cpus > need) try_to_pause(cluster, need); else if (cluster->active_cpus < need) try_to_resume(cluster, need); } ... } try_to_pause //core_ctl.c 一直去pause,直到 cluster->active_cpus 等于参数 need,过程中实时更新 cluster->active_cpus 和 cluster->nr_paused_cpus sched_pause_cpu //core_pause.c pause一个cpu pause_cpus //kernel/cpu.c try_to_resume //core_ctl.c 一直去resume,直到 cluster->active_cpus 等于参数 need,过程中实时更新 cluster->active_cpus 和 cluster->nr_paused_cpus sched_resume_cpu //core_pause.c resume一个cpu resume_cpus //kernel/cpu.c static void try_to_pause(struct cluster_data *cluster, int need) { unsigned long flags; unsigned int num_cpus = cluster->num_cpus; //检查此cluster中是否有标记not_preferred cpu bool check_not_prefer = cluster->nr_not_preferred_cpus; bool check_busy = true; again: for (cpu = nr_cpu_ids-1; cpu >= 0; cpu--) { struct cpu_data *c; success = false; if (!cpumask_test_cpu(cpu, &cluster->cpu_mask)) continue; if (!num_cpus--) break; c = &per_cpu(cpu_state, cpu); if (!is_active(c)) continue; //若此cluster中只要有一个cpu的算力使用百分比c->cpu_util_pct 不低于 cluster->cpu_busy_up_thres 就认为是busy if (check_busy && c->is_busy) continue; //per_ioctl强制isolate的cpu if (c->force_paused) continue; //直到active==need才退出pause,否则一直尝试pause if (cluster->active_cpus == need) break; //仅 Pause not_preferred 的 CPU,如果没有 CPU 被选为 not_preferred,则所有 CPU 都符合隔离条件。 if (check_not_prefer && !c->not_preferred) continue; //执行isolate cpu 操作 if (!sched_pause_cpu(c->cpu)) { if (cpu_online(c->cpu)) //记录是由core_ctl isolate 的 c->paused_by_cc = true; } cluster->active_cpus = get_active_cpu_count(cluster); } cluster->nr_paused_cpus += nr_paused; if (check_busy || (check_not_prefer && cluster->active_cpus != need)) { num_cpus = cluster->num_cpus; check_not_prefer = false; //改为false重新试一次 check_busy = false; goto again; } }
sched_pause_cpu --> pause_cpus
//参数为要pause的cpu的mask int pause_cpus(struct cpumask *cpus) //kernel/cpu.c { ... if (cpu_hotplug_disabled) { //需要没有禁止 cpu_hotplug 才能pause err = -EBUSY; goto err_cpu_maps_update; } //只能对active的cpu进行pause cpumask_and(cpus, cpus, cpu_active_mask); for_each_cpu(cpu, cpus) { //cpu是offline的,或dl任务带宽不够,是不能pasue的 if (!cpu_online(cpu) || dl_cpu_busy(cpu) || get_cpu_device(cpu)->offline_disabled == true) { err = -EBUSY; goto err_cpu_maps_update; } } //不能pause所有的active的cpu if (cpumask_weight(cpus) >= num_active_cpus()) { err = -EBUSY; goto err_cpu_maps_update; } //将要pause的cpu设置为非active的状态,就是从 cpu_active_mask 中清除掉 for_each_cpu(cpu, cpus) set_cpu_active(cpu, false); //被isolate的cpu不会再出现在 cpu_active_mask 中 ###### //进行pause err = __pause_drain_rq(cpus); trace_cpuhp_pause(cpus, start_time, 1); return err; }
2. perf_ioctl 中强制core_ctl接口
/proc/perfmgr/eas_ioctl 这里会强制进行core_ctl
static long eas_ioctl_impl(struct file *filp, unsigned int cmd, unsigned long arg, void *pKM) //perf_ioctl.c { struct _CORE_CTL_PACKAGE msgKM = {0}; ... switch (cmd) { case CORE_CTL_FORCE_PAUSE_CPU: //这是强制进行核隔离 if (perfctl_copy_from_user(&msgKM, ubuf, sizeof(struct _CORE_CTL_PACKAGE))) return -1; bval = !!msgKM.is_pause; ret = core_ctl_force_pause_cpu(msgKM.cpu, bval); break; ... } ... } //is_pause: 1 pause, 0 resume int core_ctl_force_pause_cpu(unsigned int cpu, bool is_pause) { int ret; struct cpu_data *c; struct cluster_data *cluster; ... if (!cpu_online(cpu)) return -EBUSY; c = &per_cpu(cpu_state, cpu); cluster = c->cluster; //执行实际的pause和resume if (is_pause) ret = sched_pause_cpu(cpu); else ret = sched_resume_cpu(cpu); //标记是force接口pause的 c->force_paused = is_pause; if (c->paused_by_cc) { c->paused_by_cc = false; cluster->nr_paused_cpus--; } cluster->active_cpus = get_active_cpu_count(cluster); return ret; }
若是通过 perf_ioctl 接口强制isolate的CPU,其 cpu_data::force_paused 会设置为1,是直接调用 sched_pause_cpu/sched_resume_cpu进行隔离和取消隔离的。在原路径"core_ctl_v2/X"线程中isolate/unisolate执行流程中会跳过设置了 c->force_paused 标志位的CPU,也就是说force isolate的CPU必须要force接口unisolate!
3. 通过 max_cpus/min_cpus 文件接口设置
通过设置 /sys/devices/system/cpu/cpuX/core_ctl 下的 max_cpus、min_cpus 文件接口进行设置,
static void set_min_cpus(struct cluster_data *cluster, unsigned int val) { ... cluster->min_cpus = min(val, cluster->max_cpus); ... //唤醒"core_ctl_v2/X"线程 wake_up_core_ctl_thread(cluster); } static void set_max_cpus(struct cluster_data *cluster, unsigned int val) //core_ctl.c { ... val = min(val, cluster->num_cpus); cluster->max_cpus = val; //这样的效果就是想限核只需要往 max_cpus 一个文件中echo一个值就可以了 cluster->min_cpus = min(cluster->min_cpus, cluster->max_cpus); ... //唤醒"core_ctl_v2/X"线程 wake_up_core_ctl_thread(cluster); } /sys/devices/system/cpu/cpu0/core_ctl # cat min_cpus 4 /sys/devices/system/cpu/cpu0/core_ctl # echo 1 > max_cpus /sys/devices/system/cpu/cpu0/core_ctl # cat max_cpus 1 /sys/devices/system/cpu/cpu0/core_ctl # cat min_cpus 1
总结:core_ctl_tick 和 max_cpus/min_cpus 设置路径都是通过唤醒优先级为0的RT线程"core_ctl_v2/X"来执行核隔离和取消隔离的,只不过前者更新核需求 new_need_cpus 参数,后者是增加核数限制。force路径是直接调用pause接口进行隔离和取消隔离,而且其操作过的cpu不受"core_ctl_v2/X"线程的影响。resume_cpus 是相反操作。
三、调试log
1. 相关trace
(1) trace_core_ctl_demand_eval
//调用传参: demand_eval trace_core_ctl_demand_eval(cluster->cluster_id, old_need, new_need, cluster->active_cpus, cluster->min_cpus, cluster->max_cpus, cluster->boost, cluster->enable, ret && need_flag); //trace打印: <idle>-0 [006] d.h3 2007.792026: core_ctl_demand_eval: cid=0, old=2, new=4, act=2 min=0 max=4 bst=0 enbl=1 update=1 core_ctl_v2/0-463 [006] d.h3 2007.796037: core_ctl_demand_eval: cid=0, old=4, new=4, act=3 min=0 max=4 bst=0 enbl=1 update=1
打印依次为传入的参数,只有 update=1 才会唤醒core_ctl线程,执行进一步isolate/unisolate操作。
(2) trace_core_ctl_algo_info
//调用传参: core_ctl_main_algo trace_core_ctl_algo_info(big_cpu_ts, heaviest_thres, max_util, cpumask_bits(cpu_active_mask)[0], orig_need_cpu); //trace打印: sh-18178 [004] d.h2 18903.565478: core_ctl_algo_info: big_cpu_ts=67692 heaviest_thres=770 max_util=786 active_cpus=f1 orig_need_cpus=4|9|6
big_cpu_ts: 是大核cpu7的温度,67.692度
heaviest_thres: 作为判断是否需要开启大核的util门限,当温度低于65度时是中核 up_thres/100 * max_capacity, 高于65度时是 thermal_up_thres/100 * max_capacity
max_util:记录的是所有cpu上最大task的util,在每8ms执行一次的 sched_max_util_task_tracking() 中更新。
active_cpus:打印的是 cpu_active_mask,通过它可以看哪些cpu被隔离了或被设置为offline了,实测被isolate或offline都会体现到 cpu_active_mask 上。
orig_need_cpus:是个数组,依次打印各个cluster的 cluster->new_need_cpus 成员,就是评估出来的各个cluster需要的cpu核心的个数。
注:可以看到MTK的 new_need_cpus 的评估算法明显不行,飞行熄屏场景下竟然评估出各个cluster需要 4|9|6 个核。
(3) trace_core_ctl_update_nr_over_thres
//调用传参: scheduler_tick //core.c core_ctl_tick //core_ctl.c core_ctl_main_algo get_nr_running_big_task trace_core_ctl_update_nr_over_thres(nr_up, nr_down, max_nr) //trace打印: sh-18174 [006] dNh2 23927.901480: core_ctl_update_nr_over_thres: nr_up=1|0|0 nr_down=0|5|0 max_nr=2|4|4
分别打印的是每个cluster的 cluster_data 中的 nr_up, nr_down, max_nr,在 core_ctl_main_algo() 中评估 cluster->new_need_cpus 时使用。
由 "dNh2" 可知,此函数是在硬中断上下文中关中断执行的,此时抢占计数为2。
2. 开启 debug log
若是出问题了可以 echo 1 > /sys/module/mtk_core_ctl/parameters/debug_enable 打开调试log,看代码执行流程。
3. 总结:缺失是否 force_paused 的debug log。
四、CPU online/offline流程
执行:echo 0/1 > /sys/devices/system/cpu/cpuX/online
相关函数
struct bus_type cpu_subsys = { //driver/base/cpu.c .name = "cpu", .dev_name = "cpu", .match = cpu_subsys_match, #ifdef CONFIG_HOTPLUG_CPU .online = cpu_subsys_online, .offline = cpu_subsys_offline, #endif }; static ssize_t online_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) //driver/base/core.c { ... ret = strtobool(buf, &val); ret = val ? device_online(dev) : device_offline(dev); ... }
调用路径:
device_online dev->bus->online(dev) //也就是 cpu_subsys.online cpu_device_up(dev) cpu_up(dev->id, CPUHP_ONLINE) kobject_uevent(&dev->kobj, KOBJ_ONLINE); dev->offline = false; device_offline dev->bus->offline(dev); //也就是 cpu_subsys.offline cpu_device_down(dev); cpu_down(dev->id, CPUHP_OFFLINE) kobject_uevent(&dev->kobj, KOBJ_OFFLINE); dev->offline = true;
struct device 结构中只有 offline 成员,没有 online 成员。offline 调用路径中会去判断不会 offline 唯一 active 的 cpu,实测 offline cpu 会设置cpu_active_mask,但是追踪代码,暂时还没有看到哪里设置的。