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 */

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: }
  1. 输入参数为一个字符串,如"wake_lock_test”,该字符串指定一个wakelock name。
  2. 调用capable,检查当前进程是否具备阻止系统suspend的权限。
  3. 解析字符串
  4. 调用wakelock_lookup_add接口,查找是否有相同name的wakelock。如果有,直接返回wakelock的指针;如果没有,退出。
  5. 调用__pm_relax接口,deactive wakelock对应的wakeup source。
  6. 调用wakelocks_lru_most_recent接口,将盖wakelock移到wakelocks_lru_list链表的前端(表示它是最近一个被访问到的,和GC有关,后面重点描述)。
  7. 调用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;
  1. 在wakelock结构中,嵌入一个list head(lru),用于挂入wakelocks_lru_list。可参考3.4小节的描述。

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

Linux电源管理(9)_wakelocks【转】的更多相关文章

  1. Linux电源管理_autosleep--(五)【转】

    本文转载自:https://blog.csdn.net/wlsfling/article/details/46005409 1. 前言 Autosleep也是从Android wakelocks补丁集 ...

  2. linux电源管理系列(一)

    本系列将逐步介绍linux电源管理相关的知识,涉及到常见电源管理机制.linux电源管理机制.linux驱动中有关电源管理的相关接口.内核文档中关于Linux电源管理架构文档的分析.以下将以此来介绍相 ...

  3. Linux电源管理(7)_Wakeup events framework【转】

    转自:http://www.wowotech.net/pm_subsystem/wakeup_events_framework.html 1.  前言 本文继续“Linux电源管理(6)_Generi ...

  4. ARM linux电源管理——Cortex A系列CPU(32位)睡眠和唤醒的底层汇编实现

    ARM linux电源管理——Cortex A系列CPU(32位)睡眠和唤醒的底层汇编实现 承接 http://www.wowotech.net/pm_subsystem/suspend_and_re ...

  5. Linux电源管理(2)-Generic PM基本概念和软件架构【转】

    本文转载自:http://www.wowotech.net/pm_subsystem/generic_pm_architecture.html 1. 前言 这里的Generic PM,是蜗蜗自己起的名 ...

  6. Linux电源管理(1)-整体架构【转】

    本文转载自:http://www.wowotech.net/pm_subsystem/pm_architecture.html 1. 前言 在这个世界中,任何系统的运转都需要能量.如树木依靠光能生长, ...

  7. Linux电源管理(5)_Hibernate和Sleep功能介绍【转】

    本文转载自:http://www.wowotech.net/pm_subsystem/std_str_func.html 1. 前言 Hibernate和Sleep两个功能是Linux Generic ...

  8. Linux电源管理(4)-Power Manager Interface【转】

    本文转载自:http://www.wowotech.net/pm_subsystem/pm_interface.html 1. 前言 Linux电源管理中,相当多的部分是在处理Hibernate.Su ...

  9. Linux电源管理(7)_Wakeup events framework

    1. 前言 本文继续"Linux电源管理(6)_Generic PM之Suspend功能"中有关suspend同步以及PM wakeup的话题.这个话题,是近几年Linux ker ...

随机推荐

  1. Linux 下源码编译FFMEG

    目录 1. 安装依赖关系 2. 源码安装 yasm 3. 源码安装 NASM 4. 源码安装libx264 5. 源码安装x265 6. 源码安装 libmp3lame 7. 源码安装 libopus ...

  2. Python档案袋(线程 )

    Python的进程和线程是使用的操作系统的原生线程和进程,其是去调用操作系统的相应接口实现 进程:之间不可直接共享数据,是资源的集合,进程必须有一个线程 线程:基于进程,之间可直接共享数据,可执行,只 ...

  3. 插入与归并(python)(原创)

    根据维基百科的定义: 插入排序是迭代算法,逐一获得输入数据,逐步产生有序的输出序列.每步迭代中,算法从输入序列中取出一元素,将之插入有序序列中正确的位置.如此迭代直到全部元素有序. 归并排序进行如下迭 ...

  4. android 缩放平移自定义View 显示图片

    1.背景 现在app中,图片预览功能肯定是少不了的,用户基本已经形成条件反射,看到小图,点击看大图,看到大图两个手指开始进行放大,放大后,开始移动到指定部位~~~ 我相信看图的整个步骤,大家或者说用户 ...

  5. java多线程(4)---volatile关键字

    volatile关键字 一旦一个共享变量(类的成员变量.类的静态成员变量)被volatile修饰之后,那么就具备了两层语义: 1)保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的 ...

  6. knockoutjs 上自己实现的flux

    在knockoutjs 上实现 Flux 单向数据流 状态机,主要解决多个组件之间对数据的耦合问题. 一.其实简单 flux的设计理念和实现方案,很大程度上人借鉴和参考了Vuex的实现,只是简化了某些 ...

  7. AspNetCore Mvc 使用 PartialView

    控制器: public IActionResult queryMongoDb(string dbname) { List<MongoDbModel> mdList = new List&l ...

  8. asp.net mvc学习(Vs技巧与Httpcontext)

    模型绑定分析 博客模拟的表单已经可以包含网站开发过程中遇到的大部分的表单格式了,包含一些数组.对象等等. 1.直接拼接字符串 $.ajax({ url: "/XXX", type: ...

  9. linux安装RabbitMQ

    一.RabbitMQ概念RabbitMQ是流行的开源消息队列系统,是AMQP(Advanced Message Queuing Protocol高级消息队列协议)的标准实现,用erlang语言开发.R ...

  10. asp.net core系列 39 Web 应用Razor 介绍与详细示例

    一. Razor介绍 在使用ASP.NET Core Web开发时, ASP.NET Core MVC 提供了一个新特性Razor. 这样开发Web包括了MVC框架和Razor框架.对于Razor来说 ...