Linux电源管理(10)_autosleep
Linux電源管理(10)_autosleep
作者:wowo?發(fā)布于:2014-9-18 23:42 分類:電源管理子系統(tǒng)
1. 前言
Autosleep也是從Android wakelocks補(bǔ)丁集中演化而來的(Linux電源管理(9)_wakelocks),用于取代Android wakelocks中的自動休眠功能。它基于wakeup source實(shí)現(xiàn),從代碼邏輯上講,autosleep是一個簡單的功能,但背后卻埋藏著一個值得深思的話題:
計(jì)算機(jī)的休眠(通常是STR、Standby、Hibernate等suspend操作),應(yīng)當(dāng)在什么時(shí)候、由誰觸發(fā)?
蝸蝸在“Linux電源管理(2)_Generic PM之基本概念和軟件架構(gòu)”中有提過,在傳統(tǒng)的操作場景下,如PC、筆記本電腦,這個問題很好回答:由用戶、在其不想或不再使用時(shí)。
但在移動互聯(lián)時(shí)代,用戶隨時(shí)隨地都可能使用設(shè)備,上面的回答就不再成立,怎么辦?這時(shí),Android提出了“Opportunistic suspend(這個詞匯太傳神了,很難用簡潔的中文去翻譯,就不翻譯了)”的理論,通俗的講,就是“逮到機(jī)會就睡”。而autosleep功能,無論是基于Android wakelocks的autosleep,還是基于wakeup source的autosleep,都是為了實(shí)現(xiàn)“Opportunistic suspend”。
相比較“對多樣的系統(tǒng)組件單獨(dú)控制”的電源管理方案(如Linux kernel的Dynamic PM),“Opportunistic suspend”是非常簡單的,只要檢測到系統(tǒng)沒有事情在做(逮到機(jī)會),就suspend整個系統(tǒng)。這對系統(tǒng)的開發(fā)人員(特別是driver開發(fā)者)來說,很容易實(shí)現(xiàn),幾乎不需要特別處理。
但困難的是,“系統(tǒng)沒有事情在做”的判斷依據(jù)是什么?能判斷準(zhǔn)確嗎?會不會浪費(fèi)過多的資源在"susend->resume-supsend…”的無聊動作上?如果只有一個設(shè)備在做事情,其它設(shè)備豈不是也得陪著耗電?等等…
所以,實(shí)現(xiàn)“Opportunistic suspend”機(jī)制的autosleep功能,是充滿爭議的。說實(shí)話,也是不優(yōu)雅的。但它可以解燃眉之急,因而雖然受非議,卻在Android設(shè)備中廣泛使用。
其實(shí)Android中很多機(jī)制都是這樣的(如wakelocks,如binder,等等),可以這樣比方:Android是設(shè)計(jì)中的現(xiàn)實(shí)主義,Linux kernel是設(shè)計(jì)中的理想主義,當(dāng)理想和現(xiàn)實(shí)沖突時(shí),怎么調(diào)和?不只是Linux kernel,其它的諸如設(shè)計(jì)、工作和生活,都會遇到類似的沖突,怎么對待?沒有答案,但有一個原則:不要偏執(zhí),不要試圖追求非黑即白的真理!
我們應(yīng)該慶幸有Android這樣的開源軟件,讓我們可以對比,可以思考。偏題有點(diǎn)遠(yuǎn),言歸正傳吧,去看看autosleep的實(shí)現(xiàn)。
2. 功能總結(jié)和實(shí)現(xiàn)原理
經(jīng)過前言的瞎聊,Autosleep的功能很已經(jīng)很直白了,“系統(tǒng)沒有事情在做”的時(shí)候,就將系統(tǒng)切換到低功耗狀態(tài)。
根據(jù)使用場景,低功耗狀態(tài)可以是Freeze、Standby、Suspend to RAM(STR)和Suspend to disk(STD)中的任意一種。而怎么判斷系統(tǒng)沒有事情在做呢?依賴wakeup events framework。只要系統(tǒng)沒有正在處理和新增的wakeup events,就嘗試suspend,如果suspend的過程中有events產(chǎn)生,再resume就是了。
由于suspend/resume的操作如此頻繁,解決同步問題就越發(fā)重要,這也要依賴wakeup events framework及其wakeup count功能。
3. 在電源管理中的位置
autosleep的實(shí)現(xiàn)位于kernel/power/autosleep.c中,基于wakeup count和suspend & hibernate功能,并通過PM core的main模塊向用戶空間提供sysfs文件(/sys/power/autosleep)
注1:我們在“Linux電源管理(8)_Wakeup count功能”中,討論過wakeup count功能,本文的autosleep,就是使用wakeup count的一個實(shí)例。
4. 代碼分析
4.1 /sys/power/autosleep
/sys/power/autosleep是在kernel/power/main.c中實(shí)現(xiàn)的,如下:
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打開或關(guān)閉該功能。
b)autosleep文件和state文件類似:
???? 讀取,返回“freeze”,“standby”,“mem”,“disk”, “off”,“error”等6個字符串中的一個,表示當(dāng)前autosleep的狀態(tài),分別是auto freeze、auto standby、auto STR、auto STD、autosleep功能關(guān)閉和當(dāng)前系統(tǒng)不支持該autosleep的錯誤指示;
???? 寫入freeze”,“standby”,“mem”,“disk”, “off”等5個字符串中的一個,代表將autosleep切換到指定狀態(tài)。
c)autosleep的讀取,由pm_autosleep_state實(shí)現(xiàn);autosleep的寫入,由pm_autosleep_set_state實(shí)現(xiàn)。這兩個接口為autosleep模塊提供的核心接口,位于kernel/power/autosleep.c中。
4.2 pm_autosleep_init
開始之前,先介紹一下autosleep的初始化函數(shù),該函數(shù)在kernel PM初始化時(shí)(.\kernel\power\main.c:pm_init)被調(diào)用,負(fù)責(zé)初始化autosleep所需的2個全局參數(shù):
1)一個名稱為“autosleep”的wakeup source(autosleep_ws),在autosleep執(zhí)行關(guān)鍵操作時(shí),阻止系統(tǒng)休眠(我們可以從中理解wakeup source的應(yīng)用場景和使用方法)。
2)一個名稱為“autosleep”的有序workqueue,用于觸發(fā)實(shí)際的休眠動作(休眠應(yīng)由經(jīng)常或者線程觸發(fā))。這里我們要提出2個問題:什么是有序workqueue?為什么要使用有序workqueue?后面分析代碼時(shí)會有答案。
如下:
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負(fù)責(zé)設(shè)置autosleep的狀態(tài),autosleep狀態(tài)和“Linux電源管理(5)_Hibernate和Sleep功能介紹”所描述的電源管理狀態(tài)一致,共有freeze、standby、STR、STD和off五種(具體依賴于系統(tǒng)實(shí)際支持的電源管理狀態(tài))。具體如下:
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)調(diào)用__pm_stay_awake,確保系統(tǒng)不會休眠。
c)將state保存在一個全局變量中(autosleep_state)。
d)調(diào)用__pm_relax,允許系統(tǒng)休眠。
e)根據(jù)state的狀態(tài)off還是其它,調(diào)用wakeup events framework提供的接口pm_wakep_autosleep_enabled,使能或者禁止autosleep功能。
f)如果是使能狀態(tài),調(diào)用內(nèi)部接口queue_up_suspend_work,將suspend work掛到autosleep workqueue中。
?
注2:由這里的實(shí)例可以看出,此時(shí)wakeup source不再是wakeup events的載體,而更像一個lock(呵呵,Android wakelocks的影子)。
注3:?
該接口并沒有對autosleep state的當(dāng)前值做判斷,也就意味著用戶程序可以不停的調(diào)用該接口,設(shè)置autosleep state,如寫“mem”,寫“freeze”,寫“disk”等等。那么suspend work將會多次queue到wrokqueue上。?
而在多核CPU上,普通的workqueue是可以在多個CPU上并行執(zhí)行多個work的。這恰恰是autosleep所不能接受的,因此autosleep workqueue就必須是orderd workqueue。所謂ordered workqueue,就是統(tǒng)一時(shí)刻最多執(zhí)行一個work的worqueue(具體可參考include\linux\workqueue.h中的注釋)。?
那我們再問,為什么不判斷一下狀態(tài)內(nèi)?首先,orderd workqueue可以節(jié)省資源。其次,這樣已經(jīng)夠了,何必多費(fèi)心思呢?簡潔就是美。?
pm_wakep_autosleep_enabled主要用于更新wakeup source中和autosleep有關(guān)的信息,代碼和執(zhí)行邏輯如下:
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)更新系統(tǒng)所有wakeup souce的autosleep_enabled標(biāo)志(太浪費(fèi)了!!)。
b)如果wakeup source處于active狀態(tài)(意味著它會阻止autosleep),且當(dāng)前autosleep為enable,將start_prevent_time設(shè)置為當(dāng)前實(shí)現(xiàn)(開始阻止)。
c)如果wakeup source處于active狀態(tài),且autosleep為disable(說明這個wakeup source一直堅(jiān)持到autosleep被禁止),調(diào)用update_prevent_sleep_time接口,更新wakeup source的prevent_sleep_time。
queue_up_suspend_work比較簡單,就是把suspend_work掛到workqueue,等待被執(zhí)行。而suspend_work的處理函數(shù)為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的實(shí)際觸發(fā)者,代碼如下:
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的一個例子,根據(jù)我們在“Linux電源管理(8)_Wakeup count功能”的分析,就是read wakeup count,write wakeup count,suspend,具體為:
a)調(diào)用pm_get_wakeup_count(block為true),獲取wakeup count,保存在initial_count中。如果有wakeup events正在處理,阻塞等待。
b)將讀取的count,寫入。如果成功,且當(dāng)前系統(tǒng)狀態(tài)為running,根據(jù)autosleep狀態(tài),調(diào)用hibernate或者pm_suspend,suspend系統(tǒng)。
d)如果寫count失敗,說明讀寫的過程有events產(chǎn)生,退出,進(jìn)行下一次嘗試。
e)如果suspend的過程中,或者是suspend之后,產(chǎn)生了events,醒來,再讀一次wakeup count(此時(shí)不再阻塞),保存在final_count中。
f)如果final_count和initial_count相同,發(fā)生怪事了,沒有產(chǎn)生events,竟然醒了。可能有異常,不能再立即啟動autosleep(恐怕陷入sleep->wakeup->sleep->wakeup的快速loop中),等待0.5s,再嘗試autosleep。
4.5 pm_autosleep_state
該接口比較簡單,獲取autosleep_state的值返回即可。
總結(jié)
以上是生活随笔為你收集整理的Linux电源管理(10)_autosleep的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: LCD 进入休眠的操作解决方式
- 下一篇: Linux Graphic DRI 显