Android Kernel wakeup_sources分析

因为最近再看耗电问题。 不可避免会涉及到kernel层的wakelock。在adb shell下面cat /d/wakeup_sources(需要root权限),可以得到kernel的wakelock信息。

name                    active_count    event_count    wakeup_count    expire_count    active_since    total_time    max_time    last_change    prevent_suspend_time
HVDCPD_WL                           18        18        0        0        0        100        11        76513        0
ipc000000ef_sensors@1.0-ser         2536        2891        0        0        0        483        6        85772        0
ipc000000ee_sensors@1.0-ser         3        3        0        0        0        0        0        45133        0
ipc000000ed_sensors@1.0-ser         9        9        0        0        0        8        6        84263        0
bluetooth_timer                     64        64        0        0        0        3213        3001        69776        0
hal_bluetooth_lock                  1        1        0        0        0        79        79        33380        0
SMD_TTY_APPS_RIVA_BT_ACL_RA         0        0        0        0        0        0        0        33297        0
APPS_RIVA_BT_ACL                    0        0        0        0        0        0        0        33297        0
SMD_TTY_APPS_RIVA_BT_CMD_RA         109        114        0        0        0        4        0        69775        0
所以,接下来想好好看看这些参数的含义。主要是看active_count 和active_since,total_time.

1. 节点的创建
根据节点的名称,先查看节点生成的文件为kernel/msm-3.18/drivers/base/power/wakeup.c

static int __init wakeup_sources_debugfs_init(void)
{
    wakeup_sources_stats_dentry = debugfs_create_file("wakeup_sources",
            S_IRUGO, NULL, NULL, &wakeup_sources_stats_fops);    //创建"/d/wakeup_sources"。
    return 0;
}
2.节点操作的相关函数
static const struct file_operations wakeup_sources_stats_fops = {
    .owner = THIS_MODULE,
    .open = wakeup_sources_stats_open,
    .read = seq_read,
    .llseek = seq_lseek,
    .release = single_release,
};
3.cat /d/wakeup_sources时调用的函数,wakeup_sources_stats_open
static int wakeup_sources_stats_open(struct inode *inode, struct file *file)
{
    return single_open(file, wakeup_sources_stats_show, NULL);
}
再看看wakeup_sources_stats_show。

/**
 * wakeup_sources_stats_show - Print wakeup sources statistics information.
 * @m: seq_file to print the statistics into.
 */
static int wakeup_sources_stats_show(struct seq_file *m, void *unused)
{
    struct wakeup_source *ws;
 
    seq_puts(m, "name\t\t\t\t\tactive_count\tevent_count\twakeup_count\t"
        "expire_count\tactive_since\ttotal_time\tmax_time\t"
        "last_change\tprevent_suspend_time\n");   //这就是上面的第一行。
 
    rcu_read_lock();  
    list_for_each_entry_rcu(ws, &wakeup_sources, entry)  //互斥遍历wakeup_sources,每次取出其中之一ws.
        print_wakeup_source_stats(m, ws); //打印出照这个ws的相关信息
    rcu_read_unlock();
 
    return 0;
}
再看看print_wakeup_source_stats:

/**
 * print_wakeup_source_stats - Print wakeup source statistics information.
 * @m: seq_file to print the statistics into.
 * @ws: Wakeup source object to print the statistics for.
 */
static int print_wakeup_source_stats(struct seq_file *m,
                     struct wakeup_source *ws)
{
    unsigned long flags;
    ktime_t total_time;
    ktime_t max_time;
    unsigned long active_count;
    ktime_t active_time;
    ktime_t prevent_sleep_time;
    int ret;
 
    spin_lock_irqsave(&ws->lock, flags);
 
    total_time = ws->total_time;  //之前的total_time
    max_time = ws->max_time;
    prevent_sleep_time = ws->prevent_sleep_time;
    active_count = ws->active_count;
    if (ws->active) {    //如果这个wakelock还在,没有释放掉
        ktime_t now = ktime_get();
 
        active_time = ktime_sub(now, ws->last_time); //active_time就是开始上锁到目前时间差
        total_time = ktime_add(total_time, active_time);  // total_time 就是上一次的total_time加上active_time
        if (active_time.tv64 > max_time.tv64)
            max_time = active_time;
 
        if (ws->autosleep_enabled)
            prevent_sleep_time = ktime_add(prevent_sleep_time,
                ktime_sub(now, ws->start_prevent_time));
    } else {
        active_time = ktime_set(0, 0);
    }
 
    ret = seq_printf(m, "%-32s\t%lu\t\t%lu\t\t%lu\t\t%lu\t\t"
            "%lld\t\t%lld\t\t%lld\t\t%lld\t\t%lld\n",
            ws->name, active_count, ws->event_count,
            ws->wakeup_count, ws->expire_count,
            ktime_to_ms(active_time), ktime_to_ms(total_time),
            ktime_to_ms(max_time), ktime_to_ms(ws->last_time),
            ktime_to_ms(prevent_sleep_time));     //结果。
 
    spin_unlock_irqrestore(&ws->lock, flags);
 
    return ret;
}
4. wakeup_sources,一个当前文件中的全局list。在init一个wakelock的时候添加。
上面遍历的 wakeup_sources,本文件中的一个全局链表。很容易猜测到,每次申请一个wake_lock时,都会添加一个item到这个链表中。看看这个全局的list的声明和初始化。

static LIST_HEAD(wakeup_sources);  //初始化。
接下来看这个list的添加。想要使用wakelock,肯定是要先调用下面的init函数。我们在kernel中声明的wake_lock结构都有wakeup_source结构成员。后面更多的是用这个wakeup_source结构来管理。

static inline void wake_lock_init(struct wake_lock *lock, int type,
                  const char *name)
{
    wakeup_source_init(&lock->ws, name);
}
接着看看被调用的wakeup_source_init.

static inline void wakeup_source_init(struct wakeup_source *ws,
                      const char *name)
{
    wakeup_source_prepare(ws, name); //准备工作
    wakeup_source_add(ws); //真正的添加。
}

 
继续看wakeup_source_add.

/**
 * wakeup_source_add - Add given object to the list of wakeup sources.
 * @ws: Wakeup source object to add to the list.
 */
void wakeup_source_add(struct wakeup_source *ws)
{
    unsigned long flags;
 
    if (WARN_ON(!ws))
        return;
 
    spin_lock_init(&ws->lock);
    setup_timer(&ws->timer, pm_wakeup_timer_fn, (unsigned long)ws);
    ws->active = false;  //初始化为非active
    ws->last_time = ktime_get();   //当前时间。
 
    spin_lock_irqsave(&events_lock, flags);
    list_add_rcu(&ws->entry, &wakeup_sources);  //添加到wakeup_sources
    spin_unlock_irqrestore(&events_lock, flags);
}
EXPORT_SYMBOL_GPL(wakeup_source_add);
5.wakelock上锁时的操作
上锁时,都会调用下面的wake_lock函数。

static inline void wake_lock(struct wake_lock *lock)
{
    __pm_stay_awake(&lock->ws);
}
继续看__pm_stay_awake。

/**
 * __pm_stay_awake - Notify the PM core of a wakeup event.
 * @ws: Wakeup source object associated with the source of the event.
 *
 * It is safe to call this function from interrupt context.
 */
void __pm_stay_awake(struct wakeup_source *ws)
{
    unsigned long flags;
 
    if (!ws)
        return;
 
    spin_lock_irqsave(&ws->lock, flags);
 
    wakeup_source_report_event(ws);  //主要函数。
    del_timer(&ws->timer);
    ws->timer_expires = 0;
 
    spin_unlock_irqrestore(&ws->lock, flags);
}
EXPORT_SYMBOL_GPL(__pm_stay_awake);
继续看wakeup_source_report_event。

/**
 * wakeup_source_report_event - Report wakeup event using the given source.
 * @ws: Wakeup source to report the event for.
 */
static void wakeup_source_report_event(struct wakeup_source *ws)
{
    ws->event_count++;  //event_count计数
    /* This is racy, but the counter is approximate anyway. */
    if (events_check_enabled)
        ws->wakeup_count++;//wakeup_count计数
    if (!ws->active)  //如果是非active状态
        wakeup_source_activate(ws); //那就变成active
}
接着看wakeup_source_activate.

/**
 * wakup_source_activate - Mark given wakeup source as active.
 * @ws: Wakeup source to handle.
 *
 * Update the @ws' statistics and, if @ws has just been activated, notify the PM
 * core of the event by incrementing the counter of of wakeup events being
 * processed.
 */
static void wakeup_source_activate(struct wakeup_source *ws)
{
    unsigned int cec;
 
    /*
     * active wakeup source should bring the system
     * out of PM_SUSPEND_FREEZE state
     */
    freeze_wake();  //保证上锁期间CPU不会睡下去
 
    ws->active = true;
    ws->active_count++; //active_count计数
    ws->last_time = ktime_get();  //这时的时间,也就是开始上锁的时间
    if (ws->autosleep_enabled)
        ws->start_prevent_time = ws->last_time;
 
    /* Increment the counter of events in progress. */
    cec = atomic_inc_return(&combined_event_count);
 
    trace_wakeup_source_activate(ws->name, cec);
}
6.wakelock释放时的操作
在kernel wakelock释放的时候,都会调用下面的wake_unlock函数。

static inline void wake_unlock(struct wake_lock *lock)
{
    __pm_relax(&lock->ws);
}
继续看看__pm_relax。

/**
 * __pm_relax - Notify the PM core that processing of a wakeup event has ended.
 * @ws: Wakeup source object associated with the source of the event.
 *
 * Call this function for wakeup events whose processing started with calling
 * __pm_stay_awake().
 *
 * It is safe to call it from interrupt context.
 */
void __pm_relax(struct wakeup_source *ws)
{
    unsigned long flags;
 
    if (!ws)
        return;
 
    spin_lock_irqsave(&ws->lock, flags);
    if (ws->active)   //如果目前仍是active,那就要释放掉
        wakeup_source_deactivate(ws);
    spin_unlock_irqrestore(&ws->lock, flags);
}
EXPORT_SYMBOL_GPL(__pm_relax);
再看wakeup_source_deactivate。

/**
 * wakup_source_deactivate - Mark given wakeup source as inactive.
 * @ws: Wakeup source to handle.
 *
 * Update the @ws' statistics and notify the PM core that the wakeup source has
 * become inactive by decrementing the counter of wakeup events being processed
 * and incrementing the counter of registered wakeup events.
 */
static void wakeup_source_deactivate(struct wakeup_source *ws)
{
    unsigned int cnt, inpr, cec;
    ktime_t duration;
    ktime_t now;
 
    ws->relax_count++;
    /*
     * __pm_relax() may be called directly or from a timer function.
     * If it is called directly right after the timer function has been
     * started, but before the timer function calls __pm_relax(), it is
     * possible that __pm_stay_awake() will be called in the meantime and
     * will set ws->active.  Then, ws->active may be cleared immediately
     * by the __pm_relax() called from the timer function, but in such a
     * case ws->relax_count will be different from ws->active_count.
     */
    if (ws->relax_count != ws->active_count) {
        ws->relax_count--;
        return;
    }
 
    ws->active = false;
 
    now = ktime_get();
    duration = ktime_sub(now, ws->last_time);   //完整上锁的时间
    ws->total_time = ktime_add(ws->total_time, duration); //这把所从init之后所有上锁的时间总和
    if (ktime_to_ns(duration) > ktime_to_ns(ws->max_time))
        ws->max_time = duration;
 
    ws->last_time = now;
    del_timer(&ws->timer);
    ws->timer_expires = 0;
 
    if (ws->autosleep_enabled)
        update_prevent_sleep_time(ws, now);
 
    /*
     * Increment the counter of registered wakeup events and decrement the
     * couter of wakeup events in progress simultaneously.
     */
    cec = atomic_add_return(MAX_IN_PROGRESS, &combined_event_count);
    trace_wakeup_source_deactivate(ws->name, cec);
 
    split_counters(&cnt, &inpr);
    if (!inpr && waitqueue_active(&wakeup_count_wait_queue))
        wake_up(&wakeup_count_wait_queue);
}
7. 总结
wakeup_sources这个节点的信息对分析耗电比较有用的数据有active_count, active_since,total_time。

active_count--上锁的次数

active_since--当前的wakelock已经持续的时间

total_time--这个锁开机以来一共lock的时间

当CPU无法睡下去时,很可能就是因为某个driver持有wakelock不放导致的。这时可以这个节点来分析,找出根源。

不过,这个节点只有root权限才能查看,这是限制条件。
--------------------- 
版权声明:本文为CSDN博主「咸因心上」的原创文章,遵循CC 4.0 by-sa版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/cassie_huang/article/details/80346030

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值