Linux電源管理_autosleep--(五)

1. 前言

Autosleep也是從Android wakelocks補丁集中演化而來的(Linux電源管理(9)_wakelocks),用於取代Android wakelocks中的自動休眠功能。它基於wakeup source實現,從代碼邏輯上講,autosleep是一個簡單的功能,但背後卻埋藏着一個值得深思的話題:

計算機的休眠(通常是STR、Standby、Hibernate等suspend操作),應當在什麼時候由誰觸發?

蝸蝸在“Linux電源管理(2)_Generic PM之基本概念和軟件架構”中有提過,在傳統的操作場景下,如PC、筆記本電腦,這個問題很好回答:由用戶、在其不想或不再使用時

但在移動互聯時代,用戶隨時隨地都可能使用設備,上面的回答就不再成立,怎麼辦?這時,Android提出了“Opportunistic suspend(這個詞彙太傳神了,很難用簡潔的中文去翻譯,就不翻譯了)”的理論,通俗的講,就是“逮到機會就睡”。而autosleep功能,無論是基於Android wakelocks的autosleep,還是基於wakeup source的autosleep,都是爲了實現“Opportunistic suspend”。

相比較“對多樣的系統組件單獨控制”的電源管理方案(如Linux kernel的Dynamic PM),“Opportunistic suspend”是非常簡單的,只要檢測到系統沒有事情在做(逮到機會),就suspend整個系統。這對系統的開發人員(特別是driver開發者)來說,很容易實現,幾乎不需要特別處理。

但困難的是,“系統沒有事情在做”的判斷依據是什麼?能判斷準確嗎?會不會浪費過多的資源在"susend->resume-supsend…”的無聊動作上?如果只有一個設備在做事情,其它設備豈不是也得陪着耗電?等等…

所以,實現“Opportunistic suspend”機制的autosleep功能,是充滿爭議的。說實話,也是不優雅的。但它可以解燃眉之急,因而雖然受非議,卻在Android設備中廣泛使用。

其實Android中很多機制都是這樣的(如wakelocks,如binder,等等),可以這樣比方:Android是設計中的現實主義,Linux kernel是設計中的理想主義,當理想和現實衝突時,怎麼調和?不只是Linux kernel,其它的諸如設計、工作和生活,都會遇到類似的衝突,怎麼對待?沒有答案,但有一個原則:不要偏執,不要試圖追求非黑即白的真理!

我們應該慶幸有Android這樣的開源軟件,讓我們可以對比,可以思考。偏題有點遠,言歸正傳吧,去看看autosleep的實現。

2. 功能總結和實現原理

經過前言的瞎聊,Autosleep的功能很已經很直白了,“系統沒有事情在做”的時候,就將系統切換到低功耗狀態。

根據使用場景,低功耗狀態可以是Freeze、Standby、Suspend to RAM(STR)和Suspend to disk(STD)中的任意一種。而怎麼判斷系統沒有事情在做呢?依賴wakeup events framework。只要系統沒有正在處理和新增的wakeup events,就嘗試suspend,如果suspend的過程中有events產生,再resume就是了。

由於suspend/resume的操作如此頻繁,解決同步問題就越發重要,這也要依賴wakeup events framework及其wakeup count功能。

3. 在電源管理中的位置

autosleep的實現位於kernel/power/autosleep.c中,基於wakeup count和suspend & hibernate功能,並通過PM core的main模塊向用戶空間提供sysfs文件(/sys/power/autosleep)

Autosleep architecture

注1:我們在“Linux電源管理(8)_Wakeup count功能”中,討論過wakeup count功能,本文的autosleep,就是使用wakeup count的一個實例。

4. 代碼分析

4.1 /sys/power/autosleep

/sys/power/autosleep是在kernel/power/main.c中實現的,如下:

   1: #ifdef CONFIG_PM_AUTOSLEEP
   2: static ssize_t autosleep_show(struct kobject *kobj,
   3:                               struct kobj_attribute *attr,
   4:                               char *buf)
   5: {
   6:         suspend_state_t state = pm_autosleep_state();
   7:  
   8:         if (state == PM_SUSPEND_ON)
   9:                 return sprintf(buf, "off\n");
  10:  
  11: #ifdef CONFIG_SUSPEND
  12:         if (state < PM_SUSPEND_MAX)
  13:                 return sprintf(buf, "%s\n", valid_state(state) ?
  14:                                                 pm_states[state] : "error");
  15: #endif
  16: #ifdef CONFIG_HIBERNATION
  17:         return sprintf(buf, "disk\n");
  18: #else
  19:         return sprintf(buf, "error");
  20: #endif
  21: }
  22:  
  23: static ssize_t autosleep_store(struct kobject *kobj,
  24:                                struct kobj_attribute *attr,
  25:                                const char *buf, size_t n)
  26: {
  27:         suspend_state_t state = decode_state(buf, n);
  28:         int error;
  29:  
  30:         if (state == PM_SUSPEND_ON
  31:             && strcmp(buf, "off") && strcmp(buf, "off\n"))
  32:                 return -EINVAL;
  33:  
  34:         error = pm_autosleep_set_state(state);
  35:         return error ? error : n;
  36: }
  37:  
  38: power_attr(autosleep);
  39: #endif /* CONFIG_PM_AUTOSLEEP */

