1. Linux Suspend簡介
Linux Suspend主要有以下三步:
1) 凍結用戶態進程和內核態任務
2) 調用注冊的設備的suspend的回調函數,順序是按照注冊順序
3) 休眠核心設備和使CPU進入休眠態。
凍結進程(suspend_freeze_processes)是內核把進程列表中所有的進程的狀態都設置為停止,並且保存所有進程的上下文。 當這些進程被解凍(suspend_thaw_processes)的時候,他們是不知道自己被凍結過的,只是簡單的繼續執行。如何讓Linux進入Suspend呢?用戶可以通過讀寫sys文件/sys /power/state 是實現控制系統進入休眠,比如:
# echo standby > /sys/power/state
2. Suspend流程
Suspend主要流程如下圖所示:
3. enter_state(PM_SUSPEND_MEM)
其主要功能如下:
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;
- }
3.1 准備並凍結進程(suspend_prepare)
在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;
- }
3.2 Suspend外部設備(suspend_devices_and_enter)
現在, 所有的進程(也包括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;
- }
3.2.1 suspend_console
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);
- }
3.2.2 dpm_suspend_start (PMSG_SUSPEND)
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;
- }
3.2.3 suspend_enter
使系統進入要求的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;
- }
3.2.4 dpm_resume_end (PMSG_RESUME)
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
- }
3.2.5 resume_console
resume console子系統,即printk可用了
- void resume_console(void)
- {
- if (!console_suspend_enabled)
- return;
- down(&console_sem);
- console_suspended = 0;
- console_unlock();
- }
3.3 Suspend結束(suspend_finish)
其主要功能如下(它是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(); // 釋放分配的虛擬終端
- }