Wakelocks 框架是基于Wakeup Source实现的为Android系统上层提供投票机制,以阻止系统进入休眠

1.功能说明

该模块的支持受宏CONFIG_PM_WAKELOCKS控制。在使能该宏的情况下,PM Core初始化过程中会在sysfs下创建两个属性节点:

/sys/power/wake_lock:用户程序可以向其写入一个字符串来创建一个wakelock,该字符创即为wakelock的名字,该wakelock可阻止系统进入低功耗模式

/sys/power/wake_unlock:用户程序向其写入相同的字符串,即可注销该wakelock

配置宏CONFIG_PM_WAKELOCKS_LIMIT可以限制系统所能创建的wakelock的数量。

使能宏CONFIG_PM_WAKELOCKS_GC能打开wakelock的回收机制,使得wakelock在积累一定的数量后再去清除(释放空间),从而不需要在每次释放wakelock时都去清除。

2.主要数据结构和接口

2.1 wakelock结构体

struct wakelock {
char *name; //wakelock名字
struct rb_node node; //红黑树节点,所有wakelock以红黑树的方式组织在该模块里,便于管理
struct wakeup_source *ws; //wakelock对应的ws
#ifdef CONFIG_PM_WAKELOCKS_GC
struct list_head lru; //与wakelock的回收机制有关,见后续介绍
#endif
};

2.2 模块重要变量

@ kernel/power/wakelock.c
static struct rb_root wakelocks_tree = RB_ROOT; //红黑树根节点,所有wakelock都会挂在这上面,便于管理 static LIST_HEAD(wakelocks_lru_list); //该链表用于管理已生成的wakelock,便于回收机制处理,后续称其为回收链表 //当 CONFIG_PM_WAKELOCKS_LIMIT 配置大于0时,保存已存在的wakelock数量,用于限制存在的wakelock数量不超过CONFIG_PM_WAKELOCKS_LIMIT
static unsigned int number_of_wakelocks; //当 CONFIG_PM_WAKELOCKS_GC 配置时,表示启动wakelock回收机制。该变量用于累计已解锁的wakelock的数量,当该变量超过WL_GC_COUNT_MAX(100)时,会触发回收work
static unsigned int wakelocks_gc_count;

2.3 主要接口

2.3.1 pm_wake_lock()接口

该接口是在向/sys/power/wake_lock写入字符串时调用,主要实现:

  • 查找同名wakelock,找不到时创建wakelock,并持(超时)锁
  • 配置CONFIG_PM_WAKELOCKS_LIMIT > 0的情况下,对wakelock数量计数并限制
  • 将该wakelock移到回收链表前端,以防被优先回收
