kernel reboot -> device_shutdown(void) 函数相关疑惑
本文是原「蜗窝讨论区」的历史存档(2016-09-19),来自版块「Linux kernel技术问答」,共 10 帖。讨论区已停止服务,此处仅供查阅。
大家好,初次发帖,请多多支持。
项目过程中遇到一个很棘手的问题,详细如下: 手机进行自动开关机压力测试(高通8937)400-1000次左右大概会有一次卡住导致不关机(并没有kernel panic),现象就是卡在关机动画,adb还可以连,uart log也正常输出,我的做法就是把kernel 关机流程全部加上printk,然后弄10台机器进行压力测试,等到出现问题后,触发ramdump,再解开ramdump,查看kmsg中加的trace走到哪一步进一步分析卡在哪里。
但是这个情况很特殊!!!!加了kernel log后,浮现概率大大降低,就算进行2000次测试也不一定可以出现问题(进一步怀疑是某一个dev时序问题卡住,但是不知道凶手是谁..),好不容易抓到一次,大致定位到了出问题就是卡在函数就是 device_shutdown函数,可惜log太少没有找出卡在具体哪一个device,现在还在继续压力测试中,我们分析极有可能是卡在pm_runtime_barrier 函数中,但是这个内核函数有点复杂,不太懂这块。 希望有高手可以指定一二,重点详细的帮忙分析下device_shutdown 整个函数,多谢!!!!! 集思广益,希望大神们不吝给出各种调试分析手段!
void device_shutdown(void) { struct device dev, parent;
spin_lock(&devices_kset->list_lock);
/*
* Walk the devices list backward, shutting down each in turn.
* Beware that device unplug events may also start pulling
* devices offline, even as the system is shutting down.
*/
while (!list_empty(&devices_kset->list)) {
dev = list_entry(devices_kset->list.prev, struct device,
kobj.entry);
** dev_p = (char )dev->kobj.name; step1++;*
/*
* hold reference count of device's parent to
* prevent it from being freed because parent's
* lock is to be held
*/
parent = get_device(dev->parent);
get_device(dev);
/*
* Make sure the device is off the kset list, in the
* event that dev->*->shutdown( doesn't remove it.
*/
list_del_init(&dev->kobj.entry);
spin_unlock(&devices_kset->list_lock);
/* hold lock to avoid race with probe/release */
if (parent)
device_lock(parent);
device_lock(dev);
/* Don't allow any more runtime suspends */
pm_runtime_get_noresume(dev);
pm_runtime_barrier(dev);
** step2++;**
if (dev->bus && dev->bus->shutdown) {
if (initcall_debug)
dev_info(dev, "shutdown\n");
dev->bus->shutdown(dev);
} else if (dev->driver && dev->driver->shutdown) {
if (initcall_debug)
dev_info(dev, "shutdown\n");
dev->driver->shutdown(dev);
}
device_unlock(dev);
if (parent)
device_unlock(parent);
put_device(dev);
put_device(parent);
spin_lock(&devices_kset->list_lock);
}
spin_unlock(&devices_kset->list_lock);
}
上面的 dev_p = (char *)dev->kobj.name; 和step1,step2是我加的计数器,用于记录出问题时候跑了哪一步,是哪个dev卡住,可惜目前还没出结果,只能从代码层级深入挖掘o(╯□╰)o
adb能用,说明负责poweroff的进程被调度走了。 如果真觉得pm_runtime_barrier的嫌疑比较大,那就查一下这个接口:
[== Undefined ==]
pm_runtime_barrier-->__pm_runtime_barrier
...
if (dev->power.runtime_status == RPM_SUSPENDING
|| dev->power.runtime_status == RPM_RESUMING
|| dev->power.idle_notification) {
DEFINE_WAIT(wait);
/* Suspend, wake-up or idle notification in progress. */
for (;;) {
prepare_to_wait(&dev->power.wait_queue, &wait,
TASK_UNINTERRUPTIBLE);
if (dev->power.runtime_status != RPM_SUSPENDING
&& dev->power.runtime_status != RPM_RESUMING
&& !dev->power.idle_notification)
break;
spin_unlock_irq(&dev->power.lock);
schedule(;
spin_lock_irq(&dev->power.lock);
}
finish_wait(&dev->power.wait_queue, &wait);
}
…
首先确认你所使用的系统runtime pm是否使能了(如果没有,就不会是这里)。 如果使能了,有可能在系统poweroff的时候,某个设备正在进入suspend或者resume状态,这时pm_runtime_barrier就会等这个过程结束。如果由于某些原因,无法结束,就导致负责poweroff的进程无法调度回来。
试试在这个里面加一些调试信息。
wowo 写道: adb能用,说明负责poweroff的进程被调度走了。 如果真觉得pm_runtime_barrier的嫌疑比较大,那就查一下这个接口:
[== Undefined ==]
pm_runtime_barrier-->__pm_runtime_barrier
...
if (dev->power.runtime_status == RPM_SUSPENDING
|| dev->power.runtime_status == RPM_RESUMING
|| dev->power.idle_notification) {
DEFINE_WAIT(wait);
/* Suspend, wake-up or idle notification in progress. */
for (;;) {
prepare_to_wait(&dev->power.wait_queue, &wait,
TASK_UNINTERRUPTIBLE);
if (dev->power.runtime_status != RPM_SUSPENDING
&& dev->power.runtime_status != RPM_RESUMING
&& !dev->power.idle_notification)
break;
spin_unlock_irq(&dev->power.lock);
schedule(;
spin_lock_irq(&dev->power.lock);
}
finish_wait(&dev->power.wait_queue, &wait);
}
…
首先确认你所使用的系统runtime pm是否使能了(如果没有,就不会是这里)。 如果使能了,有可能在系统poweroff的时候,某个设备正在进入suspend或者resume状态,这时pm_runtime_barrier就会等这个过程结束。如果由于某些原因,无法结束,就导致负责poweroff的进程无法调度回来。
试试在这个里面加一些调试信息。
好的,多谢热情回复,我加log在pm_runtime_barrier里面,试试正常情况下有没有跑这里。 另外,昨晚跑了一夜,浮现了一次,dev_p = (char *)dev->kobj.name; 用adb拉出来了,发现卡住的dev是 :bam_dmux_ch_0.2 , 但是不了解这个东西是干什么用的, 正在查代码,想问下您是否了解这个东西?
看着像高通的东西,我也不了解是什么。
wowo 写道: 看着像高通的东西,我也不了解是什么。
确实是高通的东西,查了下好像是跟battery以及rmnet相关的,不太懂... 另外,下午我在pm_runtime_barrier里面加了log打印,确实是会跑的。 目前的有一种可以交差的解决方案就是在device_shutdown的循环里面加一个mdelay(1);貌似就可以解决问题 ,怀疑是某个dev卸载的时序上问题, 但是没有找到根本原因,挫败感啊....
想问下你知道adb是在关机什么时候给disable掉的呢?出问题的时候adb还可以用,说明这个设备还未被卸载,想从这个上面倒推下. :cool:
victor.guo 写道:
wowo 写道: 看着像高通的东西,我也不了解是什么。
确实是高通的东西,查了下好像是跟battery以及rmnet相关的,不太懂... 另外,下午我在pm_runtime_barrier里面加了log打印,确实是会跑的。 目前的有一种可以交差的解决方案就是在device_shutdown的循环里面加一个mdelay(1);貌似就可以解决问题 ,怀疑是某个dev卸载的时序上问题, 但是没有找到根本原因,挫败感啊....
想问下你知道adb是在关机什么时候给disable掉的呢?出问题的时候adb还可以用,说明这个设备还未被卸载,想从这个上面倒推下. :cool:
delay 1ms不是一个好方法……
如果想正向的跟,可以写一个脚本,在出问题之后,把所有device在sysfs暴露出来的power状态,打印出来: /sys/devices/.../power # ls autosuspend_delay_ms control runtime_active_time runtime_status runtime_suspended_time 特别是runtime status,如果是我们怀疑的这个场景,应该能够看出来。
wowo 写道:
victor.guo 写道:
wowo 写道: 看着像高通的东西,我也不了解是什么。
确实是高通的东西,查了下好像是跟battery以及rmnet相关的,不太懂... 另外,下午我在pm_runtime_barrier里面加了log打印,确实是会跑的。 目前的有一种可以交差的解决方案就是在device_shutdown的循环里面加一个mdelay(1);貌似就可以解决问题 ,怀疑是某个dev卸载的时序上问题, 但是没有找到根本原因,挫败感啊....
想问下你知道adb是在关机什么时候给disable掉的呢?出问题的时候adb还可以用,说明这个设备还未被卸载,想从这个上面倒推下. :cool:
delay 1ms不是一个好方法……
如果想正向的跟,可以写一个脚本,在出问题之后,把所有device在sysfs暴露出来的power状态,打印出来: /sys/devices/.../power # ls autosuspend_delay_ms control runtime_active_time runtime_status runtime_suspended_time 特别是runtime status,如果是我们怀疑的这个场景,应该能够看出来。
这个要怎么打印?【尴尬】 shell@Life_One_X2:/sys/devices/platform/power $ ls -al -rw-r--r-- root root 4096 2016-01-03 07:38 autosuspend_delay_ms -rw-r--r-- root root 4096 2016-01-03 07:38 control -r--r--r-- root root 4096 2016-01-03 07:38 runtime_active_time -r--r--r-- root root 4096 2016-01-03 07:38 runtime_status -r--r--r-- root root 4096 2016-01-03 07:38 runtime_suspended_time shell@Life_One_X2:/sys/devices/platform/power $ cat runtime_status unsupported
?
就是cat啦 :cool: 找一个支持使能了runtime_pm的device。
[== Undefined ==]
drivers/base/power/sysfs.c
static ssize_t rtpm_status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
const char *p;
if (dev->power.runtime_error) {
p = "error\n";
} else if (dev->power.disable_depth) {
p = "unsupported\n";
} else {
switch (dev->power.runtime_status) {
case RPM_SUSPENDED:
p = "suspended\n";
break;
case RPM_SUSPENDING:
p = "suspending\n";
break;
case RPM_RESUMING:
p = "resuming\n";
break;
case RPM_ACTIVE:
p = "active\n";
break;
default:
return -EIO;
}
}
return sprintf(buf, p);
}
shell@Life_One_X2:/sys/devices/platform/power $ cat runtime_status unsupported
这么说我这个默认就是不支持了。
victor.guo 写道: shell@Life_One_X2:/sys/devices/platform/power $ cat runtime_status unsupported
这么说我这个默认就是不支持了。
你要看某一个具体的设备的吧?不应该是“/sys/devices/platform/power”?
