插图xxx
之前介绍了电源的开机和关机重启,本小节开始介绍省电的技术,其中最暴力的省电方法就是直接拔核hotplug处理,就像需要10个人干活都要吃饭,但是现在活少了最节省的方法就是砍掉几个人,有点像裁员啊
1. 省电技术概览
插图
对于省电,我们短时间不使用设备的时候可以进行休眠唤醒,长时间不使用就直接关机了。在使用设备的时候可以按照当前需要的性能进行调频处理就是CPUFreq和DevFeq,当没重度使用或者只运行系统必须进程的时候可以进行CPU休闲(CPUIdle)、CPU热插拔(CPU Hotplug)、CPU隔离(Core Isolate)和动态PM(Runtime PM)。
- CPUIdle指的是当某个CPU上没有进程可调度的时候可以暂时局部关掉这个CPU的电源,从而达到省电的目的,当再有进程需要执行的时候再恢复电源。
- CPU Hotplug指的是我们可以把某个CPU热移除,然后系统就不会再往这个CPU上派任务了,这个CPU就可以放心地完全关闭电源了,当把这个CPU再热插入之后,就对这个CPU恢复供电,这个CPU就可以正常执行任务了。
- CPU隔离指的是我们把某个CPU隔离开来,系统不再把它作为进程调度的目标,这样这个CPU就可以长久地进入Idle状态了,达到省电的目的。不过CPU隔离并不是专门的省电机制,我们把CPU隔离之后还可以通过set_affinity把进程专门迁移到这个CPU上,这个CPU还会继续运行。CPU隔离能达到一种介于CPUIdle和CPU热插拔之间的效果。
- Runtime PM指的是设备的动态电源管理,系统中存在很多设备,但是并不是每种设备都在一直使用,比如相机可能在大部分时间都不会使用,所以我们可以在大部分时间把相机的电源关闭,在需用相机的时候,再给相机供电。
cpu hotplug和idle的区别?hotplug是从硬件上拔掉核下电,idle只是从软件上进行处理,也就是说调度器在idle时只是不去调用但是核还是可见的,hotplug直接没这个核了,软件完全不可见。
省电管理可以达到省电的目的,但是也会降低系统的性能,包括响应延迟、带宽、吞吐量等。所以内核又提供了一个PM QoS框架,QoS是Quality Of Service(服务质量)。PM QoS框架一面向顾客提供接口,顾客可以通过这些接口对系统的性能提出要求,一面向各种省电机制下发要求,省电机制在省电的同时也要满足这些性能要求。PM QoS的顾客包括内核和进程:对于内核,PM QoS提供了接口函数可以直接调用;对于进程,PM QoS提供了一些设备文件可以让用户空间进行读写。PM QoS对某一项性能指标的要求叫做一个约束,约束分为系统级约束和设备级约束。系统级约束针对的是整个系统的性能要求,设备级约束针对的是某个设备的性能要求。
整体上电源管理也是策略和机制分离的,例如:
- hotplug是一个机制,谁去用?可以用户App制定的策略、温控策略等。
- CPUFreq是策略和机制都包含的。
###2. 热插拔代码介绍
cpu的状态包括:possible、present、online、active。
- possible状态的cpu:可理解为存在这个CPU资源,但还没有纳入Kernel的管理范围。
- present状态的cpu:表示已经被kernel接管。
- online状态的cpu:表示可以被调度器使用。
- active状态的cpu:表示可以被迁移migrate。
Linux内核在初始的时候,会创建虚拟总线cpu_subsys,每个cpu调用register_cpu注册时,都会将cpu设备挂在这个总线下。
cpu的拔插是通过操作文件节点online实现的,具体拔插操作如下(以cpu1为例):
echo 0 > /sys/devices/system/cpu/cpu1/online //拔核操作
echo 1 > /sys/devices/system/cpu/cpu1/online //插核操作
当操作/sys/devices/system/cpu/cpu1/online文件的时候,会执行drivers/base/core.c中online_store()函数
static ssize_t online_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count
)
{
bool val;
int ret;
ret = strtobool(buf, &val);
if (ret < 0)
return ret;
ret = lock_device_hotplug_sysfs();
if (ret)
return ret;
ret = val ? device_online(dev) : device_offline(dev);
unlock_device_hotplug();
return ret < 0 ? ret : count;
}
static DEVICE_ATTR_RW(online);
这块有一个sysfs的知识点,就是DEVICE_ATTR_RW(online);声明了这个宏,就可以在文件系统里面为这个设备熟悉添加一个文件,当向这个文件写入字符串的时候就会调用拼接出来的online_store()函数,读这个文件的时候就会调用online_show()函数
#define __ATTR(_name, _mode, _show, _store) { \
.attr = {.name = __stringify(_name), \
.mode = VERIFY_OCTAL_PERMISSIONS(_mode) }, \
.show = _show, \
.store = _store, \
}
#define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)
#define DEVICE_ATTR_RW(_name) \
struct device_attribute dev_attr_##_name = __ATTR_RW(_name)
在online_store()函数中,拔核就执行device_offline(dev)函数
device_offline中dev->bus->offline(dev);
drivers/base/cpu.c中
struct bus_type cpu_subsys = {
.name = "cpu",
.dev_name = "cpu",
.match = cpu_subsys_match,
#ifdef CONFIG_HOTPLUG_CPU
.online = cpu_subsys_online,
.offline = cpu_subsys_offline,
#endif
};
cpu_device_down
cpu_down
cpu_down_maps_locked
_cpu_down
cpuhp_down_callbacks
[CPUHP_TEARDOWN_CPU] = {
.name = "cpu:teardown",
.startup.single = NULL,
.teardown.single = takedown_cpu,
.cant_stop = true,
},
do_idle状态机会调用arch_cpu_idle_dead--》cpu_die();--》ops->cpu_die(cpu);
psci_cpu_die--》psci_ops.cpu_off(state);--》psci_0_2_cpu_off
__psci_cpu_off(PSCI_0_2_FN_CPU_OFF, state);
之前在XXX中介绍的PSCI协议部分,这里会发送smc指令到ATF
在ATF中同理会处理这些PSCI协议。