http://blog.csdn.net/qinjuning/article/details/6978560
http://blog.csdn.net/qinjuning/article/details/7009824
http://blog.csdn.net/qinjuning/article/details/7015313
2016年8月30日 星期二
[轉] Linux電源管理(9)_wakelocks
1. 前言
wakelocks是一個有故事的功能。
wakelocks最初出現在Android為linux kernel打的一個補丁集上,該補丁集實現了一個名稱為「wakelocks」的系統調用,該系統調用允許調用者阻止系統進入低功耗模式(如idle、suspend等)。同時,該補丁集更改了Linux kernel原生的電源管理執行過程(kernel/power/main.c中的state_show和state_store),轉而執行自定義的state_show、state_store。
這種做法是相當不規範的,它是典型的只求實現功能,不擇手段。就像國內很多的Linux開發團隊,要實現某個功能,都不去弄清楚kernel現有的機制、框架,牛逼哄哄的猛幹一番。最後功能是實現了,可都不知道重複造了多少輪子,浪費了多少資源。到此打住,Android的開發者不會這麼草率,他們推出wakelocks機制一定有一些苦衷,我們就不評論了。
但是,雖然有苦衷,kernel的開發者可是有原則的,死活不讓這種機制合併到kernel分支(換誰也不讓啊),直到kernel自身的wakeup events framework成熟後,這種僵局才被打破。因為Android開發者想到了一個壞點子:不讓合併就不讓合併唄,我用你的機制(wakeup source),再實現一個就是了。至此,全新的wakelocks出現了。
所以wakelocks有兩個,早期Android版本的wakelocks幾乎已經銷聲匿跡了,不仔細找還真找不到它的source code(這裡有一個鏈接,但願讀者看到時還有效,drivers/android/power.c)。本文不打算翻那本舊黃曆,所以就focus在新的wakelocks上(drivers/power/wakelock.c,較新的kernel都支持)。
2. Android wakelocks
雖說不翻舊黃曆了,還是要提一下Android wakelocks的功能,這樣才能知道kernel wakelocks要做什麼。總的來說,Android wakelocks提供的功能包括:
1)一個sysfs文件:/sys/power/wake_lock,用戶程序向文件寫入一個字符串,即可創建一個wakelock,該字符串就是wakelock的名字。該wakelock可以阻止系統進入低功耗模式。
2)一個sysfs文件::/sys/power/wake_unlock,用戶程序向文件寫入相同的字符串,即可註銷一個wakelock。
3)當系統中所有的wakelock都註銷後,系統可以自動進入低功耗狀態。
4)向內核其它driver也提供了wakelock的創建和註銷接口,允許driver創建wakelock以阻止睡眠、註銷wakelock以允許睡眠。
有關Android wakelocks更為詳細的描述,可以參考下面的一個鏈接:
3. Kernel wakelocks
3.1 Kernel wakelocks的功能
對比Android wakelocks要實現的功能,Linux kernel的方案是:
1)允許driver創建wakelock以阻止睡眠、註銷wakelock以允許睡眠:已經由「Linux電源管理(7)_Wakeup events framework」所描述的wakeup source取代。
2)當系統中所有的wakelock都註銷後,系統可以自動進入低功耗狀態:由autosleep實現(下一篇文章會分析)。
3)wake_lock和wake_unlock功能:由本文所描述的kernel wakelocks實現,其本質就是將wakeup source開發到用戶空間訪問。
3.2 Kernel wakelocks在電源管理中的位置
相比Android wakelocks,Kernel wakelocks的實現非常簡單(簡單的才是最好的),就是在PM core中增加一個wakelock模塊(kernel/power/wakelock.c),該模塊依賴wakeup events framework提供的wakeup source機制,實現用戶空間的wakeup source(就是wakelocks),並通過PM core main模塊,向用戶空間提供兩個同名的sysfs文件,wake_lock和wake_unlock。
3.3 /sys/power/wake_lock & /sys/power/wake_unlock
從字面意思上,新版的wake_lock和wake_unlock和舊版的一樣,都是用於創建和註銷wakelock。從應用開發者的角度,確實可以這樣理解。但從底層實現的角度,卻完全不是一回事。
Android的wakelock,真是一個lock,用戶程序創建一個wakelock,就是在系統suspend的路徑上加了一把鎖,註銷就是解開這把鎖。直到suspend路徑上所有的鎖都解開時,系統才可以suspend。
而Kernel的wakelock,是基於wakeup source實現的,因此創建wakelock的本質是在指定的wakeup source上activate一個wakeup event,註銷wakelock的本質是deactivate wakeup event。因此,/sys/power/wake_lock和/sys/power/wake_unlock兩個sysfs文件的的功能就是:
寫wake_lock(以wakelock name和timeout時間<可選>為參數),相當於以wakeup source為參數調用__pm_stay_awake(或者__pm_wakeup_event),即activate wakeup event;
寫wake_unlock(以wakelock name為參數),相當於以wakeup source為參數,調用__pm_relax;
讀wake_lock,獲取系統中所有的處於active狀態的wakelock列表(也即wakeup source列表)
讀wake_unlock,返回系統中所有的處於非active狀態的wakelock信息(也即wakeup source列表)。
這兩個sysfs文件在kernel/power/main.c中實現,如下:
1: #ifdef CONFIG_PM_WAKELOCKS
2: static ssize_t wake_lock_show(struct kobject *kobj,
3: struct kobj_attribute *attr,
4: char *buf)
5: {
6: return pm_show_wakelocks(buf, true);
7: }
8:
9: static ssize_t wake_lock_store(struct kobject *kobj,
10: struct kobj_attribute *attr,
11: const char *buf, size_t n)
12: {
13: int error = pm_wake_lock(buf);
14: return error ? error : n;
15: }
16:
17: power_attr(wake_lock);
18:
19: static ssize_t wake_unlock_show(struct kobject *kobj,
20: struct kobj_attribute *attr,
21: char *buf)
22: {
23: return pm_show_wakelocks(buf, false);
24: }
25:
26: static ssize_t wake_unlock_store(struct kobject *kobj,
27: struct kobj_attribute *attr,
28: const char *buf, size_t n)
29: {
30: int error = pm_wake_unlock(buf);
31: return error ? error : n;
32: }
33:
34: power_attr(wake_unlock);
35:
36: #endif /* CONFIG_PM_WAKELOCKS */
1)wakelocks功能不是linux kernel的必選功能,可以通過CONFIG_PM_WAKELOCKS開關。2)wake_lock的寫接口,直接調用pm_wake_lock;wake_unlock的寫接口,直接調用pm_wake_unlock;它們的讀接口,直接調用pm_show_wakelocks接口(參數不同)。這三個接口均在kernel/power/wakelock.c中實現。
3.4 pm_wake_lock
pm_wake_lock位於kernel\power\wakelock.c中,用於上報一個wakeup event(從另一個角度,就是阻止系統suspend),代碼如下:
1: int pm_wake_lock(const char *buf)
2: {
3: const char *str = buf;
4: struct wakelock *wl;
5: u64 timeout_ns = 0;
6: size_t len;
7: int ret = 0;
8:
9: if (!capable(CAP_BLOCK_SUSPEND))
10: return -EPERM;
11:
12: while (*str && !isspace(*str))
13: str++;
14:
15: len = str - buf;
16: if (!len)
17: return -EINVAL;
18:
19: if (*str && *str != '\n') {
20: /* Find out if there's a valid timeout string appended. */
21: ret = kstrtou64(skip_spaces(str), 10, &timeout_ns);
22: if (ret)
23: return -EINVAL;
24: }
25:
26: mutex_lock(&wakelocks_lock);
27:
28: wl = wakelock_lookup_add(buf, len, true);
29: if (IS_ERR(wl)) {
30: ret = PTR_ERR(wl);
31: goto out;
32: }
33: if (timeout_ns) {
34: u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;
35:
36: do_div(timeout_ms, NSEC_PER_MSEC);
37: __pm_wakeup_event(&wl->ws, timeout_ms);
38: } else {
39: __pm_stay_awake(&wl->ws);
40: }
41:
42: wakelocks_lru_most_recent(wl);
43: out:
44: mutex_unlock(&wakelocks_lock);
45: return ret;
46: }
a)輸入參數為一個字符串,如"wake_lock_test 1000」,該字符串指定上報wakeup event的wakelock name,可以在name後用空格隔開,添加一個時間值(單位為ns),表示該event的timeout值。b)調用capable,檢查當前進程是否具備阻止系統suspend的權限。
注2:capable是Linux security子系統提供的一個接口,用於權限判斷。我們說過,power是系統的核心資源,理應由OS全權管理,但wakelock違反了這一原則,將阻止系統睡眠的權利給了用戶空間。這樣一來,用戶空間程序將可以隨心所欲的佔用power資源,特別是用戶態的程序員,天生對資源佔用不敏感(這是對的),就導致該接口有被濫用的風險。不過還好,通過系統的權限管理機制,可以改善這種狀態(其實不是改善,而是矛盾轉移,很有可能把最終的裁決權交給用戶,太糟糕了!)。c)解析字符串,將timeout值(有的話)保存在timeout_ns中,解析name長度(len),並將name保存在原來的buf中。d)調用wakelock_lookup_add接口,查找是否有相同name的wakelock。如果有,直接返回wakelock的指針;如果沒有,分配一個wakelock,同時調用wakeup events framework提供的接口,創建該wakelock對應的wakeup source結構。e)如果指定timeout值,以wakelock的wakeup source指針為參數,調用__pm_wakeup_event接口,上報一個具有時限的wakeup events;否則,調用__pm_stay_awake,上報一個沒有時限的wakeup event。有關這兩個接口的詳細說明,可參考「Linux電源管理(7)_Wakeup events framework」。
wakelock_lookup_add是內部接口,代碼如下:
1: static struct wakelock *wakelock_lookup_add(const char *name, size_t len,
2: bool add_if_not_found)
3: {
4: struct rb_node **node = &wakelocks_tree.rb_node;
5: struct rb_node *parent = *node;
6: struct wakelock *wl;
7:
8: while (*node) {
9: int diff;
10:
11: parent = *node;
12: wl = rb_entry(*node, struct wakelock, node);
13: diff = strncmp(name, wl->name, len);
14: if (diff == 0) {
15: if (wl->name[len])
16: diff = -1;
17: else
18: return wl;
19: }
20: if (diff < 0)
21: node = &(*node)->rb_left;
22: else
23: node = &(*node)->rb_right;
24: }
25: if (!add_if_not_found)
26: return ERR_PTR(-EINVAL);
27:
28: if (wakelocks_limit_exceeded())
29: return ERR_PTR(-ENOSPC);
30:
31: /* Not found, we have to add a new one. */
32: wl = kzalloc(sizeof(*wl), GFP_KERNEL);
33: if (!wl)
34: return ERR_PTR(-ENOMEM);
35:
36: wl->name = kstrndup(name, len, GFP_KERNEL);
37: if (!wl->name) {
38: kfree(wl);
39: return ERR_PTR(-ENOMEM);
40: }
41: wl->ws.name = wl->name;
42: wakeup_source_add(&wl->ws);
43: rb_link_node(&wl->node, parent, node);
44: rb_insert_color(&wl->node, &wakelocks_tree);
45: wakelocks_lru_add(wl);
46: increment_wakelocks_number();
47: return wl;
48: }
在wakelock.c中,維護一個名稱為wakelocks_tree的紅黑樹(紅黑樹都用上了,可以想像wakelocks曾經使用多麼頻繁!),所有的wakelock都保存在該tree上。因此該接口的動作是:a)查找紅黑樹,如果找到name相同的wakelock,返回wakelock指針。b)如果沒找到,且add_if_not_found為false,返回錯誤。c)如果add_if_not_found為true,分配一個struct wakelock變量,並初始化它的名稱、它的wakeup source的名稱。調用wakeup_source_add接口,將wakeup source添加到wakeup events framework中。d)將該wakelock添加到紅黑樹。e)最後調用wakelocks_lru_add接口,將新分配的wakeup添加到一個名稱為wakelocks_lru_list的鏈表前端(該功能和wakelock的垃圾回收機制有關,後面會單獨描述)。
再看一下struct wakelock結構:
1: struct wakelock {
2: char *name;
3: struct rb_node node;
4: struct wakeup_source ws;
5: #ifdef CONFIG_PM_WAKELOCKS_GC
6: struct list_head lru;
7: #endif
8: };
非常簡單:一個name指針,保存wakelock的名稱;一個rb node節點,用於組成紅黑樹;一個wakeup source變量;如果開啟了wakelocks垃圾回收功能,一個用於GC的list head。
3.5 pm_wake_unlock
pm_wake_unlock和pm_wake_lock類似,如下:
1: int pm_wake_unlock(const char *buf)
2: {
3: struct wakelock *wl;
4: size_t len;
5: int ret = 0;
6:
7: if (!capable(CAP_BLOCK_SUSPEND))
8: return -EPERM;
9:
10: len = strlen(buf);
11: if (!len)
12: return -EINVAL;
13:
14: if (buf[len-1] == '\n')
15: len--;
16:
17: if (!len)
18: return -EINVAL;
19:
20: mutex_lock(&wakelocks_lock);
21:
22: wl = wakelock_lookup_add(buf, len, false);
23: if (IS_ERR(wl)) {
24: ret = PTR_ERR(wl);
25: goto out;
26: }
27: __pm_relax(&wl->ws);
28:
29: wakelocks_lru_most_recent(wl);
30: wakelocks_gc();
31:
32: out:
33: mutex_unlock(&wakelocks_lock);
34: return ret;
35: }
a)輸入參數為一個字符串,如"wake_lock_test」,該字符串指定一個wakelock name。b)調用capable,檢查當前進程是否具備阻止系統suspend的權限。c)解析字符串d)調用wakelock_lookup_add接口,查找是否有相同name的wakelock。如果有,直接返回wakelock的指針;如果沒有,退出。e)調用__pm_relax接口,deactive wakelock對應的wakeup source。f)調用wakelocks_lru_most_recent接口,將蓋wakelock移到wakelocks_lru_list鏈表的前端(表示它是最近一個被訪問到的,和GC有關,後面重點描述)。g)調用wakelocks_gc,執行wakelock的垃圾回收動作。
3.6 pm_show_wakelocks
該接口很簡單,查詢紅黑樹,返回處於acvtive或者deactive狀態的wakelock,如下:
1: ssize_t pm_show_wakelocks(char *buf, bool show_active)
2: {
3: struct rb_node *node;
4: struct wakelock *wl;
5: char *str = buf;
6: char *end = buf + PAGE_SIZE;
7:
8: mutex_lock(&wakelocks_lock);
9:
10: for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) {
11: wl = rb_entry(node, struct wakelock, node);
12: if (wl->ws.active == show_active)
13: str += scnprintf(str, end - str, "%s ", wl->name);
14: }
15: if (str > buf)
16: str--;
17:
18: str += scnprintf(str, end - str, "\n");
19:
20: mutex_unlock(&wakelocks_lock);
21: return (str - buf);
22: }
1)遍歷紅黑樹,拿到wakelock指針,判斷其中的wakeup source的active變量,如果和輸入變量(show_active)相符,將該wakelock的名字添加在buf中。2)調整buf的長度和結束符,返回長度值。
3.7 wakelocks的垃圾回收機制
由上面的邏輯可知,一個wakelock的生命週期,應只存在於wakeup event的avtive時期內,因此如果它的wakeup source狀態為deactive,應該銷毀該wakelock。但銷毀後,如果又產生wakeup events,就得重新建立。如果這種建立->銷毀->建立的過程太頻繁,效率就會降低。
因此,最好不銷毀,保留系統所有的wakelocks(同時可以完整的保留wakelock信息),但如果wakelocks太多(特別是不活動的),將會佔用很多內存,也不合理。
折衷方案,保留一些非active狀態的wakelock,到一定的時機時,再銷毀,這就是wakelocks的垃圾回收(GC)機制。
wakelocks GC功能可以開關(由CONFIG_PM_WAKELOCKS_GC控制),如果關閉,系統會保留所有的wakelocks,如果打開,它的處理邏輯也很簡單:
1)定義一個list head,保存所有的wakelock指針,如下:
1: static LIST_HEAD(wakelocks_lru_list);
2: static unsigned int wakelocks_gc_count;
2)在wakelock結構中,嵌入一個list head(lru),用於掛入wakelocks_lru_list。可參考3.4小節的描述。
3)wakelocks_lru_list中的wakelock是按訪問順序排列的,最近訪問的,靠近head位置。這是由3種操作保證的:
a)wakelock創建時(見3.4小節),調用wakelocks_lru_add接口,將改wakelock掛到wakelocks_lru_list的head處(利用list_add接口),表示它是最近被訪問的。b)pm_wake_lock或者pm_wake_unlock時,調用wakelocks_lru_most_recent接口,將該wakelcok移到鏈表的head處,表示最近訪問。c)每當pm_wake_unlock時,調用wakelocks_gc,執行wakelock的垃圾回收動作。wakelocks_gc的實現如下:
1: static void wakelocks_gc(void)
2: {
3: struct wakelock *wl, *aux;
4: ktime_t now;
5:
6: if (++wakelocks_gc_count <= WL_GC_COUNT_MAX)
7: return;
8:
9: now = ktime_get();
10: list_for_each_entry_safe_reverse(wl, aux, &wakelocks_lru_list, lru) {
11: u64 idle_time_ns;
12: bool active;
13:
14: spin_lock_irq(&wl->ws.lock);
15: idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws.last_time));
16: active = wl->ws.active;
17: spin_unlock_irq(&wl->ws.lock);
18:
19: if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC))
20: break;
21:
22: if (!active) {
23: wakeup_source_remove(&wl->ws);
24: rb_erase(&wl->node, &wakelocks_tree);
25: list_del(&wl->lru);
26: kfree(wl->name);
27: kfree(wl);
28: decrement_wakelocks_number();
29: }
30: }
31: wakelocks_gc_count = 0;
32: }
1)如果當前wakelocks的數目小於最大值(由WL_GC_COUNT_MAX配置,當前代碼為100),不回收,直接返回。2)否則,從wakelocks_lru_most_recent的尾部(最不活躍的),依次取出wakelock,判斷它的idle時間(通過wakeup source lst_time和當前時間計算)是否超出預設值(由WL_GC_TIME_SEC指定,當前為300s,好長),如果超出且處於deactive狀態,調用wakeup_source_remove,註銷wakeup source,同時把它從紅黑樹、GC list中去掉,並釋放memory資源。
訂閱:
文章 (Atom)