a)autosleep不是一個必須的功能,可以通過CONFIG_PM_AUTOSLEEP打開或關閉該功能。

b)autosleep文件和state文件類似:

     讀取,返回“freeze”,“standby”,“mem”,“disk”, “off”,“error”等6個字符串中的一個,表示當前autosleep的狀態,分別是auto freeze、auto standby、auto STR、auto STD、autosleep功能關閉和當前系統不支持該autosleep的錯誤指示;

     寫入freeze”,“standby”,“mem”,“disk”, “off”等5個字符串中的一個,代表將autosleep切換到指定狀態。

c)autosleep的讀取,由pm_autosleep_state實現;autosleep的寫入,由pm_autosleep_set_state實現。這兩個接口爲autosleep模塊提供的核心接口,位於kernel/power/autosleep.c中。

4.2 pm_autosleep_init

開始之前,先介紹一下autosleep的初始化函數,該函數在kernel PM初始化時(.\kernel\power\main.c:pm_init)被調用,負責初始化autosleep所需的2個全局參數:

1)一個名稱爲“autosleep”的wakeup source(autosleep_ws),在autosleep執行關鍵操作時,阻止系統休眠(我們可以從中理解wakeup source的應用場景和使用方法)。

2)一個名稱爲“autosleep”的有序workqueue,用於觸發實際的休眠動作(休眠應由經常或者線程觸發)。這裏我們要提出2個問題:什麼是有序workqueue?爲什麼要使用有序workqueue?後面分析代碼時會有答案。

如下:

   1: int __init pm_autosleep_init(void)
   2: {
   3:         autosleep_ws = wakeup_source_register("autosleep");
   4:         if (!autosleep_ws)
   5:                 return -ENOMEM;
   6:  
   7:         autosleep_wq = alloc_ordered_workqueue("autosleep", 0);
   8:         if (autosleep_wq)
   9:                 return 0;
  10:  
  11:         wakeup_source_unregister(autosleep_ws);
  12:         return -ENOMEM;
  13: }

4.3 pm_autosleep_set_state

pm_autosleep_set_state負責設置autosleep的狀態,autosleep狀態和“Linux電源管理(5)_Hibernate和Sleep功能介紹”所描述的電源管理狀態一致,共有freeze、standby、STR、STD和off五種(具體依賴於系統實際支持的電源管理狀態)。具體如下:

   1: int pm_autosleep_set_state(suspend_state_t state)
   2: {
   3:  
   4: #ifndef CONFIG_HIBERNATION
   5:         if (state >= PM_SUSPEND_MAX)
   6:                 return -EINVAL;
   7: #endif
   8:  
   9:         __pm_stay_awake(autosleep_ws);
  10:  
  11:         mutex_lock(&autosleep_lock);
  12:  
  13:         autosleep_state = state;
  14:  
  15:         __pm_relax(autosleep_ws);
  16:  
  17:         if (state > PM_SUSPEND_ON) {
  18:                 pm_wakep_autosleep_enabled(true);
  19:                 queue_up_suspend_work();
  20:         } else {
  21:                 pm_wakep_autosleep_enabled(false);
  22:         }
  23:  
  24:         mutex_unlock(&autosleep_lock);
  25:         return 0;
  26: }

a)判斷state是否合法。

b)調用__pm_stay_awake,確保系統不會休眠。

c)將state保存在一個全局變量中(autosleep_state)。

d)調用__pm_relax,允許系統休眠。

e)根據state的狀態off還是其它,調用wakeup events framework提供的接口pm_wakep_autosleep_enabled,使能或者禁止autosleep功能。

f)如果是使能狀態,調用內部接口queue_up_suspend_work,將suspend work掛到autosleep workqueue中。

 

注2:由這裏的實例可以看出,此時wakeup source不再是wakeup events的載體,而更像一個lock(呵呵,Android wakelocks的影子)。

注3:
該接口並沒有對autosleep state的當前值做判斷,也就意味着用戶程序可以不停的調用該接口,設置autosleep state,如寫“mem”,寫“freeze”,寫“disk”等等。那麼suspend work將會多次queue到wrokqueue上。
而在多核CPU上,普通的workqueue是可以在多個CPU上並行執行多個work的。這恰恰是autosleep所不能接受的,因此autosleep workqueue就必須是orderd workqueue。所謂ordered workqueue,就是統一時刻最多執行一個work的worqueue(具體可參考include\linux\workqueue.h中的註釋)。
那我們再問,爲什麼不判斷一下狀態內?首先,orderd workqueue可以節省資源。其次,這樣已經夠了,何必多費心思呢?簡潔就是美。

