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
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更為詳細的描述,可以參考下面的一個連結:
http://elinux.org/Android_Power_Management
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列表)。
注1:上面有關wakeup source的操作介面,可參考“Linux電源管理(7)_Wakeup events framework”。
這兩個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指標,判斷其