一般ARM SOC包含能效和性能两个cluster,共8个 core,可以把这8个core统统开放给kernel,让kernel的调度器(scheduler)根据系统的实际情况,决定哪些任务应该在哪些core上执行。但这存在一个问题:
当前linux kernel的scheduler,都是针对SMP系统设计的,而SMP系统中所有CPU core的性能和功耗都是相同的。
linux kernel使用如下的软件框架实现ARM big·Little切换功能:
arm big little cpufreq driver,位于drivers/cpufreq/目录中,负责和cpufreq framework对接,以CPU调频的形式,实现ARM big·Little的切换。
arm bL switcher,是一个arch-dependent的driver,提供实际的切换操作。
arm big little cpufreq driver位于drivers/cpufreq目录下,由三个文件组成:
arm_big_little.c
arm_big_little.h
arm_big_little_dt.c
1)支持两个cluster。
2)当bL switching处于disable状态时,该driver就是一个普通的cpufreq driver,并为每个cluster提供一个frequency table(保存在freq_table[0]、freq_table[1]中)。
此时所有的big、Little core对系统都可见,每个core都可以基于cpufreq framework调整频率。
3)当bL switching处于enable状态时,该driver变成一个特殊的cpufreq driver,在调整频率的时候,可以根据情况,切换core的cluster。
此时只有4个虚拟的core对系统可见,系统不关心这些core属于哪个cluster、是big core还是Little core;
确切的说,每一个虚拟的core,代表了属于两个cluster的CPU对,可以想象为big+Little组合,只是同一时刻,只有一个core处于enable状态(big or Little);
该driver会搜集2个cluster的frequency table,并合并成一个(保存在freq_table[2]中)。合并后,找出这些frequency中big core最小的那个(clk_big_min)以及Little core最大的那个(clk_little_max);
基于cpufreq framework进行频率调整时,如果所要求的频率小于clk_big_min,则将该虚拟core所对应的Little core使能,如果所要求得频率大于clk_little_max,则将该虚拟core所对应的big core使能。
4)基于上面的描述,ARM big·Little driver会把big·Little架构下的8个CPU core,以“big+Little”的形式组合成4对,同一时刻,每对组合的core只有一个处于运行状态,这就是Linux kernel ARM big·Little架构的核心思路。
arm bL switcher driver是一个arch-dependent的driver,以ARM平台为例,其source code包括:
arch/arm/common/bL_switcher.c
arch/arm/common/bL_switcher_dummy_if.c
该driver的功能如下:
1)提供bL switcher功能的enable和disable控制
2)通过sysfs,允许用户空间软件控制bL switcher的使能与否
接口文件位于:
/sys/kernel/bL_switcher/active
读取可以获取当前的使能情况,写1 enable,写0 disable。
3)为每个虚拟的CPU core(big+Little组合),创建一个线程,实现最终的cluster切换。该部分是平台相关的,后面将会以ARM平台为例介绍具体的过程。
本章以ARM平台为例,结合kernel source code,从初始化以及cluster切换两个角度,介绍ARM big·Little driver的核心功能。
和ARM big·Little driver有关的初始化过程主要分为三个部分:
1)CPU core的枚举和初始化,具体可参考“Linux CPU core的电源管理(5)_cpu control及cpu hotplug”中有关possible CPU、present CPU的描述。
2)arm big little cpufreq driver的初始化,为每个cluster创建一个frequency table,并主持相应的cpufreq driver。
3)arm bL switcher driver,初始化bL switcher,并使能bL switcher。
下面我们重点介绍步骤2和步骤3。
以包含两个cluster,每个cluster有4个CPU core的系统为例,start_kernel之后,系统的possible CPU包含所有的8个core。
然后arm big little cpufreq driver出场了,其init接口位于“drivers/cpufreq/arm_big_little_dt.c”中
kernel将arm big little cpufreq driver注册成了一个简单的platform driver,因此driver的入口就是其probe函数:generic_bL_probe。
1)generic_bL_probe
generic_bL_probe接口很简单,以dt_bL_ops为参数,调用bL_cpufreq_register接口,注册cpufreq driver。dt_bL_ops是一个struct cpufreq_arm_bL_ops类型的变量,提供两个回调函数,分别用于获取cluster切换之间的延迟,以及初始化opp table,后面用到的时候再介绍。
2)bL_cpufreq_register
bL_cpufreq_register位于“drivers/cpufreq/arm_big_little.c”中,主要负责如下事情:
a)执行一些初始化动作。
b)调用cpufreq_register_driver接口,注册名称为bL_cpufreq_driver的cpufreq driver。
c)调用arm bL switcher driver提供的bL_switcher_register_notifier接口,向该driver注册一个notify,当bL switcher enable或者disable的时候,该driver会通知arm big little cpufreq driver,以完成相应的动作。
3)bL_cpufreq_driver
bL_cpufreq_driver代表了具体的cpufreq driver,提供了.init()、.verify()、.target_index()等回调函数
cpufreq driver以及相关的回调函数说明如下:
.init()是cpufreq driver的入口函数,当该driver被注册到kernel中后,cpufreq core就会调用该回调函数,一般在init函数中初始化CPU core有关的frequency table,并依据该table填充相应的cpufreq policy变量。
.verify()可用于校验某个频率是否有效。
.target_index()可将CPU core设置为某一个频率,在本文的场景中,可以在修改频率是进行cluster切换,后面会详细介绍。
4)bL_cpufreq_init
bL_cpufreq_driver被注册后,cpufreq core就会调用bL_cpufreq_init接口,完成后续的初始化任务,该接口比较重要,是arm big little cpufreq driver的精髓
该接口根据当前bL switcher的使能情况(由cpu_to_cluster的返回值判断,如果等于MAX_CLUSTERS,bL switcher处于enable状态,否则,为disable状态),有两种截然不同行为。
bL switcher disable时(由于arm big little cpufreq driver先于arm bL switcher driver初始化,它初始化时,bL switcher处于disable状态):
调用get_cluster_clk_and_freq_table接口,为当前CPU所在的cluster创建frequency table,结果保存在freq_table[cluster]中
将和当前cpu以及同属于一个cluster的所有其它CPU都保存在policy->cpus中,并将它们的physical_cluster设置为当前CPU的cluster
以上逻辑的背后思路是:如果bL switcher没有enable,arm big little cpufreq driver就是一个普通的cpufreq driver,此时每个cluster的所有CPU,共享同一个frequency table、cpufreq policy,也就是说,一个cluster下的所有CPU core,共享同一个调频策略
bL switcher enable时(会复杂一些):
调用get_cluster_clk_and_freq_table接口,搜集两个cluster下CPU的frequency信息,并以升序的形式合并到一个frequency table中(freq_table[MAX_CLUSTERS]),找出这些frequency中big core最小的那个(clk_big_min)以及Little core最大的那(clk_little_max)
将当前CPU的physical_cluster变量设置为当前A15_cluster,默认初始化时为big core模式
以上逻辑背后的思路是:如果bL switcher enable,则所有的CPU core共用一个合并后的frequency table,并由一个调频策略统一调度,具体方法后面再详细介绍
bL_cpufreq_init的核心实现是get_cluster_clk_and_freq_table接口,该接口会根据bL switcher的使能情况,初始化不同的frequency table,并在bL switcher enable的时候,将不同cluster的frequency合并到一起。具体代码就不再详细分析,这里强调一下里面的一个小技巧:
为了让bL switcher逻辑顺利执行,有必要尽量准确的区分big core和Little core的频率,get_cluster_clk_and_freq_table使用了一个简单的方法:
对Little core来说,统一把frequency除以2,使用的时候再乘回来,这就基本上可以保证Little core的frequency位于合并后的频率表的前面位置,big core位于后面位置。
arm bL switcher driver的初始化接口是bL_switcher_init,由于它使用late_init宏声明,因此会在靠后的时机初始化,该接口主要完成两个事情:
1)如果no_bL_switcher参数不为1(默认为0),则调用bL_switcher_enable接口,使能bL switcher。
2)调用bL_switcher_sysfs_init接口,初始化bL switcher模块提供的sysfs API。
因此,arm bL switcher driver的初始化,就转移到bL_switcher_enable上面了。
bL switcher的使能与否,是由arm bL switcher driver控制的,以enable为例,enable的时机有两个:
1)arm bL switcher driver初始化的时候,调用bL_switcher_enable。
2)通过sysfs(/sys/kernel/bL_switcher/active)使能
bL_switcher_enable主要工作如下:
调用bL_activation_notify,向arm big little cpufreq driver发送BL_NOTIFY_PRE_ENABLE通知,cpufreq driver收到该通知后,会调用cpufreq_unregister_driver,将bL_cpufreq_driver注销(具体可参考drivers/cpufreq/arm_big_little.c中的bL_cpufreq_switcher_notifier接口)。
调用bL_switcher_halve_cpus接口,将系统所有possible的CPU core配对,并关闭不需要的core。该接口是本文的精髓,后面会稍微详细的介绍。
为每个处于online状态的CPU core(此处已经是虚拟的core了,该core是一个big/Little对,同一时刻只有一个core开启),初始化用于cluster switch的线程。
将bL_switcher_active置1,此时bL switcher正式enable了,向arm big little cpufreq driver发送BL_NOTIFY_POST_ENABLE通知,cpufreq driver会重新注册bL_cpufreq_driver。
bL_switcher_halve_cpus是ARM big·Little driver灵魂式的存在,它负责把系统8个big+Little core转化成4个虚拟的CPU core
bL_switcher_halve_cpus会将不同cluster的core组成一对,最终的结果保存在bL_switcher_cpu_pairing数组中,配对之后,把core 4~7 disable掉,就保证系统当前只有4个虚拟的CPU core了。
bL_switcher_enable前后,会通知arm big little cpufreq driver,该driver会把bL_cpufreq_driver注销之后再重新注册,整个过程又回到了“arm big little cpufreq driver的初始化”过程。
最后,我们来看一下big core和Little core到底是怎么切换的。切换是由arm big little cpufreq driver的bL_cpufreq_set_target发起的,切换的主要动作包括:
1)在bL switcher处于enable状态时,对调度器而言,只有4个core可见,而且这4个core的logical map是不变的,例如都是0、1、2、3。每一个core在物理上和2个属于不同cluster的core对应,同一时刻只有一个物理core运行
2)这4个core所处的“状态”(哪个物理core处于运行状态,big or Little),记录在“physical_cluster”中。
3)当经由cpufreq framework进行频率调整的时候,根据当前的“状态”,以及要调整的目的频率,计算是否需要切换cluster(也即disable当前正在运行的物理core,enable另外一个物理core)。
4)最终,以当前“状态”(actual_cluster)、新“状态”(new_cluster)等为参数,调用bL_cpufreq_set_rate接口,设置频率。
bL_cpufreq_set_rate接口经过一番处理后,得到真实的频率值,调用clock framework提供的接口(clk_set_rate)修改频率。之后,如果old_cluster和new_cluster不同,则调用arm bL switcher driver提供的bL_switch_request接口,进行cluster切换。该接口会启动一个线程,完成切换动作。该线程的处理函数是bL_switcher_thread,它会以目的cluster为参数,调用bL_switch_to接口,完成最终的切换操作。