在本节,我们主要对Linux内核中autosleep的实现机制进行分析,包括架构设计概览、模块功能详解、配置信息解析、主要函数实现以及函数工作时序等。
autosleep在Linux内核低功耗软件栈中的位置如图3-1所示。
autosleep中的任务是睡眠的入口任务,负责进入睡眠流程。
在Linux内核中,autosleep是睡眠流程的触发点和入口点,PM Core的睡眠流程入口pm_suspend函数就是被autosleep的睡眠工作队列调用而进入睡眠的。该机制由Rafael J.Wysocki在2012年并入内核主干,自此一直作为Linux内核低功耗睡眠的触发入口机制存在。在本节,我们会分析该机制的实现原理,并借鉴该机制实现我们自己的autosleep机制。
图3-1 autosleep在低功耗软件栈中的位置
1)autosleep功能受宏CONFIG_PM_AUTOSLEEP控制,如果需要打开该功能,则必须将CONFIG_PM_AUTOSLEEP设置为y。
2)相关实现在kernel\power\autosleep.c文件中。
3)通过写“mem,disk,standby,freeze”到/sys/power/autosleep可以开启autosleep功能。
4)通过写“off”到/sys/power/autosleep可以关闭autosleep功能。
该函数主要有两个功能。
1)autosleep_ws:创建autosleep流程中投票睡眠的睡眠锁。
2)autosleep_wq:创建睡眠工作队列。
函数原型如下:
该函数返回0时表示初始化成功,返回其他值时表示初始化失败。
pm_autosleep_init的具体实现代码如下:
其中autosleep_ws和autosleep_wq都是全局变量,方便在本文件其他函数中调用。
该函数的功能是启动autosleep_wq。
函数原型如下:
该函数无返回值。
queue_up_suspend_work的具体实现代码如下:
queue_up_suspend_work函数被调用后,会触发工作队列运行,进入对应的work_handler函数的try_to_suspend中执行。关于工作队列的实现原理和用法,推荐大家阅读《Linux内核设计与实现》一书的8.4节,其中对工作队列有详细介绍,这里不做过多说明。
该函数的功能是供文件节点autosleep使用,在init.rc中向此节点写入suspend状态,触发autosleep运行。
函数原型如下:
入参state表示要写入的suspend状态。
pm_autosleep_set_state的具体实现代码如下:
函数会先判断入参state的值,只要大于PM_SUSPEND_ON,就表示开启低功耗相关特性,然后做两件事:一是更新所有wakeup source的autosleep的标记为使能,以便其做相关维测记录;二是调用queue_up_suspend_work,进入睡眠流程。如果state的值不大于PM_SUSPEND_ON,则说明没有使能低功耗特性。同理,所有wakeup source的autosleep的标记为去使能,结束相关维测信息的记录。关于PM_SUSPEND_ON,我们将在第4章中进行详细介绍,这里不再赘述。
该函数的功能是提供给文件工作队列suspend_work的work_handler,当suspend_work被触发运行时,实际执行的函数体为本函数;主要根据当前系统中的持锁状态和autosleep_state来判断是否进入PM Core睡眠主流程。
函数原型如下:
入参*work表示对应的工作队列,在实际实现中并未使用。
try_to_suspend的具体实现代码如下:
函数的名字为try_to_suspend,顾名思义,既然是try,就意味着并不是每次执行都能顺利进入睡眠流程并真正睡下去,如果能进入睡眠状态还好,如果不能,则重新调度工作队列等待下次执行。
这里重点说一下函数中的两个关键点。
1)initial_count和final_count。initial_count是在函数入口处获取的唤醒事件总数,而final_count是退出睡眠流程后获取的唤醒事件总数,唤醒事件总数只会在释放锁时增加,为什么在函数倒数第4行再检查一次唤醒事件总数呢?这其实是一个优化措施,如果退出低功耗睡眠流程不是因为持锁状态的改变导致的,那么在这个地方可以快速地再次尝试进入睡眠流程。如果不做这个优化,那么重新调度再次进入睡眠的时间可能会比较长。
2)判断是进入hibernate()还是进入pm_suspend(autosleep_state)。hibernate()是挂起到磁盘中的,需要在下次唤醒时重新把镜像加载到DDR,在嵌入式系统中通常不会使用此功能,因为它与掉电重启其实差别不大,而且耗时比较久,可能对时间不那么敏感的PC或工作站等会使用此功能。嵌入式设备通常使用pm_suspend(autosleep_state)这个分支,即通过PM Core实现。
autosleep的工作时序如图3-2所示。
前提条件如下:
CONFIG_PM_AUTOSLEEP特性开关打开,使能autosleep功能。
工作步骤如下。
1)在init.rc中,向autosleep节点写入功耗控制的状态,通常写入mem状态,格式为write/sys/power/autosleep mem,也可以在控制台中输入echo mem>/sys/power/autosleep来触发。
图3-2 autosleep的工作时序
2)写文件节点触发调用autosleep文件中的pm_autosleep_set_state函数,该函数进行参数的判断、系统状态的更新以及调用queue_up_suspend_work来触发autosleep的工作队列。
3)queue_up_suspend_work被调用后触发工作队列suspend_work运行。
4)工作队列suspend_work运行后进入函数try_to_suspend执行,该函数会根据持锁条件决策是否进入PM Core睡眠主流程。
5)try_to_suspend通过调用pm_suspend来进入PM Core流程。
6)pm_suspend退出后回到try_to_suspend,try_to_suspend会再次触发任务或者工作队列调度,期待进入下次的睡眠流程。
7)try_to_suspend通过调用queue_up_suspend_work来再次调度autosleep的工作队列suspend_work。