目录
1、linux autosleep实现
1.1 模块功能
在Linux kernel中,autosleep是睡眠流程的触发点和入口点,pm core的睡眠流程入口pm_suspend函数就是被autosleep的睡眠工作队列调用而进入睡眠的。该套机制由Rafael J. Wysocki在2012年合入到Kernel主干,从此之后便一直作为Linux kernel低功耗睡眠的触发入口机制存在。在本章节我们会对该套机制做实现上的分析,并借鉴该机制实现我们自己定制化的autosleep机制。
1.2 功能属性
1)特性功能受宏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.3 主要函数实现
1.3.1 pm_autosleep_init
1)函数说明
函数原型 | |
int __init pm_autosleep_init(void) | |
描述 | |
1、创建autosleep流程中投票睡眠的睡眠锁autosleep_ws 2、创建睡眠工作队列autosleep_wq | |
参数 | |
无 | |
返回值 | |
0 | 初始化成功 |
其他 | 初始化失败 |
2)函数实现
其中autosleep_ws和autosleep_wq都是定义的全局变量,方便在本文件其他函数中调用。
-
int __init pm_autosleep_init(void)
-
{
-
autosleep_ws =
wakeup_source_register(
"autosleep");
-
if (!autosleep_ws)
-
return -ENOMEM;
-
-
autosleep_wq =
alloc_ordered_workqueue(
"autosleep",
0);
-
if (autosleep_wq)
-
return
0;
-
-
wakeup_source_unregister(autosleep_ws);
-
return -ENOMEM;
-
}
1.3.2 queue_up_suspend_work
1)函数说明
函数原型 |
void queue_up_suspend_work(void) |
描述 |
启动autosleep_wq运行 |
参数 |
无 |
返回值 |
无 |
2)函数实现
-
static
DECLARE_WORK(suspend_work, try_to_suspend);
-
void
queue_up_suspend_work(
void)
-
{
-
if (autosleep_state >
PM_SUSPEND_ON)
-
queue_work(autosleep_wq, &suspend_work);
-
}
函数queue_up_suspend_work被调用后,会触发工作队列运行,进入到对应的work_handler函数try_to_suspend中执行;关于工作队列的实现原理和用法,推荐大家阅读《Linux 内核设计与实现》一书的8.4节中对工作队列的详细介绍,本书中不做过多冗余的说明。
1.3.2 pm_autosleep_set_state
1)函数说明
函数原型 | |
pm_autosleep_set_state | |
描述 | |
提供给文件节点autosleep使用,在init.rc中往此节点写入mem状态,触发autosleep运行 | |
参数 | |
suspend_state_t state | 要写入的suspend状态 |
返回值 | |
0 | 成功 |
其他 | 失败 |
2)函数实现
-
int pm_autosleep_set_state(suspend_state_t state)
-
{
-
#ifndef CONFIG_HIBERNATION
-
if (state >= PM_SUSPEND_MAX)
-
return -EINVAL;
-
#endif
-
__pm_stay_awake(autosleep_ws);
-
mutex_lock(&autosleep_lock);
-
autosleep_state = state;
-
__pm_relax(autosleep_ws);
-
if (state > PM_SUSPEND_ON) {
-
pm_wakep_autosleep_enabled(
true);
-
queue_up_suspend_work();
-
}
else {
-
pm_wakep_autosleep_enabled(
false);
-
}
-
mutex_unlock(&autosleep_lock);
-
return
0;
-
}
函数中会判断入参state的值,只要大于PM_SUSPEND_ON,就表示开启低功耗相关特性,然后会做2件事,一是更新所有wakesource的autosleep的标记为使能,以便其做相关维测记录;另一件是调用queue_up_suspend_work触发进入睡眠流程。如果不大于PM_SUSPEND_ON,则说明没有使能低功耗特性,同理所有wakesource的autosleep的标记为去使能,结束相关维测信息的记录。关于PM_SUSPEND_ON我们在PM Core章节做过介绍,这里也不再赘述。
1.3.4 try_to_suspend
1)函数说明
函数原型 | |
static void try_to_suspend(struct work_struct *work) | |
描述 | |
提供给文件工作队列suspend_work的work_handler,当suspend_work被触发运行时,实际执行的函数体为本函数;主要根据当前系统中的持锁状态和autosleep_state来判断是否进入PM Core睡眠主流程。 | |
参数 | |
struct work_struct *work | 对应的工作队列,实际实现中并未使用上 |
返回值 | |
无 |
2)函数实现
-
static void try_to_suspend(struct work_struct *work)
-
{
-
unsigned
int initial_count, final_count;
-
if (!
pm_get_wakeup_count(&initial_count,
true))
-
goto out;
-
mutex_lock(&autosleep_lock);
-
if (!
pm_save_wakeup_count(initial_count) ||
-
system_state != SYSTEM_RUNNING) {
-
mutex_unlock(&autosleep_lock);
-
goto out;
-
}
-
if (autosleep_state == PM_SUSPEND_ON) {
-
mutex_unlock(&autosleep_lock);
-
return;
-
}
-
if (autosleep_state >= PM_SUSPEND_MAX)
-
hibernate();
-
else
-
pm_suspend(autosleep_state);
-
mutex_unlock(&autosleep_lock);
-
if (!
pm_get_wakeup_count(&final_count,
false))
-
goto out;
-
/*
-
* If the wakeup occurred for an unknown reason, wait to prevent the
-
* system from trying to suspend and waking up in a tight loop.
-
*/
-
if (final_count == initial_count)
-
schedule_timeout_uninterruptible(HZ /
2);
-
out:
-
queue_up_suspend_work();
-
}
函数的名字为try_to_suspend,顾名思义,既然是try,就意味着并不是每次执行都能顺利进入到睡眠中并真正睡下去,如果能睡下去还好,睡不下去的话,会重新调度工作队列等待下次执行。
在这里重点说下函数中的两个关键点:
1) initial_count和final_count,initial_count是在函数入口处获取的wakeup events,而final_count是在睡眠流程退出后获取的wakeup events,wakeup events只会在释放锁时才会增加,为什么在函数倒数第4行要再检查一次wakeup events的值呢?这样做其实也是一个优化措施,如果退出低功耗睡眠流程不是因为持锁状态的改变导致的,那么在这个地方可以快速的再次尝试进入睡眠流程,如果不做这个优化的话,那么重新调度下次再次进入睡眠的话耗费的周期可能会比较长。
2)判断进入hibernate()还是进入pm_suspend(autosleep_state),hibernate()是suspend到了nand flash,下次唤醒重新把镜像加载到DDR,在嵌入式系统中通常不会使用此功能,因为这个和掉电重启其实差别不大,而且耗时还会比较久,可能PC或者工作站等对时间没那么敏感的会使用此功能;对于嵌入式设备来讲,基本上都是使用的pm_suspend(autosleep_state)这个分支,即PM Core的实现。
1.4 函数工作时序
前提条件:
CONFIG_PM_AUTOSLEEP特性开关打开,使能autosleep功能。
工作步骤:
1)在init.rc中,往autosleep节点写入功耗控制的state,通常是写入mem,格式为:write /sys/power/autosleep mem;也可以在控制台中输入:echo mem > /sys/power/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。