Linux Suspend主要有以下三步:
1) 冻结用户态进程和内核态任务
2) 调用注册的设备的suspend的回调函数,顺序是按照注册顺序
3) 休眠核心设备和使CPU进入休眠态。
冻结进程(suspend_freeze_processes)是内核把进程列表中所有的进程的状态都设置为停止,并且保存所有进程的上下文。 当这些进程被解冻(suspend_thaw_processes)的时候,他们是不知道自己被冻结过的,只是简单的继续执行。如何让Linux进入Suspend呢?用户可以通过读写sys文件/sys /power/state 是实现控制系统进入休眠,比如:
# echo standby > /sys/power/state
Suspend主要流程如下图所示:
其主要功能如下:
1) suspend_prepare: 准备进入suspend,并冻结所有进程
2) suspend_devices_and_enter: suspend所有外设,并进入sleep状态,只有当唤醒时,此函数才返回
3) suspend_finish: suspend结束,并被唤醒
enter_state代码如下:
// kernel/kernel/power/suspend.c int enter_state(suspend_state_t state) { int error; if (!valid_state(state)) return -ENODEV; if (!mutex_trylock(&pm_mutex)) return -EBUSY; #ifdef CONFIG_SUSPEND_SYNC_WORKQUEUE suspend_sys_sync_queue(); #else sys_sync(); printk("done.\n"); #endif pr_debug("PM: Preparing system for %s sleep\n", pm_states[state]); error = suspend_prepare(); //准备进入suspend,并冻结所有进程 if (error) goto Unlock; if (suspend_test(TEST_FREEZER)) goto Finish; pr_debug("PM: Entering %s sleep\n", pm_states[state]); pm_restrict_gfp_mask(); error = suspend_devices_and_enter(state); // suspend外部设备 pm_restore_gfp_mask(); Finish: pr_debug("PM: Finishing wakeup.\n"); suspend_finish(); // 结束suspend,并被唤醒 Unlock: mutex_unlock(&pm_mutex); return error; }
在suspend_prepare()中它将完成以下任务:
1) 给suspend分配一个虚拟终端来输出信息;
2) 然后广播一个系统要进入suspend的Notify;
3) 关闭掉用户态的helper进程;
4) 最后调用suspend_freeze_processes()冻结所有的进程,这里将保存所有进程 当前的状态,也许有一些进程会拒绝进入冻结状态,当有这样的进程存在的时候,会导致冻结失败,此函数就会放弃冻结进程,并且解冻刚才冻结的所有进程。
其详细代码如下:
static int suspend_prepare(void) { int error; if (!suspend_ops || !suspend_ops->enter) return -EPERM; pm_prepare_console(); // 分配一个console error = pm_notifier_call_chain(PM_SUSPEND_PREPARE); // 发送suspend notify if (error) goto Finish; error = usermodehelper_disable(); // disable用户态的helper进程 if (error) goto Finish; error = suspend_freeze_processes(); // 冻结所有进程 if (!error) return 0; suspend_thaw_processes(); usermodehelper_enable(); Finish: pm_notifier_call_chain(PM_POST_SUSPEND); pm_restore_console(); return error; }
现在, 所有的进程(也包括workqueue/kthread) 都已经停止了,内核态进程有可能在停止的时候握有一些信号量, 所以如果这时候在外设里面去解锁这个信号量有可能会发生死锁, 所以在外设的suspend()函数里面进行lock/unlock锁要非常小心,建议设计时不要在suspend()里面等待锁。而且因为suspend的时候,有一些Log是无法输出的,所以一旦出现问题,非常难调试。
suspend_devices_and_enter的主要功能为:
1) suspend_console: Suspend console子系统,即printk将不能打印了
2) dpm_suspend_start: Suspend所有非系统设备,即调用所有注册设备的suspend回调函数
3) suspend_enter: 使系统进入要求的sleep状态,然后停在这儿,只有当系统被中断或者其他事件唤醒时,此函数才返回
以下函数只有当wakeup时才被执行:
4) dpm_resume_end: resume所有非系统设备,即执行所有注册设备的resume回调函数
5) resume_console: resume console子系统,即printk可用了
详细代码如下所示:
kernel/kernel/power/suspend.c
int suspend_devices_and_enter(suspend_state_t state) { int error; /* suspend_pos通过suspend_set_ops来进行注册, 它在kernel/arch/arm/mach-xx/pm.c中定义,其函数名 可能为xx_pm_ops,例子如下: static struct platform_suspend_ops rk30_pm_ops = { .enter = xx_pm_enter, .valid = suspend_valid_only_mem, .prepare = xx_pm_prepare, .finish = xx_pm_finish, }; */ if (!suspend_ops) return -ENOSYS; trace_machine_suspend(state); if (suspend_ops->begin) { error = suspend_ops->begin(state); if (error) goto Close; } suspend_console(); // suspend console子系统,printk将不能打印了 suspend_test_start(); error = dpm_suspend_start(PMSG_SUSPEND); // suspend所有非系统设备 // 即执行所有设备的suspend回调函数 if (error) { printk(KERN_ERR "PM: Some devices failed to suspend\n"); goto Recover_platform; } suspend_test_finish("suspend devices"); if (suspend_test(TEST_DEVICES)) goto Recover_platform; error = suspend_enter(state); // 系统进入要求的sleep状态, // 只有当wakeup时,此函数才返回 Resume_devices: suspend_test_start(); dpm_resume_end(PMSG_RESUME); // resume所有非系统设备 // 即执行所有设备的resume回调函数 suspend_test_finish("resume devices"); resume_console(); // resume console子系统,即printk可用了 Close: if (suspend_ops->end) suspend_ops->end(); trace_machine_suspend(PWR_EVENT_EXIT); return error; Recover_platform: if (suspend_ops->recover) suspend_ops->recover(); goto Resume_devices; }
Suspend console子系统,即printk将不能打印了
void suspend_console(void) { if (!console_suspend_enabled) return; printk("Suspending console(s) (use no_console_suspend to debug)\n"); console_lock(); console_suspended = 1; up(&console_sem); }
Suspend所有非系统设备,即调用所有注册设备的suspend回调函数
/** * dpm_suspend_start - Prepare devices for PM transition and suspend them. * @state: PM transition of the system being carried out. * * Prepare all non-sysdev devices for system PM transition and execute "suspend" * callbacks for them. */ int dpm_suspend_start(pm_message_t state) { int error; error = dpm_prepare(state); // 根据dpm_list生成dpm_prepared_list if (!error) error = dpm_suspend(state); //根据dpm_prepared_list生成dpm_suspended_list return error; }
使系统进入要求的sleep状态,然后停在这儿,只有当系统被中断或者其他事件唤醒时,此函数才返回,其详细代码如下:
/** * suspend_enter - enter the desired system sleep state. * @state: state to enter * * This function should be called after devices have been suspended. */ static int suspend_enter(suspend_state_t state) { int error; if (suspend_ops->prepare) { error = suspend_ops->prepare(); //即执行xx_pm_prepare if (error) goto Platform_finish; } error = dpm_suspend_noirq(PMSG_SUSPEND); //使所有外设驱动不再接收中断 if (error) { printk(KERN_ERR "PM: Some devices failed to power down\n"); goto Platform_finish; } if (suspend_ops->prepare_late) { error = suspend_ops->prepare_late(); if (error) goto Platform_wake; } if (suspend_test(TEST_PLATFORM)) goto Platform_wake; error = disable_nonboot_cpus(); // 停止非启动CPU if (error || suspend_test(TEST_CPUS)) goto Enable_cpus; arch_suspend_disable_irqs(); // 关闭中断 BUG_ON(!irqs_disabled()); error = syscore_suspend(); // 执行注册在syscore_ops_list的syscore_ops的suspend函数 if (!error) { if (!(suspend_test(TEST_CORE) || pm_wakeup_pending())) { error = suspend_ops->enter(state); // KEY: 即执行xx_pm_enter,唤醒时才返回 events_check_enabled = false; } syscore_resume(); // 执行注册在syscore_ops_list的syscore_ops的resume函数 } arch_suspend_enable_irqs(); // 打开中断 BUG_ON(irqs_disabled()); Enable_cpus: enable_nonboot_cpus(); // 启动非启动CPU Platform_wake: if (suspend_ops->wake) suspend_ops->wake(); dpm_resume_noirq(PMSG_RESUME); //使所有外设驱动接收中断 Platform_finish: if (suspend_ops->finish) suspend_ops->finish(); //即执行xx_pm_finish return error; }
resume所有非系统设备,即执行所有注册设备的resume回调函数
/** * dpm_resume_end - Execute "resume" callbacks and complete system transition. * @state: PM transition of the system being carried out. * * Execute "resume" callbacks for all devices and complete the PM transition of * the system. */ void dpm_resume_end(pm_message_t state) { dpm_resume(state); //根据dpm_suspended_list生成dpm_prepared_list dpm_complete(state); //根据dpm_prepared_list生成dpm_list }
resume console子系统,即printk可用了
void resume_console(void) { if (!console_suspend_enabled) return; down(&console_sem); console_suspended = 0; console_unlock(); }
其主要功能如下(它是suspend_prepare的逆过程):
1) 解冻所有进程;
2) 打开用户态helper进程;
3) 广播系系统suspend结束的Notify;
4) 释放分配的虚拟终端。
其详细代码如下:
static void suspend_finish(void) { suspend_thaw_processes(); //解冻所有进程 usermodehelper_enable(); // 打开用户态helper进程 pm_notifier_call_chain(PM_POST_SUSPEND); // 广播系系统suspend结束的Notify pm_restore_console(); // 释放分配的虚拟终端 }