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
[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.

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权限才能查看,这是限制条件。

 

ps:文章和https://blog.csdn.net/weixin_42322147/article/details/80469590几乎一样,为什么呢,因为那也是我写的呀...

你可能感兴趣的:(安卓学习)