Linux电源管理 - wakelocks

目录

简介:

一、wakelocks

[1、Kernel wakelocks在电源管理中的位置](#1、Kernel wakelocks在电源管理中的位置)

[二、wakelocks 内核源码分析](#二、wakelocks 内核源码分析)

[1、创建 /sys/power/wake_lock 和 /sys/power/wake_unlock](#1、创建 /sys/power/wake_lock 和 /sys/power/wake_unlock)

[2、pm_wake_lock() 接口](#2、pm_wake_lock() 接口)

[3、pm_wake_unlock() 接口](#3、pm_wake_unlock() 接口)

4、__wakelocks_gc()回收处理work

三、工作时序


简介:


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

该模块的支持受宏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时都去清除。

一、wakelocks


wakelocks提供的功能包括:

1)/sys/power/wake_lock:用户程序向文件写入一个字符串,即可创建一个wakelock,该字符串就是wakelock的名字。该wakelock可以阻止系统进入低功耗模式

2)/sys/power/wake_unlock:用户程序向文件写入相同的字符串,即可注销一个wakelock

3)当系统中所有的wakelock都注销后,系统可以自动进入低功耗状态:由autosleep实现

4)向内核其它driver也提供了wakelock的创建和注销接口,允许driver创建wakelock以阻止睡眠、注销wakelock以允许睡眠。并允许用户空间访问

有关Android wakelocks更为详细的描述,可以参考:http://elinux.org/Android_Power_Management

1、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。

二、wakelocks 内核源码分析


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

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

1、创建 /sys/power/wake_lock 和 /sys/power/wake_unlock

pm_init() 调用 kobject_create_and_add 和 sysfs_create_groups 函数,创建一系列的属性文件,其中 wake_lock_attr、wake_unlock_attr 分别会生成 /sys/power/wake_lock 和 /sys/power/wake_unlock

cpp 复制代码
/* kernel/power/main.c */
static struct attribute * g[] = {
    &state_attr.attr,
#ifdef CONFIG_PM_TRACE
    &pm_trace_attr.attr,
    &pm_trace_dev_match_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP
    &pm_async_attr.attr,
    &wakeup_count_attr.attr,
#ifdef CONFIG_SUSPEND
    &mem_sleep_attr.attr,
    &sync_on_suspend_attr.attr,
#endif
#ifdef CONFIG_PM_AUTOSLEEP
    &autosleep_attr.attr,
#endif
#ifdef CONFIG_PM_WAKELOCKS
    &wake_lock_attr.attr,            //wake_lock
    &wake_unlock_attr.attr,          //wake_unlock
#endif
#ifdef CONFIG_PM_SLEEP_DEBUG
    &pm_test_attr.attr,
    &pm_print_times_attr.attr,
    &pm_wakeup_irq_attr.attr,
    &pm_debug_messages_attr.attr,
#endif
#endif
#ifdef CONFIG_FREEZER
    &pm_freeze_timeout_attr.attr,
#endif
    NULL,
};

static const struct attribute_group attr_group = {
    .attrs = g,
};

static const struct attribute_group *attr_groups[] = {
    &attr_group,
#ifdef CONFIG_PM_SLEEP
    &suspend_attr_group,
#endif
    NULL,
};

struct workqueue_struct *pm_wq;
EXPORT_SYMBOL_GPL(pm_wq);

static int __init pm_start_workqueue(void)
{
    pm_wq = alloc_workqueue("pm", WQ_FREEZABLE, 0);

    return pm_wq ? 0 : -ENOMEM;
}

static int __init pm_init(void)
{
    int error = pm_start_workqueue();
    if (error)
        return error;
    hibernate_image_size_init();
    hibernate_reserved_size_init();
    pm_states_init();
    power_kobj = kobject_create_and_add("power", NULL);
    if (!power_kobj)
        return -ENOMEM;
    error = sysfs_create_groups(power_kobj, attr_groups);
    if (error)
        return error;
    pm_print_times_init();
    return pm_autosleep_init();
}

core_initcall(pm_init);

wake_lock_attr 和 wake_unlock_attr 的 .show、.store 定义如下

cpp 复制代码
#define power_attr(_name) \
static struct kobj_attribute _name##_attr = {   \
    .attr   = {             \
        .name = __stringify(_name), \
        .mode = 0644,           \
    },                  \
    .show   = _name##_show,         \
    .store  = _name##_store,        \
}

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

用户空间往/sys/power/wake_lock写数据最终调用 pm_wake_lock

