上文中提到Android系统通过将 mem 写入 /sys/power/state 来发起系统挂起。这篇文章大部分内容来自蜗窝科技,我更新了code以及增加自己的理解

Suspend功能有关的代码分布

内核中Suspend功能有关的代码包括PM core、Device PM、Platform PM等几大块,具体如下:

  1. PM Core
kernel/power/main.c----提供用户空间接口(/sys/power/state)

kernel/power/suspend.c----Suspend功能的主逻辑

kernel/power/suspend_test.c----Suspend功能的测试逻辑

kernel/power/console.c----Suspend过程中对控制台的处理逻辑

kernel/power/process.c----Suspend过程中对进程的处理逻辑
  1. Device PM
drivers/base/power/*
设备驱动----具体设备驱动的位置
  1. Platform dependent PM
include/linux/suspend.h----定义platform dependent PM有关的操作函数集

arch/xxx/mach-xxx/xxx.c或者

arch/xxx/plat-xxx/xxx.c----平台相关的电源管理操作

suspend&resume过程概述

下面图片对Linux suspend&resume过程做了一个概述,读者可以顺着这个流程阅读内核源代码。具体的说明,可以参考后面的代码分析。
在这里插入图片描述

内核休眠流程

发echo mem > /sys/power/state命令后,内核开始处理休眠流程:

// kernel/power/main.c 
state_store
    ->pm_suspend
        ->enter_state
// kernel/power/main.c 
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
			   const char *buf, size_t n)
{
	suspend_state_t state;
	int error;

	error = pm_autosleep_lock();
	if (error)
		return error;

	if (pm_autosleep_state() > PM_SUSPEND_ON) {
		error = -EBUSY;
		goto out;
	}

	state = decode_state(buf, n);
	if (state < PM_SUSPEND_MAX) {
		if (state == PM_SUSPEND_MEM)
			state = mem_sleep_current;

		error = pm_suspend(state);
	} else if (state == PM_SUSPEND_MAX) {
		error = hibernate();
	} else {
		error = -EINVAL;
	}

 out:
	pm_autosleep_unlock();
	return error ? error : n;
}

power_attr(state);

power_attr定义了一个名称为state的attribute文件,该文件的store接口为state_store,该接口在lock住autosleep功能后,解析用户传入的buffer(freeze、standby or mem),转换成state参数。
state参数的类型为suspend_state_t,在include\linux\suspend.h中定义,为电源管理状态在内核中的表示。具体如下:

typedef int __bitwise suspend_state_t;

#define PM_SUSPEND_ON		((__force suspend_state_t) 0)
#define PM_SUSPEND_TO_IDLE	((__force suspend_state_t) 1)
#define PM_SUSPEND_STANDBY	((__force suspend_state_t) 2)
#define PM_SUSPEND_MEM		((__force suspend_state_t) 3)
#define PM_SUSPEND_MIN		PM_SUSPEND_TO_IDLE
#define PM_SUSPEND_MAX		((__force suspend_state_t) 4)

根据state的值,如果不是(PM_SUSPEND_MAX,对应hibernate功能),则调用pm_suspend接口,进行后续的处理。

// kernel/power/suspend.c
int pm_suspend(suspend_state_t state)
{
	int error;

	if (state <= PM_SUSPEND_ON || state >= PM_SUSPEND_MAX)
		return -EINVAL;

	pr_info("suspend entry (%s)\n", mem_sleep_labels[state]);
	error = enter_state(state);
	if (error) {
		suspend_stats.fail++;
		dpm_save_failed_errno(error);
	} else {
		suspend_stats.success++;
	}
	pr_info("suspend exit\n");
	return error;
}
EXPORT_SYMBOL(pm_suspend);

pm_suspend的实现非常简单,简单的做一下参数合法性判断,直接调用enter_state接口。

休眠流程

按照内核划分的休眠测试环节分为,FREEZER(进程冻结),DEVICES(设备休眠),PLATFORM(关闭不必要的中断),CPUS(关闭noboot cpu),core(关闭cpu中断,通知cpus进入休眠处理流程)几个阶段

// kernel/power/suspend.c
enter_state
    suspend_prepare(suspend_state_t state)
    error = suspend_devices_and_enter(state);
/**
 1. enter_state - Do common work needed to enter system sleep state.
 2. @state: System sleep state to enter.
 3.  4. Make sure that no one else is trying to put the system into a sleep state.
 4. Fail if that's not the case.  Otherwise, prepare for system suspend, make the
 5. system enter the given sleep state and clean up after wakeup.
 */
