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

原文地址:https://www.cnblogs.com/hellokitty2/p/15650680.html