Andorid性能优化之功耗-Linux内核

本文参考:
http://www.wowotech.net/pm_subsystem/wakelocks.html
http://www.wowotech.net/pm_subsystem/wakelocks.html
http://www.wowotech.net/pm_subsystem/wakelocks.html
文章中代码来源于:Mtk8173,android 6.0

Linux 3.4内核开始的变化

(1)linux 3.4内核PM使用了新的wakeup_source机制,摒弃了繁杂的wake_lock机制
之前android一直是基于Linux加入了wake_lock机制来阻止系统休眠,后来Linux 3.4内核加入了wakeup_source来管理,安卓4.4跟着升级内核也就摒弃了自己的繁杂的wake_lock机制,在对上层接口并不改变,在内核wake_lock实现直接基于wakeup_source来实现的。当然也会带来debug上的一些问题,比如以前的wake_lock自身带有强大的debug信息,那么我们在调试的时候可以自己看见dmesg中默认打印active wake lock XXX,很直观来辨别需要休眠的时候那个wake lock有问题阻止了休眠。这个需要我们自己来完善。个人认为改进很大,现在使用了autosleep机制,只要不存在任何active wakeup_source了,系统自动休眠,当有active wake_source自动block住,个人认为休眠更及时,非休眠时间在减少,同时不会消耗额外的资源。使用基于queue work与进程block来管理suspend。还有这里的wakeup_source个人觉得应该叫keepawake_source或者stayawake_souce,毕竟系统的唤醒也就是cpu的再次运行是由中断唤醒的而不是wakeup_source。
(2)安卓4.4还有一个重大改变就是去除了early suspend机制改为fb event通知机制。那么现在就只有suspend与resume,runtime suspend与runtime resume了。

(1) wakelock

说起android的休眠机制,就不得不说说wakelock,下面我们详细介绍下waklock机制的历史。

Android和Linux的恩恩怨怨

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都支持)。

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以允许睡眠。

Kernel wakelocks的功能

对比Android wakelocks要实现的功能,Linux kernel的方案是:

1)允许driver创建wakelock以阻止睡眠、注销wakelock以允许睡眠:已经由新的wakeup source机制取代。

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

3)wake_lock和wake_unlock功能:由本文所描述的kernel wakelocks实现,其本质就是将wakeup source开发到用户空间访问。

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。


/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列表)。

这两个sysfs文件在kernel/power/main.c中实现,如下:

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

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;

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

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

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

 out:
    mutex_unlock(&wakelocks_lock);
    return ret;
}

a)输入参数为一个字符串,如"wake_lock_test 1000”,该字符串指定上报wakeup event的wakelock name,可以在name后用空格隔开,添加一个时间值(单位为ns),表示该event的timeout值。
b)调用capable,检查当前进程是否具备阻止系统suspend的权限。
capable是Linux security子系统提供的一个接口,用于权限判断。我们说过,power是系统的核心资源,理应由OS全权管理,但wakelock违反了这一原则,将阻止系统睡眠的权利给了用户空间。这样一来,用户空间程序将可以随心所欲的占用power资源,特别是用户态的程序员,天生对资源占用不敏感(这是对的),就导致该接口有被滥用的风险。不过还好,通过系统的权限管理机制,可以改善这种状态(其实不是改善,而是矛盾转移,很有可能把最终的裁决权交给用户,太糟糕了!)。</font>
c)解析字符串,将timeout值(有的话)保存在timeout_ns中,解析name长度(len),并将name保存在原来的buf中。
d)调用wakelock_lookup_add接口,查找是否有相同name的wakelock。如果有,直接返回wakelock的指针;如果没有,分配一个wakelock,同时调用wakeup events framework提供的接口,创建该wakelock对应的wakeup source结构。
e)如果指定timeout值,以wakelock的wakeup source指针为参数,调用__pm_wakeup_event接口,上报一个具有时限的wakeup events;否则,调用__pm_stay_awake,上报一个没有时限的wakeup event。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容