/* call by wake_lock_store()*/
int pm_wake_lock(const char *buf)
{
const char *str = buf;
struct wakelock *wl;
u64 timeout_ns = 0;
size_t len;
int ret = 0; //解析传入的字符串,第一个参数为wakelock名称,第二个参数(可选)则是wakelock超时时间
while (*str && !isspace(*str))
str++; len = str - buf;
if (!len)
return -EINVAL; if (*str && *str != '\n') {
/* Find out if there's a valid timeout string appended. */
ret = kstrtou64(skip_spaces(str), 10, &timeout_ns);
if (ret)
return -EINVAL;
} mutex_lock(&wakelocks_lock);
//查找wakelock,找不到时创建
wl = wakelock_lookup_add(buf, len, true);
if (IS_ERR(wl)) {
ret = PTR_ERR(wl);
goto out;
}
if (timeout_ns) { //如果传入了超时参数,则持锁,超时后会自动释放该锁
u64 timeout_ms = timeout_ns + NSEC_PER_MSEC - 1;
do_div(timeout_ms, NSEC_PER_MSEC);
__pm_wakeup_event(wl->ws, timeout_ms);
} else { //否则直接持锁
__pm_stay_awake(wl->ws);
} wakelocks_lru_most_recent(wl); //将该wakelock移到回收链表前端,使得回收机制触发时靠后处理 out:
mutex_unlock(&wakelocks_lock);
return ret;
}
static struct wakelock *wakelock_lookup_add(const char *name, size_t len,
bool add_if_not_found)
{
struct rb_node **node = &wakelocks_tree.rb_node;
struct rb_node *parent = *node;
struct wakelock *wl; //根据名称在红黑树上查找是否已经存在该wakelock
while (*node) {
int diff; parent = *node;
wl = rb_entry(*node, struct wakelock, node);
diff = strncmp(name, wl->name, len);
if (diff == 0) {
if (wl->name[len])
diff = -1;
else
return wl; //找到同名wakelock,返回
}
if (diff < 0)
node = &(*node)->rb_left;
else
node = &(*node)->rb_right;
}
if (!add_if_not_found)
return ERR_PTR(-EINVAL); //配置CONFIG_PM_WAKELOCKS_LIMIT>0的情况下,会检测已创建的wakelock数量是否已经超过该配置
if (wakelocks_limit_exceeded())
return ERR_PTR(-ENOSPC); /* 未找到同名wakelock的情况下,开始创建wakelock */
wl = kzalloc(sizeof(*wl), GFP_KERNEL);
if (!wl)
return ERR_PTR(-ENOMEM); wl->name = kstrndup(name, len, GFP_KERNEL);
if (!wl->name) {
kfree(wl);
return ERR_PTR(-ENOMEM);
}
//本质wakelock是通过wakeup_source机制实现的
wl->ws = wakeup_source_register(NULL, wl->name);
if (!wl->ws) {
kfree(wl->name);
kfree(wl);
return ERR_PTR(-ENOMEM);
}
wl->ws->last_time = ktime_get();
//将该wakelock挂到红黑树上
rb_link_node(&wl->node, parent, node);
rb_insert_color(&wl->node, &wakelocks_tree);
wakelocks_lru_add(wl); //添加到回收链表
increment_wakelocks_number(); //wakelock数量+1
return wl;
}

2.3.2 pm_wake_unlock() 接口

该接口是在向/sys/power/wake_unlock写入字符串时调用,主要实现:

  • 查找同名wakelock,找不到时返回错误
  • 配置CONFIG_PM_WAKELOCKS_GC开启回收机制的情况下,对wakelock数量计数并在超过上限时触发回收处理work
/* call by wake_unlock_store()*/
int pm_wake_unlock(const char *buf)
{
struct wakelock *wl;
size_t len;
int ret = 0; len = strlen(buf);
if (!len)
return -EINVAL; if (buf[len-1] == '\n')
len--; if (!len)
return -EINVAL; mutex_lock(&wakelocks_lock);
//查找wakelock,找不到时直接返回错误
wl = wakelock_lookup_add(buf, len, false);
if (IS_ERR(wl)) {
ret = PTR_ERR(wl);
goto out;
}
__pm_relax(wl->ws); //释放锁 wakelocks_lru_most_recent(wl); //将该wakelock移到回收链表前端,使得回收机制触发时靠后处理
wakelocks_gc(); //已解锁的wakelock加1,并判断是否超过上限,触发回收处理work out:
mutex_unlock(&wakelocks_lock);
return ret;
}

2.3.3 __wakelocks_gc()回收处理work

该接口在已解锁的wakelock数量超过上限WL_GC_COUNT_MAX(100)时调用,用于处理回收已创建的wakelock,释放空间。