pm_wakep_autosleep_enabled主要用於更新wakeup source中和autosleep有關的信息,代碼和執行邏輯如下:

   1: #ifdef CONFIG_PM_AUTOSLEEP
   2: /**
   3:  * pm_wakep_autosleep_enabled - Modify autosleep_enabled for all wakeup sources.
   4:  * @enabled: Whether to set or to clear the autosleep_enabled flags.
   5:  */
   6: void pm_wakep_autosleep_enabled(bool set)
   7: {
   8:         struct wakeup_source *ws;
   9:         ktime_t now = ktime_get();
  10:  
  11:         rcu_read_lock();
  12:         list_for_each_entry_rcu(ws, &wakeup_sources, entry) {
  13:                 spin_lock_irq(&ws->lock);
  14:                 if (ws->autosleep_enabled != set) {
  15:                         ws->autosleep_enabled = set;
  16:                         if (ws->active) {
  17:                                 if (set)
  18:                                         ws->start_prevent_time = now;
  19:                                 else
  20:                                         update_prevent_sleep_time(ws, now);
  21:                         }
  22:                 }
  23:                 spin_unlock_irq(&ws->lock);
  24:         }
  25:         rcu_read_unlock();
  26: }
  27: #endif /* CONFIG_PM_AUTOSLEEP */

a)更新系統所有wakeup souce的autosleep_enabled標誌(太浪費了!!)。

b)如果wakeup source處於active狀態(意味着它會阻止autosleep),且當前autosleep爲enable,將start_prevent_time設置爲當前實現(開始阻止)。

c)如果wakeup source處於active狀態,且autosleep爲disable(說明這個wakeup source一直堅持到autosleep被禁止),調用update_prevent_sleep_time接口,更新wakeup source的prevent_sleep_time。

queue_up_suspend_work比較簡單,就是把suspend_work掛到workqueue,等待被執行。而suspend_work的處理函數爲try_to_suspend,如下:

   1: static DECLARE_WORK(suspend_work, try_to_suspend);
   2:  
   3: void queue_up_suspend_work(void)
   4: {
   5:         if (autosleep_state > PM_SUSPEND_ON)
   6:                 queue_work(autosleep_wq, &suspend_work);
   7: }

4.4 try_to_suspend

try_to_suspend是suspend的實際觸發者,代碼如下:

   1: static void try_to_suspend(struct work_struct *work)
   2: {
   3:         unsigned int initial_count, final_count;
   4:  
   5:         if (!pm_get_wakeup_count(&initial_count, true))
   6:                 goto out;
   7:  
   8:         mutex_lock(&autosleep_lock);
   9:  
  10:         if (!pm_save_wakeup_count(initial_count) ||
  11:                 system_state != SYSTEM_RUNNING) {
  12:                 mutex_unlock(&autosleep_lock);
  13:                 goto out;
  14:         }
  15:  
  16:         if (autosleep_state == PM_SUSPEND_ON) {
  17:                 mutex_unlock(&autosleep_lock);
  18:                 return;
  19:         }
  20:         if (autosleep_state >= PM_SUSPEND_MAX)
  21:                 hibernate();
  22:         else
  23:                 pm_suspend(autosleep_state);
  24:  
  25:         mutex_unlock(&autosleep_lock);
  26:  
  27:         if (!pm_get_wakeup_count(&final_count, false))
  28:                 goto out;
  29:  
  30:         /*
  31:          * If the wakeup occured for an unknown reason, wait to prevent the
  32:          * system from trying to suspend and waking up in a tight loop.
  33:          */
  34:         if (final_count == initial_count)
  35:                 schedule_timeout_uninterruptible(HZ / 2);
  36:  
  37:  out:
  38:         queue_up_suspend_work();
  39: }

該接口是wakeup count的一個例子,根據我們在“Linux電源管理(8)_Wakeup count功能”的分析,就是read wakeup count,write wakeup count,suspend,具體爲:

a)調用pm_get_wakeup_count(block爲true),獲取wakeup count,保存在initial_count中。如果有wakeup events正在處理,阻塞等待。

b)將讀取的count,寫入。如果成功,且當前系統狀態爲running,根據autosleep狀態,調用hibernate或者pm_suspend,suspend系統。

d)如果寫count失敗,說明讀寫的過程有events產生,退出,進行下一次嘗試。

e)如果suspend的過程中,或者是suspend之後,產生了events,醒來,再讀一次wakeup count(此時不再阻塞),保存在final_count中。

f)如果final_count和initial_count相同,發生怪事了,沒有產生events,竟然醒了。可能有異常,不能再立即啓動autosleep(恐怕陷入sleep->wakeup->sleep->wakeup的快速loop中),等待0.5s,再嘗試autosleep。

4.5 pm_autosleep_state

該接口比較簡單,獲取autosleep_state的值返回即可。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章