Linux电源管理

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。

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资源。

原创文章,转发请注明出处。蜗窝科技,www.wowotech.net

(0)

相关推荐

  • 【Android休眠】之Android休眠机制

    一.休眠概述 休眠,简而言之就是设备在不需要工作的时候把一些部件.外设关掉(掉电或让它进入低功耗模式). 为什么要休眠呢?一言以蔽之:省电. 休眠分主动休眠和被动休眠.主动休眠:比如我电脑不用了,就通 ...

  • 万字整理,肝翻Linux内存管理所有知识点

    Linux的内存管理可谓是学好Linux的必经之路,也是Linux的关键知识点,有人说打通了内存管理的知识,也就打通了Linux的任督二脉,这一点不夸张.有人问网上有很多Linux内存管理的内容,为什 ...

  • Linux 内存管理之vmalloc

    走进vmalloc 根据前面的系列文章,我们知道了buddy system是基于页框分配器,kmalloc是基于slab分配器,而且这些分配的地址都是物理内存连续的.但是随着碎片化的积累,连续物理内存 ...

  • 未来汽车的电源管理发展趋势研究

    本文来源:智车科技 / 导读 / 在汽车领域,电子系统已变得越来越密集,互连程度也越来越高.这些复杂系统需要在越来越小的空间里放入电源转换器,导致各敏感系统彼此靠近,EMI(电磁干扰)的问题在所难免. ...

  • linux进程管理:进程,程序,线程 & 9个进程管理工具 & 作业控制

    程序 程序:一组指令以及参数集合,按照相应的逻辑控制计算机完成特性任务 (一个程序可以产生多个进程) linux程序:系统,用户,固件 (包含指令,参数) 程序 ------>系统调用----- ...

  • Linux 内存管理之CMA

    什么是CMA CMA是reserved的一块内存,用于分配连续的大块内存.当设备驱动不用时,内存管理系统将该区域用于分配和管理可移动类型页面:当设备驱动使用时,此时已经分配的页面需要进行迁移,又用于连 ...

  • 3.1.1 linux用户管理 : 常用查询(debian和RedHat通用) : 用户查询

    3.1.1 linux用户管理 : 常用查询(debian和RedHat通用) : 用户查询

  • 常见的8种电源管理芯片

    描述 1.AC/DC调制IC:内含低电压控制电路及高压开关晶体管. 2.DC/DC调制IC:包括升压降压调节器,以及电荷泵. 3.功率因数控制PFC预调制IC:提供具有功率因数校正功能的电源输入电路. ...

  • 电源管理芯片8个引脚说明

    电源管理芯片8个引脚说明

  • 变频空调电源管理芯片STR-A6069H资料

    封装名:DIP8 (无第6引脚)STR-A6000 系列是将功率 MOSFET 和电流模式 PWM 控制器IC置于同一封装中的 PWM 型开关电源控制芯片.为了实现低功耗及低待机功耗,内置启动电路和待 ...