Skip to main content
 首页 » 操作系统

Linux 调度器之core_ctl

2022年07月19日54lonelyxmas

基于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,但是追踪代码,暂时还没有看到哪里设置的。


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