static int enter_state(suspend_state_t state)
{
	int error;

	trace_suspend_resume(TPS("suspend_enter"), state, true);
	if (state == PM_SUSPEND_TO_IDLE) {
#ifdef CONFIG_PM_DEBUG
		if (pm_test_level != TEST_NONE && pm_test_level <= TEST_CPUS) {
			pr_warn("Unsupported test mode for suspend to idle, please choose none/freezer/devices/platform.\n");
			return -EAGAIN;
		}
#endif
	} else if (!valid_state(state)) {
		return -EINVAL;
	}
	if (!mutex_trylock(&system_transition_mutex))
		return -EBUSY;

	if (state == PM_SUSPEND_TO_IDLE)
		s2idle_begin();

	if (sync_on_suspend_enabled) {
		trace_suspend_resume(TPS("sync_filesystems"), 0, true);
		ksys_sync_helper();
		trace_suspend_resume(TPS("sync_filesystems"), 0, false);
	}

	pm_pr_dbg("Preparing system for sleep (%s)\n", mem_sleep_labels[state]);
	pm_suspend_clear_flags();
	error = suspend_prepare(state);
	if (error)
		goto Unlock;

	if (suspend_test(TEST_FREEZER))
		goto Finish;

	trace_suspend_resume(TPS("suspend_enter"), state, false);
	pm_pr_dbg("Suspending system (%s)\n", mem_sleep_labels[state]);
	pm_restrict_gfp_mask();
	error = suspend_devices_and_enter(state);
	pm_restore_gfp_mask();

 Finish:
	events_check_enabled = false;
	pm_pr_dbg("Finishing wakeup.\n");
	suspend_finish();
 Unlock:
	mutex_unlock(&system_transition_mutex);
	return error;
}
  1. 调用valid_state,判断该平台是否支持该电源状态。
    suspend的最终目的,是让系统进入可恢复的挂起状态,而该功能必须有平台相关代码的参与才能完成,因此内核PM Core就提供了一系列的回调函数(封装在platform_suspend_ops中),让平台代码(如arch/arm/mach-xxx/pm.c)实现,然后由PM Core在合适的时机调用。这些回调函数包含一个valid函数,就是用来告知PM Core,支持哪些state。
static bool valid_state(suspend_state_t state)
{
   /*
    * PM_SUSPEND_STANDBY and PM_SUSPEND_MEM states need low level
    * support and need to be valid to the low level
    * implementation, no valid callback implies that none are valid.
    */
   return suspend_ops && suspend_ops->valid && suspend_ops->valid(state);
}

调用suspend_ops的valid回掉,由底层平台代码判断是否支持。
2. 加互斥锁,只允许一个实例处理suspend。
3. 如果state是freeze,调用freeze_begin,进行suspend to freeze相关的特殊动作。我会在后面统一分析freeze的特殊动作,这里暂不描述
4. 打印提示信息,同步文件系统。
5. 调用suspend_prepare,进行suspend前的准备,主要包括switch console和process&thread freezing。如果失败,则终止suspend过程。
6. 然后,调用suspend_devices_and_enter接口,该接口负责suspend和resume的所有实际动作。前半部分,suspend console、suspend device、关中断、调用平台相关的suspend_ops使系统进入低功耗状态。后半部分,在系统被事件唤醒后,处理相关动作,调用平台相关的suspend_ops恢复系统、开中断、resume device、resume console。
7. 最后,调用suspend_finish,恢复(或等待恢复)process&thread,还原console。

suspend_prepare

