Suspend流程介绍

随着智能手机的普及,大家对手机续航也越来越重视,而手机处于休眠状态又是手机最省电的一种模式,因此本文简单介绍下android下suspend的流程。

一、用户空间发起suspend流程

我们知道内核提供了文件节点 /sys/power/state 给用户空间,用来设置当前系统要处于的状态(s2idle/s2ram/std),本文主要介绍s2ram,我们先查看与这个文件相关的进程:

发现是android.system.suspend@1.0-service这个进程在打开这个文件,查看这个进程相关的代码,可以看到autosuspend的流程:

图 1 autosuspend流程

  • Sleep 100ms(初始值)。

  • 读取/sys/power/wakeup_count的值,读取不到返回第一步;读到的值这里暂以count_r来代替。

  • 把count_r写入/sys/power/wakeup_count文件节点,写入成功,则写“mem”到/sys/power/state节点;写入失败,增加sleep时间(maxsleeptime:2min),转到1。

从上面的流程能看到wakeup_count很重要,读取不到wakeup_count及写入wakeup_count失败,都会导致本次的suspend流程失败。

1、wakeup_count的由来

想想如果没有wakeup_count,这时系统马上就要进入suspend,但一个event已经通知到了用户空间,还没来得及执行后续的动作,那这个event只能等待下个wakeup event唤醒系统才有机会执行了。因此,wakeup_count存在的一个根本原因是保证wakeup event执行的原子性(这里与指令操作的原子性有区别)。

wakeup_count是怎样影响suspend流程的呢?

先看下kernel里面wakeup_count的存在形式:

回到autosuspend的流程,当在读取wakeup_count的时候,如果有正处于active状态的wakeup event时, autosuspend进程会在此阻塞(能走到suspend流程说明系统也确实没其它什么事了),直到系统没有了处于active状态的wakeup event或者该进程被中断、信号唤醒才会返回,当返回时如果还有处于active状态的wakeup event,这也标识着读失败,继续循环读取;读取成功后,会把前16bit的值传给autosuspend进程。

读到wakeup_count后,会尝试再往wakeup_count里面写入读到的值,如果这时检测到有处于active状态的wakeup event或者本次读取到的Registered wakeup events与写入的值不相等(表示这期间有wakeup 的events触发过,是有风险的),这时会写入失败,suspend流程被中止。

2、pm_wakeup_pending的作用

检测suspend流程是否能触发,并继续走下去,能看到是内核与用户空间通过wakeup_count这个媒介一起配合完成的(参考pm_save_wakeup_count的实现),而在写入wakeup_count完成后,内核继续在suspend 执行路径里面做这种异常的检测,基本是在suspend路径中比较耗时的动作前后插桩检测,见pm_wakeup_pending的实现,如果我们想要知道在suspend过程中是因为哪些wakeup events的出现导致了流程被中止,在这里可以做下标识。

二、Suspend内核流程跟踪

风险暂时排除后,开始尝试进入suspend流程,通过写入“mem”到state节点(本文只讨论mem的这种情况)。由于suspend内核流程比较复杂,这里只描述比较重要的环节。

如果系统走的是psci的统一接口,suspend_ops只有valid和enter被赋值,其它的成员先暂不介绍。

图 2 suspend_ops示例

1、首先是同步文件系统,见SYSCALL_DEFINE0(sync)的实现,这一步保证suspend前所有写入的数据被同步到块设备,不会丢失,主要是唤醒块设备的pdflush相关进程,然后写脏inode,写块设备,提交缓存数据到journal(ext4),保证在sync这一刻之前的数据不会丢失。

2、准备并冻结进程(suspend_prepare),由以下几步组成:

1) pm_prepare_console给suspend分配一个虚拟终端来输出信息;

2) 发送一个系统要进入suspend的notify。

3) _usermodehelper_disable禁止创建新的usermode helper,内核不再接收这种helper进程的创建。

4) 冻结用户进程及内核进程,及内核相工作队列,唤醒所有的应用进程(fake_signal_wake_up)及内核进程,应用进程的freeze通过信号处理相关函数来调用到try_to_freeze进行冻结,而可冻结的内核进程需要有能力处理冻结流程并显式调用try_to_freeze进行冻结,冻结的最终实现在__refrigerator,每一个被冻结的进程都会调到这来,就像放入冰箱一样,主要也是设置state为TASK_UNINTERRUPTIBLE,然后判断如果要freeze,就调用schedule()把自己切出运行队列。

3、suspend_test这个功能,我们可以用来调试系统在未进入真正的suspend前的某些固定阶段的状态,使能后,借助rtc来做timer发起suspend test主流程,这些固定阶段嵌在suspend流程里,包括以下几个阶段:TEST_FREEZER->TEST_DEVICES-> TEST_PLATFORM->TEST_CPUS->TEST_CORE,由浅入深,当suspend走到你要调试的阶段后,会在这个阶段上睡眠一段时间,供我们来调试系统。

4、休眠设备并进入suspend(suspend_devices_and_enter),由以下步骤组成:

1) 调用platform_suspend_begin,这个区分平台,看下平台是否有在suspend开始时执行操作的需求。略过。

2) suspend console,ptintk将不能打印。

