1. 程式人生 > >Linux電源管理(9)_wakelocks

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更為詳細的描述,可以參考下面的一個連結:

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。

kernel wakelocks architecture

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指標,判斷其