zoukankan      html  css  js  c++  java
  • 调度器16—core_ctl Hello

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

  • 相关阅读:
    day01--计算机硬件基础笔记
    22 Jun 18 Django,ORM
    21 Jun 18 Django,ORM
    20 Jun 18 复习, mysql
    20 Jun 18 Django,ORM
    19 Jun 18 复习, 正则表达式
    19 Jun 18 Django
    15 Jun 18 复习, shutil模块
    15 Jun 18 Django
    14 Jun 18 复习, form表单
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15650680.html
Copyright © 2011-2022 走看看