Wakelocks 框架设计与实现
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 框架设计与实现的更多相关文章
- 前端MVVM框架设计及实现
最近抽出点时间想弄个dom模块化的模板引擎,不过现在这种都是MVVM自带的,索性就想自己造轮子写一个简单的MVVM框架了 借鉴的自然还是从正美的Avalon开始了,我2013年写过一个关于MVC MV ...
- 前端MVVM框架设计及实现(二)
在前端MVVM框架设计及实现(一)中有一个博友提出一个看法: “html中使用mvvm徒增开发成本” 我想这位朋友要表达的意思应该是HTML定义了大量的语法标记,HTML中放入了太多的逻辑,从而增加了 ...
- JavaScript 框架设计
JavaScript 高级框架设计 在现在,jQuery等框架已经非常完美,以致于常常忽略了JavaScript原生开发,但是这是非常重要的. 所以,我打算写一个简单的框架,两个目的 熟练框架的思想 ...
- JavaScript 框架设计(二)
JavaScript 高级框架设计 (二) 上一篇,JavaScript高级框架设计(一)我们 实现了对tag标签的选择 下来我们实现对id的选择,即id选择器. 我们将上一篇的get命名为getTa ...
- JavaScript框架设计(三) push兼容性和选择器上下文
JavaScript框架设计(三) push兼容性和选择器上下文 博主很久没有更博了. 在上一篇 JavaScript框架设计(二) 中实现了最基本的选择器,getId,getTag和getClass ...
- JavaScript框架设计(四) 字符串选择器(选择器模块结束)
JavaScript框架设计(四) 字符串选择器(选择器模块结束) 经过前面JavaScript框架设计(三) push兼容性和选择器上下文的铺垫,实现了在某一元素下寻找,现在终于进入了字符串选择器 ...
- Atitit.加密算法ati Aes的框架设计
Atitit.加密算法ati Aes的框架设计 版本进化 c:\1t\aesC47.java c:\1t\aes.java 增加了public static byte[] encrypt(byte[] ...
- .NET架构设计、框架设计系列文章总结
从事.NET开发到现在已经有七个年头了.慢慢的可能会很少写.NET文章了.不知不觉竟然走了这么多年,热爱.NET热爱c#.突然想对这一路的经历进行一个总结. 是时候开始下一阶段的旅途,希望这些文章可以 ...
- 新书出版《.NET框架设计—模式、配置、工具》感恩回馈社区!
很高兴我的第一本书由图灵出版社出版.本书总结了我这些年来对框架学习.研究的总结,里面纯干货,无半句废话. 书的详情请看互动网的销售页面:http://product.china-pub.com/377 ...
- js框架设计1.1命名空间笔记
借到了司徒正美的写的js框架设计一书,司徒大神所著有些看不太懂,果然尚需循序渐进,稳扎js基础之中. 第一张开篇司徒阐述了种子模块的概念 种子模块亦为核心模块,框架最先执行模块,司徒见解应包含:对象扩 ...
随机推荐
- [Go] panic: assignment to entry in nil map
以上错误出现在给 map 变量赋值的时候. 例如: type AbMap map[string]string var abMap AbMap abMap['a'] = 'b' 使用 map 变量需要使 ...
- dotnet 警惕 Assembly.Location 返回空
在大部分情况下,获取当前所运行的应用程序的所在路径时,常用的就是 Assembly.Location 属性,按照之前的经验,使用 Assembly.Location 是最为稳定的做法,然而在 dotn ...
- Java中的读写锁ReentrantReadWriteLock详解,存在一个小缺陷
写在开头 最近是和java.util.concurrent.locks包下的同步类干上了,素有 并发根基 之称的concurrent包中全是精品,今天我们继续哈,今天学习的主题要由一个大厂常问的Jav ...
- R_回归模型实例一
工作和生活中存在大量的具有相关性的事件,当找到不同变量之间的关系,我们就会用到回归分析.回归分析(Regression Analysis):是用来确定2个或2个以上变量间关系的一种统计分析方法. 在回 ...
- Go语言连接Redis之go-redis使用指南
参考下面的连接: https://mp.weixin.qq.com/s?__biz=MzU5MjAxMDc1Ng==&mid=2247483899&idx=1&sn=b103c ...
- 报错:Client does not support authentication protocol requested by server; consider upgrading MySQL cli
IDEA启动项目登录时显示用户或密码错误 或者 连接mysql数据库时报错 原因: mysql8 之前的版本中加密规则是mysql_native_password,而在mysql8之后,加密规则是ca ...
- geojson介绍和常用转换编辑工具
GeoJSON是一种基于JSON的地理空间数据交换格式,它定义了几种类型JSON对象以及它们组合在一起的方法,以表示有关地理要素.属性和它们的空间范围的数据. 2015年,互联网工程任务组(IETF) ...
- Java 工程文件的 .gitignore
以下是一个排查 Java 工程文件的 .gitignore 文件示例: # Java 编译器生成的文件 *.class # Maven 生成的文件夹 target/ # Eclipse 生成的文件夹 ...
- 网络安全—PGP8.1软件应用
文章目录 安装PGP8 了解 工作原理 PGP使用 准备工作 加密与解密 加密者视角(发送方) 接收者视角(接收方) 签名 签名方(发送方) 验证签名方(接收方) 补充 加密签名一段文字 签名后的格式 ...
- linux下使用fdisk进行磁盘分区详解
目录 一.前言 二.关于磁盘分区的结构 三.fdisk命令详解 四.使用fdisk进行磁盘分区 4.1 磁盘分区规划 4.2 fdisk进行磁盘分区 4.3 格式化分区 4.4 创建挂载点/挂载目录 ...