用户空间往/sys/power/wake_unlock写数据最终调用 pm_wake_unlock

2、pm_wake_lock() 接口

/sys/power/wake_lock 写如字符串时调用 pm_wake_lock()
pm_wake_lock() 主要实现功能:

  • 查找同名 wakelock,找不到时创建 wakelock,并持(超时)锁
  • 配置 CONFIG_PM_WAKELOCKS_LIMIT > 0 的情况下,对wakelock数量计数并限制
  • 将该wakelock移到回收链表前端,以防被优先回收
cpp 复制代码
/* kernel/power/wakelock.c*/
int pm_wake_lock(const char *buf)
{
    const char *str = buf;
    struct wakelock *wl;
    u64 timeout_ns = 0;
    size_t len;
    int ret = 0;

    if (!capable(CAP_BLOCK_SUSPEND))
        return -EPERM;
    //解析传入的字符串,第一个参数为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;
}

wakelock_lookup_add() 查找wakelock,找不到时创建

cpp 复制代码
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
    /* Not found, we have to add a new one. */
    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;
}

3、pm_wake_unlock() 接口

/sys/power/wake_unlock 写入字符串时调用 pm_wake_unlock()

  • 查找同名wakelock,找不到时返回错误
  • 释放锁
  • 配置 CONFIG_PM_WAKELOCKS_GC 开启回收机制的情况下,对wakelock数量计数并在超过上限时触发回收处理work
cpp 复制代码
/* kernel/power/wakelock.c */
int pm_wake_unlock(const char *buf)
{
    struct wakelock *wl;
    size_t len;
    int ret = 0;

    if (!capable(CAP_BLOCK_SUSPEND))
        return -EPERM;

    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;
}

4、__wakelocks_gc()回收处理work

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

cpp 复制代码
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);
}

回收机制的好处:

  • 上层频繁操作wakelock时,不用每次unlock时都耗时去释放资源;
  • 如果频繁操作的是同一个wakelock,也不用反复创建/释放资源。

三、工作时序


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注销,释放资源

把 /sys/power/wake_lock 和 /sys/power/wake_unlock 当成普通文件读写即可,参考代码如下:

cpp 复制代码
static void do_wake_lock(char *module)
{
    char wake_lock_path[] = "/sys/power/wake_lock";
    int wake_lock_fd = -1;
    int ret = -1;

    wake_lock_fd= open(wake_lock_path, O_WRONLY | O_APPEND);
    if (wake_lock_fd < 0) {
        printf("%s Failed to open wakelock file - %s", __func__, strerror(errno));
    }

    ret = write(wake_lock_fd, module, strlen(module));
    if (ret != (int)strlen(module)) {
        printf("%s write to wakelock file failed %d - %s", __func__, ret, strerror(errno));
        
    }
    else
        printf("module:%s write to wake_lock file success",module);
    close(wake_lock_fd);
}

static void do_wake_unlock(char *module)
{
    char wake_unlock_path[] = "/sys/power/wake_unlock";
    int wake_unlock_fd = -1;
    int ret = -1;

    wake_unlock_fd= open(wake_unlock_path, O_WRONLY | O_APPEND);
    if (wake_unlock_fd < 0) {
        printf("%s Failed to open wakelock file - %s", __func__, strerror(errno));
    }

    ret = write(wake_unlock_fd, module, strlen(module));
    if (ret != (int)strlen(module)) {
        printf("%s write to wakelock file failed %d - %s", __func__, ret, strerror(errno));
        
    }
    else
        printf("module:%s write to wake_unlock file success",module);
    close(wake_unlock_fd);
}

相关推荐
你怎么知道我是队长2 小时前
win11系统查看设备配置
android·java·javascript
DevangLic2 小时前
【确认是否安装了 C++ 工具】
android·java·c++
qq_229058012 小时前
Docker常用命令
linux·服务器·docker
Vect__2 小时前
基于抢票系统的线程互斥详解
linux
是个西兰花2 小时前
进程间通信:匿名管道
linux·运维·服务器
小北方城市网2 小时前
Spring Cloud Gateway 生产级微内核架构设计与可插拔过滤器开发
java·大数据·linux·运维·spring boot·redis·分布式
2501_916007472 小时前
不越狱如何查看iOS 应用的详细信息及其文件目录结构
android·macos·ios·小程序·uni-app·cocoa·iphone
wacpguo2 小时前
Ubuntu 24.04 安装 Docker
linux·ubuntu·docker
龚礼鹏2 小时前
图像显示框架十——BufferQueue的工作流程(基于Android 15源码分析)
android