2016年8月30日 星期二

[轉] ActivityManager的使用

http://blog.csdn.net/qinjuning/article/details/6978560
http://blog.csdn.net/qinjuning/article/details/7009824
http://blog.csdn.net/qinjuning/article/details/7015313

[轉] 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。
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指針,判斷其中的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資源。