// kernel/power/suspend.c
/**
 13. suspend_prepare - Prepare for entering system sleep state.
 14. @state: Target system sleep state.
 15.  16. Common code run for every system sleep state that can be entered (except for
 16. hibernation).  Run suspend notifiers, allocate the "suspend" console and
 17. freeze processes.
 */
static int suspend_prepare(suspend_state_t state)
{
	int error;

	if (!sleep_state_supported(state))
		return -EPERM;

	pm_prepare_console();

	error = pm_notifier_call_chain_robust(PM_SUSPEND_PREPARE, PM_POST_SUSPEND);
	if (error)
		goto Restore;

	trace_suspend_resume(TPS("freeze_processes"), 0, true);
	error = suspend_freeze_processes();
	trace_suspend_resume(TPS("freeze_processes"), 0, false);
	if (!error)
		return 0;

	log_suspend_abort_reason("One or more tasks refusing to freeze");
	suspend_stats.failed_freeze++;
	dpm_save_failed_step(SUSPEND_FREEZE);
	pm_notifier_call_chain(PM_POST_SUSPEND);
 Restore:
	pm_restore_console();
	return error;
}
  1. 检查suspend_ops是否提供了.enter回调,没有的话,返回错误。
  2. 调用pm_prepare_console,将当前console切换到一个虚拟console并重定向内核的kmsg(需要的话)。该功能称作VT switch,后面我会在稍微详细的介绍一下,但Linux控制台子系统是相当复杂的,更具体的分析,要在控制台子系统的分析文章中说明。
  3. 调用pm_notifier_call_chain,发送suspend开始的消息(PM_SUSPEND_PREPARE)。PM notifier有讲他的作用。
  4. 如果freezing-of-tasks失败,调用pm_restore_console,将console切换回原来的console,并返回错误,以便能终止suspend。

suspend_devices_and_enter

// kernel/power/suspend.c
/**
 23. suspend_devices_and_enter - Suspend devices and enter system sleep state.
 24. @state: System sleep state to enter.
 */
int suspend_devices_and_enter(suspend_state_t state)
{
	int error;
	bool wakeup = false;

	if (!sleep_state_supported(state))
		return -ENOSYS;

	pm_suspend_target_state = state;

	if (state == PM_SUSPEND_TO_IDLE)
		pm_set_suspend_no_platform();

	error = platform_suspend_begin(state);
	if (error)
		goto Close;

	suspend_console();
	suspend_test_start();
	error = dpm_suspend_start(PMSG_SUSPEND);
	if (error) {
		pr_err("Some devices failed to suspend, or early wake event detected\n");
		log_suspend_abort_reason(
				"Some devices failed to suspend, or early wake event detected");
		goto Recover_platform;
	}
	suspend_test_finish("suspend devices");
	if (suspend_test(TEST_DEVICES))
		goto Recover_platform;

	do {
		error = suspend_enter(state, &wakeup);
	} while (!error && !wakeup && platform_suspend_again(state));

 Resume_devices:
	suspend_test_start();
	dpm_resume_end(PMSG_RESUME);
	suspend_test_finish("resume devices");
	trace_suspend_resume(TPS("resume_console"), state, true);
	resume_console();
	trace_suspend_resume(TPS("resume_console"), state, false);

 Close:
	platform_resume_end(state);
	pm_suspend_target_state = PM_SUSPEND_ON;
	return error;

 Recover_platform:
	platform_recover(state);
	goto Resume_devices;
}
  1. 再次检查平台代码是否需要提供以及是否提供了suspend_ops。
  2. 调用suspend_ops的begin回调(有的话),通知平台代码,以便让其作相应的处理(需要的话)。可能失败,需要跳至Close处执行恢复操作(suspend_ops->end)。如果state == PM_SUSPEND_TO_IDLE无需平台代码参与即可支持。
  3. 调用suspend_console,挂起console。该接口由"kernel\printk.c"实现,主要是hold住一个lock,该lock会阻止其它代码访问console。
  4. 调用dpm_suspend_start,调用所有设备的->prepare和->suspend回调函数(具体可参考“Linux电源管理(4)_Power Management Interface”的描述),suspend需要正常suspend的设备。suspend device可能失败,需要跳至 Recover_platform,执行recover操作(suspend_ops->recover)。
  5. 以上都是suspend前的准备工作,此时,调用suspend_enter接口,使系统进入指定的电源状态。该接口的内容如下。

