Suspend to RAM和Suspend to Idle分析,以及在HiKey上性能对比
Posted Arnold Lu@南京
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Suspend to RAM和Suspend to Idle分析,以及在HiKey上性能对比相关的知识,希望对你有一定的参考价值。
测试环境:AOSP 7.1.1+Kernel 4.4.17 HW:HiKey
Ubuntu 14.04+Kernel 4.4.0-31
联系方式:arnoldlu@qq.com
1. Linux内核suspend状态
Linux内核支持多种类型的睡眠状态,通过设置不同的模块进入低功耗模式来达到省电功能。
目前存在四种模式:suspend to idle、power-on standby(Standby)、suspend to ram(STR)和sudpend to disk(Hibernate),分别对应ACPI状态的S0、S1、S3和S4。
State in Linux | Label | state | ACPI state | 注释 |
#define PM_SUSPEND_ON ((__force suspend_state_t) 0) | 一切正常 | |||
#define PM_SUSPEND_FREEZE ((__force suspend_state_t) 1) | freeze | Suspend-to-Idle | S0 | 冻结进程+挂起设备+CPU空闲 |
#define PM_SUSPEND_STANDBY ((__force suspend_state_t) 2) | standby | Standby/Power-on Suspend | S1 | 冻结进程+挂起设备+关闭nonbootCPU |
#define PM_SUSPEND_MEM ((__force suspend_state_t) 3) | mem | Suspend-to-RAM | S3 | 仅保留RAM自刷新 |
#define PM_SUSPEND_MAX ((__force suspend_state_t) 4) | disk | Suspend-to-disk | S4 |
关闭所有设备包括RAM,也被称为Hibernate |
从freeze-->standby-->mem睡眠程度越来越深,唤醒花费的时间也越来越多。
Suspend-To-Idle
此状态包括frozen processes+suspended devices+idle processors,具有轻量化的特点;
并且相对于相对于Idle状态能节省更多的功耗,因为此时的用户空间被冻结且I/O设备进入了低功耗状态。
相对于Suspend-To-RAM它具有低延时的优势。
Standby/Power-On Suspend
此状态包括frozen processes+suspended devices+offline nonboot CPUs+suspend low-level system,对CPU的处理更近一步。
所以相对于Suspend-To-Idle节省了更多的功耗,但是由于需要恢复CPU和一些底层功能也花费了更多的时间。
Suspend-to-RAM
此状态使所有的设备进入低功耗状态,仅保留RAM自刷新。
所有的设备和系统状态都保存在RAM中,所有外设被挂起。
(在HiKey的实际测试中,boot CPU是没有关闭的!实际上这里也没有standby,mem和standby基本上没有区别。)
Suspend-to-disk
此状态是最省功耗的模式。
相对Suspend-to-RAMRAM能节省更多功耗的原因是数据会被写入磁盘中,RAM也可以被关闭。
但是这也导致了,更多的恢复延时,在resume的时候读回到RAM,然后在进行系统和设备状态恢复工作。
但是在一般的嵌入式设备上,此种状态不支持。
下面用STR表示Suspend to RAM,STI表示Suspend to Idle。
详情请参考:http://www.linaro.org/blog/suspend-to-idle/
2. Suspend状态,以及STR 和STI区别
写入/sys/power/state不同字符串,可以让系统进入不同睡眠状态。
针对state sysfs节点的写入,最终会进入到state_store这个函数,将字符串转换成上表中不同状态。
state_store(kernel/power/main.c) -->pm_suspend (kernel/power/suspend.c)-------------处理除freeze、standby、mem三种类型suspend -->enter_state---------------------------------在进入睡眠之前,做一些准备工作 -->suspend_devices_and_enter -->suspend_enter-----------------------这里才是freeze与standby/mem区别所在。 -->hibernate---------------------------------------进入suspend to disk流程
STR和STI的最主要区别就是下面一段代码:
static int suspend_enter(suspend_state_t state, bool *wakeup) { … /* * PM_SUSPEND_FREEZE equals * frozen processes + suspended devices + idle processors. * Thus we should invoke freeze_enter() soon after * all the devices are suspended. */
//====================================FREEZE=============================================================== if (state == PM_SUSPEND_FREEZE) {------------------------------------如果要进入freeze状态,就会执行此段代码。 trace_suspend_resume(TPS("machine_suspend"), state, true); freeze_enter(); trace_suspend_resume(TPS("machine_suspend"), state, false); goto Platform_wake;----------------------------------------------在执行结束跳转到Platform_wake,中间一段绿色代码将会被跳过。所以说freeze和standby、mem相比,多了freeze_enter,少了对non-boot CPUs、arch、syscore的操作。 } //=====================================MEM=============================================================== error = disable_nonboot_cpus(); if (error || suspend_test(TEST_CPUS)) { log_suspend_abort_reason("Disabling non-boot cpus failed"); goto Enable_cpus; } arch_suspend_disable_irqs(); BUG_ON(!irqs_disabled()); error = syscore_suspend(); if (!error) { *wakeup = pm_wakeup_pending(); if (!(suspend_test(TEST_CORE) || *wakeup)) { trace_suspend_resume(TPS("machine_suspend"), state, true); error = suspend_ops->enter(state); trace_suspend_resume(TPS("machine_suspend"), state, false); events_check_enabled = false; } else if (*wakeup) { pm_get_active_wakeup_sources(suspend_abort, MAX_SUSPEND_ABORT_LEN); log_suspend_abort_reason(suspend_abort); error = -EBUSY; } syscore_resume(); } arch_suspend_enable_irqs(); BUG_ON(irqs_disabled()); Enable_cpus: enable_nonboot_cpus(); Platform_wake: platform_resume_noirq(state); dpm_resume_noirq(PMSG_RESUME); … }
3 suspend/resume流程梳理
下面分析一下suspend/resume每个细分阶段。
整个suspend可以分为若干阶段,每个阶段函数—>关键节点Trace—>analyze_suspend.py解析Trace—>根据Trace时间画出Timeline图表
这样就可以分析出总的时间差异,每个阶段差异,甚至一个设备suspend/resume、一个子系统suspend/resume的时间差异。
analyze_suspend.py 基于默认基于ftrace进行分析(在指定dmesg的时候,会发现缺失了很多log信息,无法生成timeline类型的html文件),将suspend/resume分为若干阶段。
下面简要介绍一下各个阶段,然后基于此进行代码分析。
在kernel版本大于等与3.15之后,解析需要的所有log信息都可以从ftrace中获取。之前的内核版本还需要借助于dmesg。
由于使用的kernel版本是4.4.17,sysvals.usetraceeventsonly被置位,所以只会parseTraceLog()。
下表中的各个阶段通过解析suspend_resume: XXXXXXX类型的ftrace来获取。
各子模块、子系统的解析通过device_pm_callback_start和device_pm_callback_end来截取时间段,以及这时间段内的callgraph。
Phase名称 | ftrace关键词 | ||
suspend_prepare | dpm_prepare | ||
suspend | dpm_suspend | ||
suspend_late | dpm_suspend_late | ||
suspend_noirq | dpm_suspend_noirq | ||
suspend_machine | machine_suspend start | ||
resume_machine | machine_suspend end | ||
resume_noirq | dpm_resume_noirq | ||
resume_early | dpm_resume_early | ||
resume | dpm_resume | ||
resume_complete | dpm_complete |
下面是一组suspend/resume执行ftrace log,我们将据此进行各阶段代码分析,包括suspend_enter、suspend_prepare、suspend、suspend_late、suspend_noirq、suspend_machine、resume_machine、resume_noirq、resume_early、resume、resume_complete。
从这里也可以看出freeze和mem/standby除了machine部分不同之外,还少了CPU开关和syscore suspend/resume操作。
suspend_resume: suspend_enter[1] begin |
suspend_resume: suspend_enter[3] begin |
在介绍相关代码之前,先介绍一下HiKey使用的platform_suspend_ops:
static const struct platform_suspend_ops psci_suspend_ops = { |
freeze的platform_freeze_ops如下:
static const struct platform_freeze_ops acpi_freeze_ops = { |
3.1 suspend_enter
enter_state作为suspend/resume的入口点,完成了绝大部分工作。首先确保系统没有正在进入睡眠状态;然后为suspend做一些准备,使系统进入睡眠并在唤醒后进行必要清理恢复工作。
下面分析一下suspend之前的准备工作,即suspend_enter阶段:
static int enter_state(suspend_state_t state) { int error; trace_suspend_resume(TPS("suspend_enter"), state, true); if (state == PM_SUSPEND_FREEZE) {--------------------------------------是否是freeze类型suspend #ifdef CONFIG_PM_DEBUG if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) { pr_warning("PM: Unsupported test mode for suspend to idle," "please choose none/freezer/devices/platform.\\n"); return -EAGAIN; } #endif } else if (!valid_state(state)) {-------------------------------------目前只支持mem类型suspend return -EINVAL; } if (!mutex_trylock(&pm_mutex)) return -EBUSY; if (state == PM_SUSPEND_FREEZE) freeze_begin();--------------------------------------------------初始化suspend_freeze_state为FREEZE_STATE_NONE #ifndef CONFIG_SUSPEND_SKIP_SYNC trace_suspend_resume(TPS("sync_filesystems"), 0, true); printk(KERN_INFO "PM: Syncing filesystems ... "); sys_sync();----------------------------------------------------------sync文件系统缓存文件,确保数据sync到硬盘。 printk("done.\\n"); trace_suspend_resume(TPS("sync_filesystems"), 0, false); #endif pr_debug("PM: Preparing system for sleep (%s)\\n", pm_states[state]); pm_suspend_clear_flags(); error = suspend_prepare(state);--------------------------------------注意这里面的suspend_prepare和下面的suspend_prepare阶段容易搞混。 if (error) goto Unlock; if (suspend_test(TEST_FREEZER)) goto Finish; trace_suspend_resume(TPS("suspend_enter"), state, false); pr_debug("PM: Suspending system (%s)\\n", pm_states[state]); pm_restrict_gfp_mask(); error = suspend_devices_and_enter(state); pm_restore_gfp_mask(); Finish: pr_debug("PM: Finishing wakeup.\\n"); suspend_finish();---------------------------------------------------解冻,重启进程;发送PM_POST_SUSPEND通知;释放之前分配的console。 Unlock: mutex_unlock(&pm_mutex); return error; }
接着分析一下suspend_prepare函数:
static int suspend_prepare(suspend_state_t state) if (!sleep_state_supported(state)) 验证suspend状态 pm_prepare_console(); 分配一个suspend console error = pm_notifier_call_chain(PM_SUSPEND_PREPARE); 发送PM_SUSPEND_PREPARE通知消息 trace_suspend_resume(TPS("freeze_processes"), 0, true); suspend_stats.failed_freeze++; |
suspend_freeze_process先处理用户空间进程,然后处理内核进程:
static inline int suspend_freeze_processes(void) error = freeze_processes(); 触发用户空间进程进入freeze状态。当前进程不会被冻结。因为冻结失败的进程会自动被解冻,所以不需要进行错误处理。 error = freeze_kernel_threads(); 冻结内核线程 return error; |
下面的阶段都在suspend_devices_and_enter中,可以看出这是一个对称的流程,每一阶段的suspend,都有对应的resume。
int suspend_devices_and_enter(suspend_state_t state) if (!sleep_state_supported(state)) error = platform_suspend_begin(state); suspend_console(); 关闭console子系统,暂停printk打印 do { Resume_devices: Close: Recover_platform: |
还有必要过一下suspend_enter:
static int suspend_enter(suspend_state_t state, bool *wakeup) error = platform_suspend_prepare(state); 因为suspend_ops的prepare为空,所以返回0 error = dpm_suspend_late(PMSG_SUSPEND); suspend_late error = dpm_suspend_noirq(PMSG_SUSPEND); suspend_noirq if (suspend_test(TEST_PLATFORM)) /* error = disable_nonboot_cpus(); 关闭所有boot-CPU之外的CPU arch_suspend_disable_irqs(); error = syscore_suspend(); 执行syscore_ops_list上所有syscore_ops的suspend回调函数 arch_suspend_enable_irqs(); Enable_cpus: Platform_wake: Platform_early_resume: Devices_early_resume: Platform_finish: |
3.2 suspend_prepare和suspend
DPM是Device Power Management的意思,这些操作都是针对非系统设备(non-sysdev)进行的。那什么是系统设备呢?下面的machine应该就是所谓的sysdev了。
dpm_prepare实际上就是遍历dpm_list上的所有设备,执行->prepare回调函数。如果设备存在->prepare回电函数,会将设备的prepare阶段打印到ftrace。
int dpm_prepare(pm_message_t state) trace_suspend_resume(TPS("dpm_prepare"), state.event, true); mutex_lock(&dpm_list_mtx); get_device(dev); trace_device_pm_callback_start(dev, "", state.event); mutex_lock(&dpm_list_mtx); |
dpm_suspend遍历dpm_prepared_list,这点和dpm_prepare有区别。然后执行设备的->suspend回调函数。
int dpm_suspend(pm_message_t state) trace_suspend_resume(TPS("dpm_suspend"), state.event, true); cpufreq_suspend(); mutex_lock(&dpm_list_mtx); get_device(dev); error = device_suspend(dev); 执行设备->suspend回调函数 mutex_lock(&dpm_list_mtx); |
3.3 suspend_late和suspend_noirq
dpm_suspend_late基于dpm_suspended_list操作设备,所以这也需要函数之间顺序执行。
int dpm_suspend_late(pm_message_t state) trace_suspend_resume(TPS("dpm_suspend_late"), state.event, true); while (!list_empty(&dpm_suspended_list)) { 遍历dpm_suspended_list列表 get_device(dev); error = device_suspend_late(dev); 执行->suspend_late回调函数 mutex_lock(&dpm_list_mtx); if (error) { if (async_error) |
dpm_suspend_noirq基于dpm_late_early_list遍历所有设备。首先阻止设备驱动接收中断信息,然后执行->suspend_noirq回调函数。
int dpm_suspend_noirq(pm_message_t state) trace_suspend_resume(TPS("dpm_suspend_noirq"), state.event, true); while (!list_empty(&dpm_late_early_list)) { get_device(dev); error = device_suspend_noirq(dev); 调用->suspend_noirq回调函数 mutex_lock(&dpm_list_mtx); if (async_error) if (error) { |
3.4 suspend_machine和resume_machine
freeze和mem/standby在这部分是不同的。
mem/standby直接调用suspend_ops->enter进入对应的睡眠模式。
而freeze就要稍微复杂了:
static void freeze_enter(void) suspend_freeze_state = FREEZE_STATE_ENTER; get_online_cpus(); /* Push all the CPUs into the idle loop. */ spin_lock_irq(&suspend_freeze_lock); out: |
3.5 resume_noirq
执行dpm_noirq_list上设备的resume_noirq回调函数。
void dpm_resume_noirq(pm_message_t state) trace_suspend_resume(TPS("dpm_resume_noirq"), state.event, true); /* while (!list_empty(&dpm_noirq_list)) { 遍历dpm_noirq_list if (!is_async(dev)) { error = device_resume_noirq(dev, state, false); mutex_lock(&dpm_list_mtx); |
3.6 resume_early
执行前述dpm_late_early_list设备的resume_early回调函数,移动设备到dpm_suspended_list列表。
void dpm_resume_early(pm_message_t state) trace_suspend_resume(TPS("dpm_resume_early"), state.event, true); /* while (!list_empty(&dpm_late_early_list)) { if (!is_async(dev)) { error = device_resume_early(dev, state, false); |
3.7 resume
执行所有dpm_suspended_list上设备的resume回调函数。
void dpm_resume(pm_message_t state) trace_suspend_resume(TPS("dpm_resume"), state.event, true); mutex_lock(&dpm_list_mtx); list_for_each_entry(dev, &dpm_suspended_list, power.entry) { while (!list_empty(&dpm_suspended_list)) { mutex_unlock(&dpm_list_mtx); error = device_resume(dev, state, false); mutex_lock(&dpm_list_mtx); cpufreq_resume(); |
3.8 resume_complete
执行所
以上是关于Suspend to RAM和Suspend to Idle分析,以及在HiKey上性能对比的主要内容,如果未能解决你的问题,请参考以下文章
Windows——Modern Standby(现代待机) S0改Suspend to RAM(待机到内存)S3睡眠解决方案(以机械革命F1 i5-11300H为例)
Linux 何时以及如何将 VGA 内存保存到 RAM?它在 pm-suspend 脚本中吗?或在 echo mem > /sys/power/state 之后的内核中