3) suspend所有非系统设备,即调用设备注册的suspend回调(dpm_suspend_start)。device节点的流转过程就是device在整个休眠唤醒过程中的流程,对文字敏感的朋友可以直接看下图。对单个设备来讲,先从dpm_list链表中取出device并执行该 device的prepare回调,成功后会把该device节点移动到dpm_prepared_list链表,后面在做 dpm_suspend时,会从这个链表里面取device,执行suspend的回调,成功后会把该device移动到dpm_suspended_list链表,dpm_suspend_late中会从dpm_suspended_list链表中取出device执行suspend_late回调,然后把device节点移动到dpm_late_early_list链表,dpm_noirq_suspend_devices里会从dpm_late_early_list链表中取出device执行suspend_noirq回调,并把device节点移动到dpm_noirq_list链表,在后续resume过程中dpm_resume_noirq中又会从dpm_noirq_list链表中取出device执行resume_noirq回调,并把device节点移动到dpm_late_early_list链表,dpm_resume_early时会从dpm_late_early_list链表中取出device并执行resume_early回调,然后把device节点链入dpm_suspended_list链表,在dpm_resume时会从dpm_suspended_list链表中拿到device执行resume回调,并把device节点再移动到dpm_prepared_list链表,最后执行dpm_complete时,会从dpm_prepared_list链表中拿到device执行complete回调,并把device重新链入dpm_list链表。完成device节点的流转。设备的休眠和唤醒是系统休眠唤醒的重要组成部分,流转流程如下图:

图 3 设备节点在休眠唤醒流程中流转过程

4) 把系统进入到要求的sleep状态,然后停止运行(suspend_enter)。

三、suspend_enter

单独介绍下suspend_enter,略过一些无感的流程:

1、某些平台需要在这个阶段做一些准备动作,通过调用suspend_ops->prepare来实现平台相关的操作。

2、执行上面已经介绍过的设备的suspend_late回调。

3、dpm_suspend_noirq做的工作是先禁止cpu进入idle,毕竟要进suspend了就不需要再进行idle的选择了,然后开始禁止设备的中断,禁止中断前,会先遍历所有的wakeup source,把对应的中断状态置位IRQD_WAKEUP_STATE flag,这样再禁止中断的过程中轮询到这个中断时,会再加上IRQD_WAKEUP_ARMED flag,没有包含IRQD_WAKEUP_STATE状态的中断会被禁掉。IRQD_WAKEUP_ARMED主要是用来对进入到suspend的wakeup source对应的irq做标识,在irq_may_run里面做快速响应,想想如果在irq_may_run里面返回false代表了什么呢?

4、disable_nonboot_cpus 负责关闭noboot的那些cpu,随后用arch_suspend_disable_irqs关闭bootcpu的中断,即cpu不再响应中断。

5、进入suspend前的最后一步,syscore_suspend主要是执行那些系统及平台在suspend前必需的流程,比如保存一些clk的状态,在resume的时候能恢复clk到suspend前的状态、系统为了对suspend时间的统计及系统时间的更新、也可以用作调试手段,来收集suspend前后的相关信息。

6、最后一步,进入suspend,每种类型的平台实现不同。一般是把主时钟停掉,会启用一个频率更低的时钟来减少功耗,cpu停止运行。

参考文献:

kernel-4.14/Documentation/power/freezing-of-tasks.txt

http://www.wowotech.net/linux_kenrel/suspend_and_resume.html

扫码关注
“内核工匠”微信公众号
Linux 内核黑科技 | 技术文章 | 精选教程
  • 6
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
Linux 内核中,可以通过在设备树中定义 GPIO 来控制蓝牙的供电。下面是一个示例: ``` &gpio { bluetooth_power: bluetooth-power { gpio-hog; gpios = <1 2>; output-high; }; }; &bluetooth { ... wakeup-source; power-gpio = <&gpio bluetooth_power GPIO_ACTIVE_LOW>; ... }; ``` 在上面的示例中,我们在设备树中定义了一个 GPIO,用于控制蓝牙的供电。在蓝牙节点中,我们指定了 `power-gpio` 属性,告诉内核使用哪个 GPIO 来控制供电。此外,我们还将 `wakeup-source` 属性设置为使蓝牙成为唤醒源,以便在系统进入 suspend 模式时保持唤醒能力。 在 suspend 流程中,内核会调用设备的 `suspend()` 方法。我们可以在这个方法中将 GPIO 置为低电平,以断电蓝牙: ``` static int bluetooth_suspend(struct device *dev) { struct bluetooth_device *bt_dev = to_bluetooth_device(dev); gpio_set_value(bt_dev->power_gpio, 0); return 0; } ``` 在 resume 流程中,内核会调用设备的 `resume()` 方法。我们可以在这个方法中将 GPIO 置为高电平,以给蓝牙供电并恢复其工作: ``` static int bluetooth_resume(struct device *dev) { struct bluetooth_device *bt_dev = to_bluetooth_device(dev); gpio_set_value(bt_dev->power_gpio, 1); return 0; } ``` 需要注意的是,我们需要在驱动的 `probe()` 方法中获取 GPIO 的句柄并保存下来,以便在 suspend 和 resume 流程中使用: ``` static int bluetooth_probe(struct platform_device *pdev) { struct bluetooth_device *bt_dev; bt_dev = devm_kzalloc(&pdev->dev, sizeof(*bt_dev), GFP_KERNEL); if (!bt_dev) return -ENOMEM; bt_dev->power_gpio = devm_gpiod_get(&pdev->dev, "bluetooth-power", GPIOD_OUT_HIGH); if (IS_ERR(bt_dev->power_gpio)) return PTR_ERR(bt_dev->power_gpio); ... return 0; } static struct platform_driver bluetooth_driver = { .probe = bluetooth_probe, .suspend = bluetooth_suspend, .resume = bluetooth_resume, .driver = { .name = "bluetooth", .of_match_table = bluetooth_of_match, }, }; ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

内核工匠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值