功耗案例分析:周期性底电流抬高问题分析和解决
最近遇到一个间歇性底电流抬高的问题,没有其他提示。
刚开始发现有周期性问题,准备分析中断以及timer,看看能否找到线索。
结果timer数据量太大,timer多,timer超时记录更多;中断也看的云里雾里的。
然后想了一下去看看调度的规律是否能找到问题的根源。
下面就是记录分析的过程。
1. 底电流抬高问题描述
问题可以通过下面的一张图来观察。
简单描述就是,每个一段时间底电流就会被抬高0.15mA左右,持续时间不等,然后恢复原样。
这里有两个问题,一是周期在20+秒左右,另一个是每次底电流抬高的持续时间不等。
2. 发现问题根源
首先抓住周期性问题,关键词20秒。尝试通过分析timer、中断等信息,但是数据量很大,找不出头绪。
然后通过分析进程调度,借助trace-cmd和kernelshark查看调度情况。使用方法可以参考:《ftrace利器之trace-cmd和kernelshark》。
2.1 20秒周期问题
通过kernelshark分析如下,可以看出进程1052有着大概的20+秒周期的规律。
然后分析进程1052的代码,发现这些任务设定在很短的时间结束,周期为20秒。
但是上图中可以看出在20秒超时后,进程被调入,然后又被调出一段时间再调入。这中间持续的时间不等。
2.2 电流抬高时间不等问题
将进程1052的细节展开,可以发现一个周期内工作时间是不一致的。有的持续3.2秒才退出,有的是毫秒级别的。
这就也就解释了为什么电流图中为什么有的持续几秒,有的甚至看不到。
2.3 抬高期间执行情况
可以看出在进程1052的一次执行期间,进入了idle。
中间持续了大概1.86秒,在此期间进程处于操作过程中,导致底电流持续状态过长。
由以上分析可知,问题的根源在进程1052执行到中途进入了idle线程。
何时唤醒要取决于下一次唤醒时机。
2.4 验证假设
简单粗暴的验证就是不启动这个线程,通过修改内核关闭initcall。然后再也发现不了底电流抬高现象。
至此范围已经大幅缩小。
3. 解决方法
3.1 确定问题点msleep()
在基本确定了进程之后,进入进程代码进行分析。
adc_read()
{
i2c_read();
i2c_write();
for(num=1; num<=50; num++)
{
i2c_read();
i2c_write();-----------------------------------------------------(1)
msleep(5);-----------------------------------------------------(2)
i2c_read();-----------------------------------------------------(3)
}
i2c_read();
i2c_read();
}
这里需要结合function_graph和function来发现问题。
通过function_graph,监测执行路径上的主要函数,可以动态添加修改。filter可以使用当前进程的pid+函数的组合。
通过function,可以看到每个函数在系统启动timeline上的执行情况。
这两者一结合,最后发现了问题点在(1)和(3)两个函数之间有很大的时间空隙。
所以重点怀疑(2) msleep()。
msleep调度出去,如果系统进入睡眠状态,那么下一次执行的时机就取决于再次唤醒的时间。所以上面表现为底电流抬高时间不定。
将msleep修改为mdelay之后,问题不复存在。
3.2 问题解决方法
使用mdelay()之后问题得到解决,但是带来的副作用也很大。
mdelay()和msleep()的区别在于mdelay()是忙等待,独占CPU。其它进程得不到调度。
在极端情况下,for循环50次,50*5=250ms。这时一个恐怖的延时。
那么最根本的原因是什么呢?
简要分析一下,原来是msleep()之后,当前进程1052调出。系统在无事可做的情况下,进入了idle。
这里的idle是经过特殊处理的,关闭了系统tick的中断响应。
(3)的执行依赖于唤醒中断的时机,这也是抬高电流持续时间不定的根源。
在明白了问题根源之后,也就找到了解决方法。
wake_lock(&adc_wake_lock);
adc_read();
wake_unlock(&adc_wake_lock);
在采取了wake_lock()/wake_unlock()措施之后,问题得到解决。
4. 小结
首先根据周期性规律,找到了进程。
然后通过分析进程代码,借助ftrace的function_graph和function两者查看进程中各函数执行流程及其时间戳。
进而找到异常点在(1)和(3)之间,也即msleep()。
由msleep()分析到问题最根本的原因,进而找到解决方法:对关键代码加睡眠锁,读取adc信息期间禁止睡眠。
解决问题。