驱动篇:inux 电源管理的系统架构和驱动(四)
驱动篇:inux 电源管理的系统架构和驱动(四)
CPU 热插拔
Linux CPU 热插拔的功能已经存在相当长的时间了, Linux 3.8 之后的内核里一个小小的改进就是 CPU0 也可以热插拔。
一般来讲,在用户空间可以通过 /sys/devices/system/cpu/cpun/online 节点来操作一个 CPU 的在线和离线:
# echo 0>/sys/devices/system/cpu/cpu3/online
CPU 3 is now offline
# echo1 >/sys/devices/system/cpu/cpu3/online
通过 echo0>/sys/devices/system/cpu/cpu3/online 关闭 CPU3 的时候, CPU3 上的进程都会被迁移到其他的 CPU 上,以保证这个拔除 CPU3 的过程中,系统仍然能正常运行。一旦通过 echo 1>/sys/devices/system/cpu/cpu3/online 再次开启 CPU3 , CPU3 又可以参与系统的负载均衡,分担系统中的任务。
在嵌入式系统中, CPU 热插拔可以作为一种省电的方式,在系统负载小的时候,动态关闭 CPU ,在系统负载增大的时候,再开启之前离线的 CPU 。目前各个芯片公司可能会根据自身 SoC 的特点,对内核进行调整,来实现运行时 “ 热插拔 ” 。这里以 Nvidia 的 Tegra3 为例进行说明。
Tegra3 采用 vSMP ( variableSymmetric Multiprocessing )架构,共有 5 个 Cortex-A9 处理器,其中 4 个为高性能设计的G 核, 1 个为低功耗设计的 LP 核,如图所示:在系统运行过程中, Tegra3 的 Linux 内核会根据 CPU 负载切换低功耗处理器和高功耗处理器。除此之外, 4 个高性能 ARM 核心也会根据运行情况,动态借用 Linux 内核支持的 CPU 热插拔进行 CPU 的插入 / 拔出操作。
用华硕 EeePad 运行高负载、低负载应用,通过 dmesg 查看内核消息也确实验证了多核的热插拔以及 G 核和 LP 核之间的动态切换:
<4>[104626.426957] CPU1: Booted secondary processor
<7>[104627.427412] tegra CPU: force EDP limit 720000 kHz<4>[104627.427670] CPU2: Booted secondary processor
<4>[104628.537005] stop_machine_cpu_stop cpu=0
<4>[104628.537017] stop_machine_cpu_stop cpu=2
<4>[104628.537059] stop_machine_cpu_stop cpu=1
<4>[104628.537702] __stop_cpus: wait_for_completion_timeout+
<4>[104628.537810] __stop_cpus: smp=0 done.executed=1 done.ret =0-
<5>[104628.537960] CPU1: clean shutdown
<4>[104630.537092] stop_machine_cpu_stop cpu=0
<4>[104630.537172] stop_machine_cpu_stop cpu=2
<4>[104630.537739] __stop_cpus: wait_for_completion_timeout+
<4>[104630.538060] __stop_cpus: smp=0 done.executed=1 done.ret =0-
<5>[104630.538203] CPU2: clean shutdown
<4>[104631.306984] tegra_watchdog_touch
高性能处理器和低功耗处理器切换:
<3>[104666.799152] LP=>G: prolog 22 us, switch 2129 us, epilog 24 us, total 2175 us
<3>[104667.807273] G=>LP: prolog 18 us, switch 157 us, epilog 25 us, total 200 us
<4>[104671.407008] tegra_watchdog_touch
<4>[104671.408816] nct1008_get_temp: ret temp=35C
<3>[104671.939060] LP=>G: prolog 17 us, switch 2127 us, epilog 22 us, total 2166 us
<3>[104672.938091] G=>LP: prolog 18 us, switch 156 us, epilog 24 us, total 198 us
在运行过程中,我们发现 4 个 G 核会动态热插拔,而 4 个 G 核和 1 个 LP 核之间,会根据运行负载进行集群切换。这一部分都是在内核里面实现的,和 tegra 的 CPUF req 驱动( DVFS 驱动)紧密关联。
1 . 如何判断自己是什么核
每个核都可以通过调用 is_lp_cluster ()来判断当前正在执行的 CPU 是 LP 还是 G 处理器:
static inline unsigned int is_lp_cluster(void)
{
unsigned int reg;
reg =readl(FLOW_CTRL_CLUSTER_CONTROL);
return (reg& 1); /* 0 == G, 1 == LP */
}
即读 FLOW_CTRL_CLUSTER_CONTROL 寄存器判断自己是 G 核还是 LP 核。
2.G 核和 LP 核集群的切换时机
[场景 1 ]何时从 LP 核切换给 G 核:当前执行于 LP 集群, CPUFreq 驱动判断出 LP 核需要增频率到超过高值门限,即 TEGRA_HP_UP :
case TEGRA_HP_UP:
if(is_lp_cluster() && !no_lp) {
if(!clk_set_parent(cpu_clk, cpu_g_clk)) {
hp_stats_update(CONFIG_NR_CPUS, false);
hp_stats_update(0, true);
/* catch-upwith governor target speed */
tegra_cpu_set_speed_cap(NULL);
}
}
[场景 2 ]何时从 G 核切换给 LP 核:当前执行于 G 集群, CPUFreq 驱动判断出某 G 核需要降频率到低于低值门限,即 TEGRA_HP_DOWN ,且最慢的 CPUID 不小于 nr_cpu_ids (实际上代码逻辑跟踪等价于只有 CPU0 还活着的情况):
caseTEGRA_HP_DOWN:
cpu= tegra_get_slowest_cpu_n();
if(cpu < nr_cpu_ids) {
...
}else if(!is_lp_cluster() && !no_lp) {
if(!clk_set_parent(cpu_clk, cpu_lp_clk)) {
hp_stats_update(CONFIG_NR_CPUS, true);
hp_stats_update(0, false);
/* catch-upwith governor target speed */
tegra_cpu_set_speed_cap(NULL);
} else
queue_delayed_work(
hotplug_wq, &hotplug_work, down_delay);
}
break;
切换实际上就发生在 clk_set_parent ()更改 CPU 的父时钟里面,这部分代码写得比较不好, 1 个函数完成 n 个功能,实际上不仅切换了时钟,还切换了 G 和 LP 集群:
clk_set_parent(cpu_clk, cpu_lp_clk) ->
tegra3_cpu_cmplx_clk_set_parent(structclk *c, struct clk *p) ->
tegra_cluster_control(unsigned int us, unsigned int flags) ->
tegra_cluster_switch_prolog()->
tegra_cluster_switch_epilog()
3.G 核动态热插拔
何时进行 G 核的动态插拔,具体如下。
[场景 3 ]当前执行于 G 集群, CPUFreq 驱动判断出某 G 核需要降频率到低于低值门限,即 TEGRA_HP_DOWN ,且最慢的 CPUID 小于 nr_cpu_ids (实际上等价于还有两个或两个以上的 G 核活着的情况),关闭最慢的 CPU ,留意 tegra_get_slowest_cpu_n ()不会返回 0 ,这意味着 CPU0 要么活着,要么切换给了 LP 核,对应于[场景 2 ]:
caseTEGRA_HP_DOWN:
cpu= tegra_get_slowest_cpu_n();
if(cpu < nr_cpu_ids) {
up = false;
queue_delayed_work(
hotplug_wq,&hotplug_work, down_delay);
hp_stats_update(cpu, false);
}
[场景 4 ]当前执行于 G 集群, CPUFreq 驱动判断出某 G 核需要设置频率大于高值门限,即TEGRA_HP_UP ,如果负载平衡状态为 TEGRA_CPU_SPEED_BALANCED ,再开一个核;如果状态为TEGRA_CPU_SPEED_SKEWED ,则关一个核。 TEGRA_CPU_SPEED_BALANCED 的含义是当前所有 G 核要求的频率都高于最高频率的 50% , TEGRA_CPU_SPEED_SKEWED 的含义是当前至少有两个 G 核要求的频率低于门限的 25% ,即 CPU 频率的要求在各个核之间有倾斜。
caseTEGRA_HP_UP:
if(is_lp_cluster() && !no_lp) {
...
}else {
switch (tegra_cpu_speed_balance()) {
/* cpu speed is up and balanced - one more on-line */
case TEGRA_CPU_SPEED_BALANCED:
cpu =cpumask_next_zero(0, cpu_online_mask);
if(cpu <nr_cpu_ids) {
up =true;hp_stats_update(cpu, true);
}
break;
/* cpu speed is up, but skewed - remove one core */
case TEGRA_CPU_SPEED_SKEWED:
cpu =tegra_get_slowest_cpu_n();
if(cpu < nr_cpu_ids) {
up =false;
hp_stats_update(cpu, false);
}
break;
/* cpu speed is up, butunder-utilized - do nothing */
case TEGRA_CPU_SPEED_BIASED:
default:
break;
}
}
上述代码中 TEGRA_CPU_SPEED_BIASED 路径的含义是有 1 个以上 G 核的频率低于最高频率的 50% 但是还未形成 “SKEWED” 条件,即只是 “BIASED” ,还没有达到 “SKEWED” 的程度,因此暂时什么都不做。
目前, ARM 和 Linux 社区都在从事关于 big.LITTLE 架构下, CPU 热插拔以及调度器方面有针对性的改进工作。在big.LITTLE 架构中,将高性能且功耗也较高的 Cortex-A15 和稍低性能且功耗低的 Cortex-A7 进行了结合,或者在 64位下,进行 Cortex-A57 和 Cortex-A53 的组合,如图 19.8 所示。
big.LITTLE 架构的设计旨在为适当的作业分配恰当的处理器。 Cortex-A15 处理器是目前已开发的性能最高的低功耗 ARM 处理器,而 Cortex-A7 处理器是目前已开发的最节能的 ARM 应用程序处理器。可以利用 Cortex-A15 处理器的性能来承担繁重的工作负载,而用 Cortex-A7 可以最有效地处理智能手机的大部分工作负载。这些操作包括操作系统活动、用户界面和其他持续运行、始终连接的任务。