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。

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

原文地址:https://www.cnblogs.com/linhaostudy/p/10320226.html

时间: 2024-10-01 07:03:33

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

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

ARM linux电源管理——Cortex A系列CPU(32位)睡眠和唤醒的底层汇编实现 承接 http://www.wowotech.net/pm_subsystem/suspend_and_resume.html Linux电源管理(6)_Generic PM之Suspend功能一文中的下图. 本文主要分析平台相关的CPU睡眠和唤醒,即下电和上电流程,以及ARM底层汇编代码实现. 内核版本:3.1.0               CPU:ARM Cortex-A7 1 平台相关函数执行流程

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

本文转载自:http://www.wowotech.net/pm_subsystem/std_str_func.html 1. 前言 Hibernate和Sleep两个功能是Linux Generic PM的核心功能,它们的目的是类似的:暂停使用——>保存上下文——>关闭系统以节电········>恢复系统——>恢复上下文——>继续使用. 本文以内核向用户空间提供的接口为突破口,从整体上对这两个功能进行介绍,并会在后续的文章中,分析它们的实现逻辑和执行动作. 顺便感概一下,虽

Linux电源管理系统架构和驱动(1)-Linux电源管理全局架构

1.   Linux电源管理全局架构 Linux电源管理非常复杂,牵扯到系统级的待机.频率电压变换.系统空闲时的处理以及每个设备驱动对于系统待机的支持和每个设备的运行时电源管理,可以说和系统中的每个设备驱动都息息相关. 对于消费电子产品来说,电源管理相当重要.因此,这部分工作往往在开发周期中占据相当大的比重,图1呈现了Linux内核电源管理的整体架构.大体可以归纳为如下几类: 1.      CPU在运行时根据系统负载进行动态电压和频率变换的CPUFreq 2.      CPU在系统空闲时根据

Linux电源管理(1)_整体架构(转自蜗窝科技,www.wowotech.net)

Linux电源管理(1)_整体架构(转自蜗窝科技,www.wowotech.net) 1. 前言 在这个世界中,任何系统的运转都需要能量.如树木依靠光能生长,如马儿依靠食物奔跑,如计算机系统依靠电能运行.而 能量的获取是有成本的,因此如果能在保证系统运转的基础上,尽量节省对能量的消耗,就会大大提升该系统的生存竞争力.这方面,大自然已经做的很好了,如植 物的落叶,如动物的冬眠,等等.而在计算机的世界里(这里以运行Linux OS的嵌入式系统为例),称作电源管理(Power Management).

Linux电源管理(2)_Generic PM之基本概念和软件架构(蜗窝科技,www.wowotech.net)

1. 前言 这里的Generic PM,是蜗蜗自己起的名字,指Linux系统中那些常规的电源管理手段,包括关机(Power off).待机(Standby or Hibernate).重启(Reboot)等.这些手段是在嵌入式Linux普及之前的PC或者服务器时代使用的.在那个计算机科学的蛮荒时代,人类在 摩尔定律的刺激下,孜孜追求的是计算机的计算能力.处理性能,因此并不特别关心Power消耗. 在这种背景下发展出来的Linux电源管理机制,都是粗放的.静态的.被动的,具体请参考下面的介绍. 2

Linux电源管理(3)_Generic PM之Reboot过程(转自蜗窝科技,www.wowotech.net)

Linux电源管理(3)_Generic PM之Reboot过程 1. 前言 在使用计算机的过程中,关机和重启是最先学会的两个操作.同样,这两个操作在Linux中也存在,称作shutdown和restart.这就是本文要描述的对象. 在Linux Kernel中,主流的shutdown和restart都是通过“reboot”系统调用(具体可参考kernel/sys.c)来实现的,这也是本文 使用“Generic PM之Reboot过程”作为标题的原因.另外,除了我们常用的shutdown和res

linux电源管理系列(一)

本系列将逐步介绍linux电源管理相关的知识,涉及到常见电源管理机制.linux电源管理机制.linux驱动中有关电源管理的相关接口.内核文档中关于Linux电源管理架构文档的分析.以下将以此来介绍相关内容,尽量做到通俗易懂,条理清晰. 电是现在社会的基础设施,它点亮了整个世界.随着移动互联网的盛行,各种智能设备层出不穷,各种CPU和大屏幕,都在不断折磨手机的电池,各种刺激的手游,也在压榨智能手机的电量.电池技术发展了这么多年,在没有新型储能材料发现之前,考虑到整体的重量和发热的可接受度,手机电

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

转自:http://www.wowotech.net/pm_subsystem/wakeup_events_framework.html 1.  前言 本文继续“Linux电源管理(6)_Generic PM之Suspend功能”中有关suspend同步以及PM wakeup的话题.这个话题,是近几年Linux kernel最具争议的话题之一,在国外Linux开发论坛,经常可以看到围绕该话题的辩论.辩论的时间跨度和空间跨度可以持续很长,且无法达成一致. wakeup events framewo

Linux电源管理【转】

转自:http://www.cnblogs.com/sky-zhang/archive/2012/06/05/2536807.html PM notifier机制: 应用场景: There are some operations that subsystems or drivers may want to carry out before hibernation/suspend or after restore/resume, but they require the system to be