原文:蝸窩科技Linux進程凍結技術
功耗中經常需要用到,但是linux這塊了解甚少,看到這個文章還蠻適合我閱讀的
1 什么是進程凍結
進程凍結技術(freezing of tasks)是指在系統hibernate或者suspend的時候,將用戶進程和部分內核線程置于“可控”的暫停狀態。
2 為什么需要凍結技術
假設沒有凍結技術,進程可以在任意可調度的點暫停,而且直到cpu_down才會暫停并遷移。這會給系統帶來很多問題:
(1)有可能破壞文件系統。在系統創建hibernate image到cpu down之間,如果有進程還在修改文件系統的內容,這將會導致系統恢復之后無法完全恢復文件系統;
(2)有可能導致創建hibernation image失敗。創建hibernation image需要足夠的內存空間,但是在這期間如果還有進程在申請內存,就可能導致創建失敗;
(3)有可能干擾設備的suspend和resume。在cpu down之前,device suspend期間,如果進程還在訪問設備,尤其是訪問競爭資源,就有可能引起設備suspend異常;
(4)有可能導致進程感知系統休眠。系統休眠的理想狀態是所有任務對休眠過程無感知,睡醒之后全部自動恢復工作,但是有些進程,比如某個進程需要所有cpu online才能正常工作,如果進程不凍結,那么在休眠過程中將會工作異常。
3 代碼實現框架
凍結的對象是內核中可以被調度執行的實體,包括用戶進程、內核線程和work_queue。用戶進程默認是可以被凍結的,借用信號處理機制實現;內核線程和work_queue默認是不能被凍結的,少數內核線程和work_queue在創建時指定了freezable標志,這些任務需要對freeze狀態進行判斷,當系統進入freezing時,主動暫停運行。
標記系統freeze狀態的有三個重要的全局變量:pm_freezing、system_freezing_cnt和pm_nosig_freezing,如果全為0,表示系統未進入凍結;system_freezing_cnt>0表示系統進入凍結,pm_freezing=true表示凍結用戶進程,pm_nosig_freezing=true表示凍結內核線程和workqueue。它們會在freeze_processes和freeze_kernel_threads中置位,在thaw_processes和thaw_kernel_threads中清零。
fake_signal_wake_up函數巧妙的利用了信號處理機制,只設置任務的TIF_SIGPENDING位,但不傳遞任何信號,然后喚醒任務;這樣任務在返回用戶態時會進入信號處理流程,檢查系統的freeze狀態,并做相應處理。
任務主動調用try_to_freeze的代碼如下:
/**
?* @brief 嘗試將當前任務(進程)主動置入凍結狀態(非安全上下文)
?* @return bool - true: 凍結成功;false: 未觸發凍結條件
?* @note 此函數通常在原子上下文或中斷禁用場景調用,需確保無鎖競爭風險
?*/
static inline bool try_to_freeze_unsafe(void)
{
? ? // 檢查當前進程是否滿足凍結條件(通過freezing(current)快速判斷)
? ? if (likely(!freezing(current)))?
? ? ? ? return false; // 系統未啟用凍結或當前進程無需凍結
? ? // 調用核心凍結邏輯,參數false表示非強制凍結(允許進程自行處理信號等)
? ? return __refrigerator(false);?
}
/**
?* @brief 快速檢查系統全局凍結狀態及當前進程的凍結條件
?* @param p - 待檢查的進程描述符(struct task_struct指針)
?* @return bool - true: 需要凍結;false: 無需凍結
?* @note 通過原子變量system_freezing_cnt優化高頻調用的性能
?*/
static inline bool freezing(struct task_struct *p)
{
? ? // 原子讀取系統全局凍結計數器,若為0則快速返回(likely優化分支預測)
? ? if (likely(!atomic_read(&system_freezing_cnt)))
? ? ? ? return false; // 系統未啟用凍結功能
? ? // 進入慢速路徑,進一步檢查進程特定的凍結條件
? ? return freezing_slow_path(p);
}
/**
?* @brief 慢速路徑檢查進程的詳細凍結條件
?* @param p - 待檢查的進程描述符
?* @return bool - true: 需凍結;false: 豁免凍結
?* @note 此函數處理以下凍結策略:
?* ? ? ? 1. PF_NOFREEZE標記的進程(如內核關鍵線程)始終豁免
?* ? ? ? 2. 系統級凍結(pm_nosig_freezing)或cgroup凍結策略
?* ? ? ? 3. 用戶進程凍結(pm_freezing)且非內核線程
?*/
bool freezing_slow_path(struct task_struct *p)
{
? ? // 檢查進程是否標記為禁止凍結(如某些關鍵內核線程)
? ? if (p->flags & PF_NOFREEZE) ?
? ? ? ? return false; // 明確豁免凍結
? ? // 檢查系統是否處于"無信號"凍結模式(如休眠時凍結內核線程)
? ? // 或進程屬于需凍結的cgroup
? ? if (pm_nosig_freezing || cgroup_freezing(p)) ?
? ? ? ? return true; // 強制凍結
? ? // 檢查系統是否在凍結用戶進程,且當前進程為用戶進程(非內核線程)
? ? if (pm_freezing && !(p->flags & PF_KTHREAD))?
? ? ? ? return true; // 凍結用戶空間進程
? ? return false; // 默認不凍結
}
進入凍結狀態直到恢復的主要函數:
bool __refrigerator(bool check_kthr_stop)???????
for (;;) { ?// 無限循環,直到滿足解凍條件
? ? /* 1. 設置進程為不可中斷睡眠狀態(D狀態)
? ? ?* - TASK_UNINTERRUPTIBLE 確保進程不會被信號喚醒,僅能通過內核主動喚醒
? ? ?* - 這種狀態是凍結進程的核心,避免進程在凍結期間被調度執行 */
? ? set_current_state(TASK_UNINTERRUPTIBLE);
? ? /* 2. 獲取 freezer_lock 自旋鎖并禁用中斷(spin_lock_irq)
? ? ?* - 保護對 current->flags 的原子修改,防止競態條件
? ? ?* - 禁用中斷避免中斷處理程序并發訪問凍結狀態 */
? ? spin_lock_irq(&freezer_lock);
? ? /* 3. 標記當前進程為已凍結(PF_FROZEN)
? ? ?* - PF_FROZEN 是凍結完成的標志,被 thaw_processes() 等解凍函數檢測
? ? ?* - 此處僅設置標志,實際凍結通過后續 schedule() 放棄CPU實現 */
? ? current->flags |= PF_FROZEN;
? ? /* 4. 檢查是否需要解除凍結:
? ? ?* - freezing(current): 系統是否仍要求凍結(如休眠未完成)
? ? ?* - kthread_should_stop(): 內核線程是否收到停止信號(如模塊卸載)
? ? ?* 若任一條件為真,則清除 PF_FROZEN 標志,準備退出循環 */
? ? if (!freezing(current) || (check_kthr_stop && kthread_should_stop()))
? ? ? ? current->flags &= ~PF_FROZEN; ?// 清除凍結標志
? ? spin_unlock_irq(&freezer_lock); ?// 釋放鎖并恢復中斷
? ? /* 5. 檢查是否已解除凍結:
? ? ?* - 若 PF_FROZEN 被清除,則跳出循環,恢復執行
? ? ?* - 否則繼續進入調度等待 */
? ? if (!(current->flags & PF_FROZEN))
? ? ? ? break;
? ? /* 6. 記錄凍結狀態并主動放棄CPU
? ? ?* - was_frozen 用于返回是否曾被凍結(用于統計或調試)
? ? ?* - schedule() 觸發進程切換,當前進程進入睡眠隊列,直到被解凍喚醒 */
? ? was_frozen = true;
? ? schedule(); ?// 調用調度器,進程進入睡眠狀態
}
4 參考文獻
(1)http://www.wowotech.net/linux_kenrel/suspend_and_resume.html
(2) http://www.wowotech.net/linux_kenrel/std_str_func.html
(3) kenrel document: freezing-of-tasks.txt