static void __wakelocks_gc(struct work_struct *work)
{
struct wakelock *wl, *aux;
ktime_t now; mutex_lock(&wakelocks_lock); now = ktime_get();
//从回收链表尾部开始倒序遍历(越靠近链表头部的wakelock,越是最近才操作的wakelock)
list_for_each_entry_safe_reverse(wl, aux, &wakelocks_lru_list, lru) {
u64 idle_time_ns;
bool active; spin_lock_irq(&wl->ws->lock);
idle_time_ns = ktime_to_ns(ktime_sub(now, wl->ws->last_time)); //计算该锁有多长时间未被操作过
active = wl->ws->active; //获取锁的激活状态
spin_unlock_irq(&wl->ws->lock); if (idle_time_ns < ((u64)WL_GC_TIME_SEC * NSEC_PER_SEC)) //如果锁空闲时间小于300s,则不再继续回收
break; //如果锁已经失活,则注销该锁,从红黑树中移除,并移除出回收链表,释放空间,wakelock数量-1
if (!active) {
wakeup_source_unregister(wl->ws);
rb_erase(&wl->node, &wakelocks_tree);
list_del(&wl->lru);
kfree(wl->name);
kfree(wl);
decrement_wakelocks_number();
}
}
wakelocks_gc_count = 0; //重置回收锁计数 mutex_unlock(&wakelocks_lock);
}

使能回收机制的好处是:

1.上层频繁操作wakelock时,不用每次unlock时都耗时去释放资源;

2.如果频繁操作的是同一个wakelock,也不用反复创建/释放资源。

3. 工作时序

wakelock的工作时序如下:

1)应用程序在处理数据前不希望系统进入休眠状态,通过向/sys/power/wake_lock写入一个字符串作为wakelock名字,此时pm_wake_lock()被调用

2)在pm_wake_lock()里,会查找是否已存在同名wakelock,已存在则持锁,不存在则创建锁并持锁

3)应用程序在处理完数据后允许系统进入休眠状态时,通过向/sys/power/wake_unlock写入已持锁的wakelock名字,此时pm_wake_unlock()被调用

4)在pm_wake_unlock()里,会查找是否已存在同名wakelock,并释放该锁,同时判断此时是否要触发wakelock的回收机制

5)当wakelock回收链表里的wakelock数量达到上限后,触发wakelock的回收机制,将长时间未使用且已经解锁的wakelock注销,释放资源

关于wakelock的发展变化以及使用,强烈建议拜读:http://www.wowotech.net/pm_subsystem/wakelocks.html

注:此源码分析基于kernel-5.10。

