赞
踩
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上(kernel/power/wakelock.c,较新的kernel都支持)。
虽说不翻旧黄历了,还是要提一下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
对比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开发到用户空间访问。
相比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。
从字面意思上,新版的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中实现,如下:
#ifdef CONFIG_PM_WAKELOCKS static ssize_t wake_lock_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return pm_show_wakelocks(buf, true); } static ssize_t wake_lock_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { int error = pm_wake_lock(buf); return error ? error : n; } power_attr(wake_lock); static ssize_t wake_unlock_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf) { return pm_show_wakelocks(buf, false); } static ssize_t wake_unlock_store(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t n) { int error = pm_wake_unlock(buf); return error ? error : n; } power_attr(wake_unlock); #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中实现。
pm_wake_lock位于kernel\power\wakelock.c中,用于上报一个wakeup event(从另一个角度,就是阻止系统suspend),代码如下:
int pm_wake_lock(const char *buf) { const char *str = buf; struct wakelock *wl; u64 timeout_ns = 0; size_t len; int ret = 0; // 检查当前进程是否具有CAP_BLOCK_SUSPEND权限,如果没有则返回EPERM错误 if (!capable(CAP_BLOCK_SUSPEND)) return -EPERM; // 查找buf中的第一个空格或者换行符 while (*str && !isspace(*str)) str++; // 计算buf的长度 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; } // 加锁以保证对wakelocks数据结构的访问是互斥的 mutex_lock(&wakelocks_lock); // 在wakelocks数据结构中查找或添加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); } // 更新最近使用的wakelock的LRU链表位置 wakelocks_lru_most_recent(wl); out: // 解锁wakelocks数据结构 mutex_unlock(&wakelocks_lock); return ret; }
wakelock_lookup_add是内部接口,代码如下:
/** * wakelock_lookup_add - 查找或添加wakelock * @name: wakelock的名称 * @len: wakelock名称的长度 * @add_if_not_found: 如果找不到是否添加新的wakelock * * 该函数用于查找已存在的wakelock,如果找不到且add_if_not_found为true,则会添加一个新的wakelock。 * 这里使用红黑树来维护wakelock的有序集合,根据名称进行查找和插入操作。 * * 返回值: * - 成功查找到现有的wakelock时,返回指向该wakelock的指针。 * - 找不到现有wakelock并且add_if_not_found为true时,返回新添加的wakelock指针。 * - 发生错误(如内存不足)时,返回相应的错误指针。 */ 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) { // 名称匹配,找到现有的wakelock if (wl->name[len]) diff = -1; else return wl; } if (diff < 0) node = &(*node)->rb_left; else node = &(*node)->rb_right; } // 找不到现有的wakelock,如果需要添加新的wakelock if (!add_if_not_found) return ERR_PTR(-EINVAL); if (wakelocks_limit_exceeded()) return ERR_PTR(-ENOSPC); /* Not found, we have to add a new one. */ // 分配新的wakelock节点 wl = kzalloc(sizeof(*wl), GFP_KERNEL); if (!wl) return ERR_PTR(-ENOMEM); // 复制wakelock的名称 wl->name = kstrndup(name, len, GFP_KERNEL); if (!wl->name) { kfree(wl); return ERR_PTR(-ENOMEM); } // 注册wakeup_source wl->ws = wakeup_source_register(NULL, wl->name); if (!wl->ws) { kfree(wl->name); kfree(wl); return ERR_PTR(-ENOMEM); } // 记录wakelock的最后使用时间 wl->ws->last_time = ktime_get(); // 将新的wakelock节点插入红黑树 rb_link_node(&wl->node, parent, node); rb_insert_color(&wl->node, &wakelocks_tree); // 将新的wakelock添加到LRU链表 wakelocks_lru_add(wl); // 增加wakelock的数量计数 increment_wakelocks_number(); // 返回新添加的wakelock节点指针 return wl; }
该函数维护一个红黑树来保存wakelock,根据名称进行查找和添加操作。如果找到现有的wakelock,则返回该wakelock的指针;如果找不到现有的wakelock且add_if_not_found为true,则会分配并添加新的wakelock,并返回新添加的wakelock的指针。在添加新的wakelock时,会进行一系列的初始化操作,包括注册wakeup_source,并记录wakelock的最后使用时间。此外,每次添加新的wakelock时,都会将其添加到LRU链表,用于管理最近使用的wakelock。
/** * wakeup_source_register - 创建wakeup_source并将其添加到列表中 * @dev: 与该wakeup_source关联的设备(如果为虚拟设备则为NULL) * @name: 要注册的wakeup_source的名称 * * 该函数用于创建wakeup_source并将其添加到wakeup_source列表中。wakeup_source是Linux内核中用于追踪系统唤醒源的机制, * 它允许标识哪些设备或驱动程序可能导致系统从低功耗状态中唤醒。通过追踪唤醒源,内核可以更有效地管理系统的 * 低功耗模式和唤醒过程。 * * 参数: * @dev: 与wakeup_source关联的设备,如果是虚拟设备则为NULL。 * @name: 要注册的wakeup_source的名称。 * * 返回值: * - 成功创建wakeup_source并添加到列表时,返回指向该wakeup_source的指针。 * - 创建或添加失败时,返回NULL。 */ struct wakeup_source *wakeup_source_register(struct device *dev, const char *name) { struct wakeup_source *ws; int ret; // 创建一个新的wakeup_source ws = wakeup_source_create(name); if (ws) { // 如果设备已经注册,将wakeup_source添加到sysfs中 if (!dev || device_is_registered(dev)) { ret = wakeup_source_sysfs_add(dev, ws); if (ret) { // 添加失败,释放wakeup_source并返回NULL wakeup_source_free(ws); return NULL; } } // 将wakeup_source添加到wakeup_source链表中 wakeup_source_add(ws); } // 返回wakeup_source指针 return ws; } EXPORT_SYMBOL_GPL(wakeup_source_register);
该函数用于创建wakeup_source并将其添加到wakeup_source列表中。wakeup_source是Linux内核中用于追踪系统唤醒源的机制,它允许标识哪些设备或驱动程序可能导致系统从低功耗状态中唤醒。通过追踪唤醒源,内核可以更有效地管理系统的低功耗模式和唤醒过程。
函数首先调用wakeup_source_create(name)创建一个新的wakeup_source,如果创建成功,则根据传入的设备指针dev判断是否将wakeup_source添加到sysfs中。如果dev为NULL或者设备已经注册,则通过调用wakeup_source_sysfs_add(dev, ws)将wakeup_source添加到sysfs中。最后,通过调用wakeup_source_add(ws)将wakeup_source添加到wakeup_source链表中。
返回值为指向创建的wakeup_source的指针,如果创建或添加失败,则返回NULL。
再看一下struct wakelock结构:
struct wakelock {
char *name; // 唤醒锁的名称
struct rb_node node; // 用于将唤醒锁添加到红黑树中
struct wakeup_source *ws; // 指向唤醒源结构体的指针
#ifdef CONFIG_PM_WAKELOCKS_GC
struct list_head lru; // 用于将唤醒锁添加到链表中(可选,依赖于配置选项)
#endif
};
/sys/class/wakeup目录下能查到注册到kernel中的wakelock
pm_wake_unlock和pm_wake_lock类似,如下:
/** * pm_wake_unlock - 解锁并允许系统进入睡眠状态 * @buf: 要解锁的唤醒锁的名称 * * 该函数用于解锁之前由调用者使用"pm_wake_lock"函数获取的唤醒锁。唤醒锁是系统用于阻止设备进入睡眠状态的机制,用于保证在某些关键任务或进程运行时设备保持唤醒状态。 * * 参数: * - @buf: 要解锁的唤醒锁的名称。该名称对应的唤醒锁将被解锁,如果没有其他活动的唤醒锁,则允许系统进入睡眠状态。 * * 返回值: * - 返回0表示成功。失败时返回以下错误代码之一: * - -EPERM:调用者没有解锁唤醒锁的必要权限(CAP_BLOCK_SUSPEND)。 * - -EINVAL:参数无效,如空名称或以换行符结尾的名称。 * - 其他错误代码:如果添加唤醒锁时出现错误,则返回"wakelock_lookup_add"函数的错误代码。 * * 用例示例: * - pm_wake_unlock("example_wakelock"); */ int pm_wake_unlock(const char *buf) { struct wakelock *wl; size_t len; int ret = 0; // 检查调用者是否具有解锁唤醒锁的权限(CAP_BLOCK_SUSPEND)。 if (!capable(CAP_BLOCK_SUSPEND)) return -EPERM; // 计算唤醒锁名称的长度,并检查其是否是有效的非空字符串。如果名称为空或仅包含换行符,则返回错误代码(-EINVAL)。 len = strlen(buf); if (!len) return -EINVAL; // 如果唤醒锁名称以换行符结尾,则去除换行符以确保正确处理字符串。 if (buf[len-1] == '\n') len--; // 如果去除换行符后唤醒锁名称为空,则返回错误代码(-EINVAL)。 if (!len) return -EINVAL; // 获取wakelocks_lock互斥锁,以避免对唤醒锁列表的并发访问。 mutex_lock(&wakelocks_lock); // 在唤醒锁列表中查找或添加具有指定名称的唤醒锁。 wl = wakelock_lookup_add(buf, len, false); if (IS_ERR(wl)) { ret = PTR_ERR(wl); goto out; } // 解锁唤醒锁,如果没有其他活动的唤醒锁,则允许系统进入睡眠状态。 __pm_relax(wl->ws); // 将解锁的唤醒锁移动到LRU(Least Recently Used)列表中的最近位置,以优先进行垃圾回收。 wakelocks_lru_most_recent(wl); // 执行垃圾回收(wakelocks_gc),从列表中删除任何过期或不必要的唤醒锁。 wakelocks_gc(); out: // 释放wakelocks_lock互斥锁。 mutex_unlock(&wakelocks_lock); // 返回操作结果(成功或错误代码)。 return ret; }
pm_wake_unlock函数用于解锁之前由调用者使用pm_wake_lock函数获取的唤醒锁。唤醒锁是系统用于阻止设备进入睡眠状态的机制,用于保证在某些关键任务或进程运行时设备保持唤醒状态。该函数接收唤醒锁的名称作为参数,并解锁它,如果没有其他活动的唤醒锁,则允许系统进入睡眠状态。
/** * pm_show_wakelocks - 显示唤醒锁列表 * @buf: 存储唤醒锁列表信息的缓冲区 * @show_active: 是否只显示活动的唤醒锁(true表示只显示活动的唤醒锁,false表示显示所有唤醒锁) * * 该函数用于在指定缓冲区中显示唤醒锁列表的信息。唤醒锁是系统用于阻止设备进入睡眠状态的机制,用于保证在某些关键任务或进程运行时设备保持唤醒状态。 * * 参数: * - @buf: 存储唤醒锁列表信息的缓冲区。该缓冲区将用于输出唤醒锁列表的名称。 * - @show_active: 是否只显示活动的唤醒锁。如果该参数为true,则只显示活动的唤醒锁;如果为false,则显示所有唤醒锁。 * * 返回值: * - 返回成功输出的唤醒锁列表的长度(以字节为单位)。如果参数@buf为NULL或其他错误情况,则返回负值。 * * 用例示例: * - char buf[256]; * - ssize_t len = pm_show_wakelocks(buf, true); * - if (len >= 0) { * - // 处理唤醒锁列表的信息(存储在buf中) * - } else { * - // 处理错误情况 * - } */ ssize_t pm_show_wakelocks(char *buf, bool show_active) { struct rb_node *node; struct wakelock *wl; int len = 0; // 获取wakelocks_lock互斥锁,以避免对唤醒锁列表的并发访问。 mutex_lock(&wakelocks_lock); // 遍历唤醒锁列表,显示唤醒锁名称到指定缓冲区中。 for (node = rb_first(&wakelocks_tree); node; node = rb_next(node)) { wl = rb_entry(node, struct wakelock, node); // 只显示活动的唤醒锁,如果唤醒锁是活动的(show_active为true),则输出唤醒锁名称到缓冲区。 // 如果show_active为false,则显示所有唤醒锁。 if (wl->ws->active == show_active) len += sysfs_emit_at(buf, len, "%s ", wl->name); } // 在缓冲区末尾添加换行符,以表示唤醒锁列表的结束。 len += sysfs_emit_at(buf, len, "\n"); // 释放wakelocks_lock互斥锁。 mutex_unlock(&wakelocks_lock); // 返回成功输出的唤醒锁列表的长度(以字节为单位)。 return len; }
pm_show_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。
(Android GKI中CONFIG_PM_WAKELOCKS_GC没有使能)
参考文章:http://www.wowotech.net/pm_subsystem/wakelocks.html
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。