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

  • 相关阅读:
    P2486 [SDOI2011]染色 (树链剖分)
    机房测试:Dove打扑克(vector暴力)
    机房测试:sort(归并+概率期望dp)
    区间覆盖问题总结(贪心)
    机房测试:停不下来的团长奥加尔(dp)
    博客目录
    团队作业week16
    Beta阶段项目展示
    Beta阶段项目终审报告
    Beta阶段测试报告
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/15650680.html
Copyright © 2011-2022 走看看