suspend_enter

/**
 * suspend_enter - Make the system enter the given sleep state.
 * @state: System sleep state to enter.
 * @wakeup: Returns information that the sleep state should not be re-entered.
 *
 * This function should be called after devices have been suspended.
 */
static int suspend_enter(suspend_state_t state, bool *wakeup)
{
	int error, last_dev;

	error = platform_suspend_prepare(state);
	if (error)
		goto Platform_finish;

	error = dpm_suspend_late(PMSG_SUSPEND);
	if (error) {
		last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
		last_dev %= REC_FAILED_NUM;
		pr_err("late suspend of devices failed\n");
		log_suspend_abort_reason("late suspend of %s device failed",
					 suspend_stats.failed_devs[last_dev]);
		goto Platform_finish;
	}
	error = platform_suspend_prepare_late(state);
	if (error)
		goto Devices_early_resume;

	error = dpm_suspend_noirq(PMSG_SUSPEND);
	if (error) {
		last_dev = suspend_stats.last_failed_dev + REC_FAILED_NUM - 1;
		last_dev %= REC_FAILED_NUM;
		pr_err("noirq suspend of devices failed\n");
		log_suspend_abort_reason("noirq suspend of %s device failed",
					 suspend_stats.failed_devs[last_dev]);
		goto Platform_early_resume;
	}
	error = platform_suspend_prepare_noirq(state);
	if (error)
		goto Platform_wake;

	if (suspend_test(TEST_PLATFORM))
		goto Platform_wake;

	if (state == PM_SUSPEND_TO_IDLE) {
		s2idle_loop();
		goto Platform_wake;
	}

	error = suspend_disable_secondary_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());

	system_state = SYSTEM_SUSPEND;

	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);
		} else if (*wakeup) {
			error = -EBUSY;
		}
		syscore_resume();
	}

	system_state = SYSTEM_RUNNING;

	arch_suspend_enable_irqs();
	BUG_ON(irqs_disabled());

 Enable_cpus:
	suspend_enable_secondary_cpus();

 Platform_wake:
	platform_resume_noirq(state);
	dpm_resume_noirq(PMSG_RESUME);

 Platform_early_resume:
	platform_resume_early(state);

 Devices_early_resume:
	dpm_resume_early(PMSG_RESUME);

 Platform_finish:
	platform_resume_finish(state);
	return error;
}

  1. 该接口处理完后,会通过返回值告知是否enter成功,同时通过wakeup指针,告知调用者,是否有wakeup事件发生,导致电源状态切换失败。

  2. 调用suspend_ops的prepare回调(有的话),通知平台代码,以便让其在即将进行状态切换之时,再做一些处理(需要的话)。该回调可能失败(平台代码出现意外),失败的话,需要跳至Platform_finish处,调用suspend_ops的finish回调,执行恢复操作。

  3. 调用dpm_suspend_late和dpm_suspend_noirq,调用所有设备的->suspend_late和->suspend_noirq回调函数(具体可参考“Linux电源管理(4)_Power Management Interface”的描述),suspend late suspend设备和需要在关中断下suspend的设备。需要说明的是,这里的noirq,是通过禁止所有的中断线的形式,而不是通过关全局中断的方式。同样,该操作可能会失败,失败的话,跳至Platform_finish处,执行恢复动作。

  4. 调用suspend_ops的prepare_late回调(有的话),通知平台代码,以便让其在最后关头,再做一些处理(需要的话)。该回调可能失败(平台代码出现意外),失败的话,需要跳至Platform_wake处,调用suspend_ops的wake回调,执行device的resume、调用suspend_ops的finish回调,执行恢复操作。

  5. 如果是suspend to freeze,执行相应的操作,包括冻结进程、suspended devices(参数为PM_SUSPEND_FREEZE)、cpu进入idle。如果有任何事件使CPU从idle状态退出,跳至Platform_wake处,执行wake操作。

  6. 调用disable_nonboot_cpus,禁止所有的非boot cpu。也会失败,执行恢复操作即可。

  7. 调用arch_suspend_disable_irqs,关全局中断。如果无法关闭,则为bug。

  8. 调用syscore_suspend,suspend system core。同样会失败,执行恢复操作即可。有关syscore,我会在另一篇文章中详细描述。

  9. 调用syscore_suspend,suspend system core。同样会失败,执行恢复操作即可。

  10. .如果很幸运,以上操作都成功了,那么,切换吧。不过,别高兴太早,还得调用pm_wakeup_pending检查一下,这段时间内,是否有唤醒事件发生,如果有就要终止suspend。就像我在【Android】 android suspend/resume总结(1)中提到的:将从 /sys/power /wakeup_count(第 1 步中)读取的值写入此文件。 如果写入失败,则返回到循环的开头。(这一步很重要相当于events_check_enabled,这也是android suspend和直接echo mem > sys/power/state的不同之处,events_check_enabled之后在suspend的过程中只要有wakeup_source_report_event,就会导致wakeup_count++,就会导致cnt != saved_count,进而导致"Wakeup pending, aborting suspend")。

  11. 如果一切顺利,调用suspend_ops的enter回调,进行状态切换。这时,系统应该已经suspend了……

  12. suspend过程中,唤醒事件发生,系统唤醒,该函数接着执行resume动作,并最终返回。resume动作基本上是suspend的反动作,就不再继续分析了。

  13. 或者,由于意外,suspend终止,该函数也会返回。

  14. suspend_enter返回,如果返回原因不是发生错误,且不是wakeup事件。则调用suspend_ops的suspend_again回调,检查是否需要再次suspend。再什么情况下要再次suspend呢?需要看具体的平台了,谁知道呢。

  15. 继续resume操作,resume device、start ftrace、resume console、suspend_ops->end等等。

  16. 该函数返回后,表示系统已经resume。

