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 內核黑科技 | 技術文章 | 精選教程


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM