因为最近再看耗电问题。 不可避免会涉及到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
[email protected] 2536 2891 0 0 0 483 6 85772 0
[email protected] 3 3 0 0 0 0 0 45133 0
[email protected] 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.
根据节点的名称,先查看节点生成的文件为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;
}
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,
};
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;
}
上面遍历的 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);
上锁时,都会调用下面的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);
}
在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);
}
wakeup_sources这个节点的信息对分析耗电比较有用的数据有active_count, active_since,total_time。
active_count--上锁的次数
active_since--当前的wakelock已经持续的时间
total_time--这个锁开机以来一共lock的时间
当CPU无法睡下去时,很可能就是因为某个driver持有wakelock不放导致的。这时可以这个节点来分析,找出根源。
不过,这个节点只有root权限才能查看,这是限制条件。
ps:文章和https://blog.csdn.net/weixin_42322147/article/details/80469590几乎一样,为什么呢,因为那也是我写的呀...