suspend_finish

/**
 17. suspend_finish - Clean up before finishing the suspend sequence.
 18.  19. Call platform code to clean up, restart processes, and free the console that
 20. we've allocated. This routine is not called for hibernation.
 */
static void suspend_finish(void)
{
	suspend_thaw_processes();
	pm_notifier_call_chain(PM_POST_SUSPEND);
	pm_restore_console();
}
  1. 恢复所有的用户空间进程和内核线程。
  2. 发送suspend结束的通知。
  3. 将console切换回原来的。

freezing of task

进程的freezing功能,是suspend、hibernate等电源管理功能的组成部分,在新版本内核中,它被独立出来,作为一个独立的电源管理状态(freeze)。该功能的目的,是在电源管理的状态切换过程中,确保所有用户空间进程和部分内核线程处于一个稳定的状态。有关该功能的具体描述,请参考wowotech后续的文章。

PM notifier

PM notifier是基于内核blocking notifier功能实现的。blocking notifier提供了一种kernel内部的消息通知机制,消息接受者通过notifier注册的方式,注册一个回调函数,关注消息发送者发出的notifier。当消息产生时,消息产生者通过调用回调函数的形式,通知消息接受者。这种调用,是可以被阻塞的,因此称作blocking notifier。
那suspend功能为什么使用notifier呢?原因可能有多种,这里我举一个例子,这是我们日常开发中可能会遇到的。
由之前的描述可知,suspend过程中,suspend device发生在进程被freeze之后,resume device发生在进程被恢复之前。那么:
1.如果有些设备就需要在freeze进程之前suspend怎么办?
2.如果有些设备的resume动作需要较多延时,或者要等待什么事情发生,那么如果它的resume动作发生在进程恢复之前,岂不是要阻止所有进程的恢复?更甚者,如果该设备要等待某个进程的数据才能resume,怎么办?
再来看suspend_prepare和suspend_finish中的处理:

static int suspend_prepare(suspend_state_t state)
{
    ....
	pm_notifier_call_chain_robust(PM_SUSPEND_PREPARE, PM_POST_SUSPEND);
	if (error)
		goto Restore;
	.....
}