Wakelocks 框架设计与实现的更多相关文章

  1. 前端MVVM框架设计及实现

    最近抽出点时间想弄个dom模块化的模板引擎,不过现在这种都是MVVM自带的,索性就想自己造轮子写一个简单的MVVM框架了 借鉴的自然还是从正美的Avalon开始了,我2013年写过一个关于MVC MV ...

  2. 前端MVVM框架设计及实现(二)

    在前端MVVM框架设计及实现(一)中有一个博友提出一个看法: “html中使用mvvm徒增开发成本” 我想这位朋友要表达的意思应该是HTML定义了大量的语法标记,HTML中放入了太多的逻辑,从而增加了 ...

  3. JavaScript 框架设计

    JavaScript 高级框架设计 在现在,jQuery等框架已经非常完美,以致于常常忽略了JavaScript原生开发,但是这是非常重要的. 所以,我打算写一个简单的框架,两个目的 熟练框架的思想 ...

  4. JavaScript 框架设计(二)

    JavaScript 高级框架设计 (二) 上一篇,JavaScript高级框架设计(一)我们 实现了对tag标签的选择 下来我们实现对id的选择,即id选择器. 我们将上一篇的get命名为getTa ...

  5. JavaScript框架设计(三) push兼容性和选择器上下文

    JavaScript框架设计(三) push兼容性和选择器上下文 博主很久没有更博了. 在上一篇 JavaScript框架设计(二) 中实现了最基本的选择器,getId,getTag和getClass ...

  6. JavaScript框架设计(四) 字符串选择器(选择器模块结束)

    JavaScript框架设计(四) 字符串选择器(选择器模块结束) 经过前面JavaScript框架设计(三) push兼容性和选择器上下文的铺垫,实现了在某一元素下寻找,现在终于进入了字符串选择器 ...

  7. Atitit.加密算法ati Aes的框架设计

    Atitit.加密算法ati Aes的框架设计 版本进化 c:\1t\aesC47.java c:\1t\aes.java 增加了public static byte[] encrypt(byte[] ...

  8. .NET架构设计、框架设计系列文章总结

    从事.NET开发到现在已经有七个年头了.慢慢的可能会很少写.NET文章了.不知不觉竟然走了这么多年,热爱.NET热爱c#.突然想对这一路的经历进行一个总结. 是时候开始下一阶段的旅途,希望这些文章可以 ...

  9. 新书出版《.NET框架设计—模式、配置、工具》感恩回馈社区!

    很高兴我的第一本书由图灵出版社出版.本书总结了我这些年来对框架学习.研究的总结,里面纯干货,无半句废话. 书的详情请看互动网的销售页面:http://product.china-pub.com/377 ...

  10. js框架设计1.1命名空间笔记

    借到了司徒正美的写的js框架设计一书,司徒大神所著有些看不太懂,果然尚需循序渐进,稳扎js基础之中. 第一张开篇司徒阐述了种子模块的概念 种子模块亦为核心模块,框架最先执行模块,司徒见解应包含:对象扩 ...

随机推荐

  1. WPF 使用 ManipulationDemo 工具辅助调试设备触摸失效问题

    本文将和大家介绍我所在的团队开源的 ManipulationDemo 工具.通过 ManipulationDemo 工具可以提升调试设备触摸失效的效率 此工具在 GitHub 上完全开源,请看 htt ...

  2. WinUI 3 修复非打包应用运行提示缺少 Windows App Runtime 环境

    本文将告诉大家如何修复 WinUI 3 非打包的应用,在分发给到客户时,在客户的机器上运行提示缺少 Windows App Runtime 环境 在用户的机器上提示的 Windows App Runt ...

  3. 将Go开发的代码部署到k8s集群

    一.在服务器上编译go程序 1.1 编译go语言程序 # 安装go yum install go -y mkdir /root/test && cd /root/test # 设置代理 ...

  4. CMDB开发(一)

    一.CMDB前戏 # 项目开发流程 1.需求分析 产品经理 开发人员 客户等三方会议 2.架构设计 框架的选择 语言选择 数据库选择 3.分组开发 小组成员各自开发各自的功能(可能也会有交集) 4. ...

  5. SQL 多表关联更新语句

    A表WHERE条件来自B表查询结果集 UPDATE a SET a.is_sync = 0 FROM A表 a LEFT JOIN B表 b ON a.order_id = b.order_id AN ...

  6. JS实现跟随鼠标移动的div,和一串跟随鼠标的div,鼠标移入移出实现图片的颜色淡入淡出

    1.一直跟着鼠标移动的div:原理是div的left和top值有oEvent.clientX+scrollLeft鼠标指针向对于浏览器页面(或客户区)的水平坐标+元素中滚动条的水平偏移 <!DO ...

  7. 荒岛野人Savage

    题目描述 样例 3 1 3 4 2 7 3 3 2 1 6 分析 首先,我们先设4个变量,初始坐标 \(d[i]\),每年步数 \(p[i]\),寿命 \(l[i]\),根据题目很容易得到一个不等式 ...

  8. installshield 安装jdk并配置环境变量

    今天来通过installshield安装jdk以及配置环境变量,本质上是调用第三方安装程序. 首先将jdk的安装文件添加到我们的安装程序中 然后编写我们的脚本 选择BEHAVIOR AND LOGIC ...

  9. 九、.net core(.NET 6)添加通用的Redis功能

     .net core 编写通用的Redis功能 在 Package项目里面,添加包:StackExchange.Redis: 在Common工具文件夹下,新建 Wsk.Core.Redis类库项目,并 ...

  10. Axure和墨刀——两款原型设计工具介绍

    Axure与墨刀是两款在原型设计领域广受欢迎的工具,各具特点和优势: Axure: Axure RP是一款功能强大的原型设计工具,广泛应用于交互设计和用户体验设计领域.它提供了丰富的交互元素库.高保真 ...