static void suspend_finish(void)
{
        suspend_thaw_processes();
        pm_notifier_call_chain(PM_POST_SUSPEND);
        pm_restore_console();
}

原来PM notifier是在设备模型的框架外,开了一个后门,那些比较特殊的driver,可以绕过设备模型,直接接收PM发送的suspend信息,以便执行自身的suspend动作。特别是resume时,可以在其它进程都正好工作的时候,只让suspend进程等待driver的resume。
感兴趣的读者,可以围观一下下面这个活生生的例子(顺便提一下,好的设计是不应该有例外的):
drivers/video/fbdev/omap2/omapfb/dss/core.c

//drivers/video/fbdev/omap2/omapfb/dss/core.c
static struct notifier_block omap_dss_pm_notif_block = {
	.notifier_call = omap_dss_pm_notif,
};

device PM ops 和platform PM ops的调用时机

对Linux驱动工程师来说,device PM ops和platform PM ops就是电源管理(suspend)的全部,只要在合适的地方,实现合适的回调函数,即可实现系统的电源管理。但现实太复杂了,以至于kernel提供的这两个数据结构也很复杂,再回忆一下,如下:

//include/linux/pm.h 
struct dev_pm_ops {
	int (*prepare)(struct device *dev);
	void (*complete)(struct device *dev);
	int (*suspend)(struct device *dev);
	int (*resume)(struct device *dev);
	int (*freeze)(struct device *dev);
	int (*thaw)(struct device *dev);
	int (*poweroff)(struct device *dev);
	int (*restore)(struct device *dev);
	int (*suspend_late)(struct device *dev);
	int (*resume_early)(struct device *dev);
	int (*freeze_late)(struct device *dev);
	int (*thaw_early)(struct device *dev);
	int (*poweroff_late)(struct device *dev);
	int (*restore_early)(struct device *dev);
	int (*suspend_noirq)(struct device *dev);
	int (*resume_noirq)(struct device *dev);
	int (*freeze_noirq)(struct device *dev);
	int (*thaw_noirq)(struct device *dev);
	int (*poweroff_noirq)(struct device *dev);
	int (*restore_noirq)(struct device *dev);
	int (*runtime_suspend)(struct device *dev);
	int (*runtime_resume)(struct device *dev);
	int (*runtime_idle)(struct device *dev);
};

//include/linux/suspend.h
struct platform_suspend_ops {
	int (*valid)(suspend_state_t state);
	int (*begin)(suspend_state_t state);
	int (*prepare)(void);
	int (*prepare_late)(void);
	int (*enter)(suspend_state_t state);
	void (*wake)(void);
	void (*finish)(void);
	bool (*suspend_again)(void);
	void (*end)(void);
	void (*recover)(void);
};

虽然内核的注释已经相当详细了,但我们一定会犯晕,到底该实现哪些回调?这些回调的应用场景又是什么?蜗蜗以为,要熟练使用这些回调,唯一的方法就是多coding、多理解。除此之外,我们可以总结一下在电源状态切换时,这些回调的调用时机,从侧面帮助理解。如下(只介绍和suspend功能有关的,struct dev_pm_ops简称D,struct platform_suspend_ops简称P):
在这里插入图片描述

suspend过程的同步和PM wakeup

最重要的事情,如果suspend的过程中,有唤醒事件产生怎么办?正常的流程,应该终止suspend,返回并处理事件。但由于suspend过程的特殊性,进程被freeze、关中断等等,导致事情并没有那么简单,以至于在很久的一段时间内,kernel都不能很好的处理。这也称作suspend过程的同步问题。
在美好的旧时光里,suspend大多用于热关机,因此同步问题的影响并不突出(因为操作并不频繁)。但来到新时代之后,事情变了,Android竟然用suspend作日常的待机(操作就相当频繁了),这时问题就大了。那怎么解决呢?得靠system wakeup framework,也就是suspend过程中所调用的pm_wakeup_pending接口所在的模块。我会在下一篇文章中继续该模块的分析 Linux电源管理(7)_Wakeup events framework,这里就不再继续了。

参考文献
Linux电源管理(6)_Generic PM之Suspend功能
Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