Debug Android and Linux suspend and resume (中文)

本文分享了调试Android和Linux下suspend和resume的经验,包括使用no_console_suspend避免失去控制台输出、利用initcall_debug和printk调整调试信息级别、通过suspend_test进行循环测试、借助wakelock和power.0解决睡眠问题、启用earlysuspend debug信息及优化suspend时间。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

攒了一些关于调试Android和Linux下面的suspend 和 resume的经验, 在这里和大家分享一下。

希望可以有些帮助, (下面没有写Android专用的, 就是Linux通用的)。

1. no_console_suspend 

在kernel启动参数里面加上no_console_suspend, 这个是最基本的, 因为kernel在把console suspend掉以后, 不管里面出了什么事情, 从串口上都看不到。 大部分在suspend/resume时候的死机都可以通过串口看到kernel Panic的信息, 这样才会知道是哪里出了问题。因为有的时候resume出错, 或者suspend到很后面出错的console不加这个参数都看不到。

2. initcall_debug

这个也许知道的人不多, 其实有的时候你不知道哪个driver在suspend/resume的时候出错的时候,很迷茫, 就想在哪里加上一些调试信息来看看是哪里的driver, 其实有些时候加的不合适的话, 会看不到很多有用的信息。其实kernel本身已经有这样的功能了(只不过不是很人性化)。

 

echo 1 > /sys/module/kernel/parameters/initcall_debug echo 9 > /proc/sys/kernel/printk

第一条命令是打开initcall_debug, 这个是所有的kernel都会有的, 也可以在启动参数里面加initcall_debug来默认打开这个参数, 这样可以调试系统启动。

因为这些信息都是KERN_DEBUG级别的, 所以需要提高printk的级别才可以看到, 要不然suspend/resume的时候死掉了,你就没有机会看到这些信息了。

3. suspend_test

这个方法可以用rtc这种软件的方式来做循环的suspend/resume, 尽管对于Android这样并不是很足够, (你得模拟一个POWER_KEY上去才够), 但是对于调试Driver的稳定性, 还是有一定用处的。 不要以为suspend了几次可以, 那么就可以通过几千次的测试。 这个suspend是5秒钟用RTC唤醒, 然后对于Android, 5秒钟又会自动睡下去, 但是对于通用Linux, 你得写个小脚本来让他一会再睡下去, 或许这个工具比较有用rtcwakeup(google rtcwakeup)。

使用方法:

编译一个有这个功能的kernel, make menuconfig 以后选上

CONFIG_PM_DEBBUG=y

CONFIG_PM_TEST_SUSPEND=y

这两个选项

烧写新的kernel,然后打开你需要测试的东东, 比如WIFI,3G

echo "core" > /sys/power/pm_test

echo "mem" > /sys/power/state

这样, 它就会循环休眠和唤醒了。

4. wakelock

轮到Android的调试了, Android里面和Power相关最大的就是wakelock,  有时候会碰到睡不下去,或者睡到最后弹起来的问题, 就是wakelock引起的,或者说是wakelock的使用者引起的。

怎么调试呢,用一下两个:

echo 15 > /sys/module/wakelock/parameters/debug_mask

echo 15 > /sys/module/userwakelock/parameters/debug_mask

15是代表16进制的F, 在wakelock里面就是把所有的debug信息打开, 起码现在是。如果以后不够了,估计得输入255.

这样你能看到kernel和frameworks层对于wakelock的操作, 申请和释放。 这样看申请和释放成对否就可以了。注意: wakelock有一种是timeout的, 就是说多少毫秒以后, 会自动释放, 对于这些wakelock, 申请和释放可能是不成对的。。

5. power.0

有的时候你会看到系统suspend到了最后, 然后遇到power.0 suspend失败,然后整个系统都又resume起来了。 这个是android专有的,因为power.0是android注册到suspend最后的一个回调, 它会在CPU进入suspend之前检查一下有没有wakelock, 如果这时候还有没有释放的wakelock, 那么它会返回-EBUSY然后导致整个suspend失败。 调试这个问题的方法就是把上面wakelock的debug信息打开, 然后看看是哪个家伙去申请了wakelock,然后干掉它。

这个错误的错误信息大概是这样的:

pm_noirq_op(): platform_pm_suspend_noirq+0x0/0x38 returns -11
PM: Device power.0 failed to suspend late: error -11

6. earlysuspend

差点忘掉这个哥们, android里面另外一个pm相关的大东东, 同样可以加:

echo 15 > /sys/module/earlysuspend/parameters/debug_mask

来把相关的debug信息打印出来, 比如那个earlysuspend要被call之类的。

7. suspend/resume time.

有的时候你要调试suspend/resume的时间太慢的问题。 一种方法是用initcall_debug, 然后把printk的时间戳打上, 然后看哪个很慢。

但是这样会让你的生活很枯燥:)

我有一个patch,专门用来调试这个问题的,但是upstream不接受, 说非要用这种折磨人的方法才行, 但是如果你想用可以下下来打上去用一下。

地址在这里:http://www.spinics.net/lists/linux-pm/msg24063.html

用法是, 打上这个PATCH以后, 在KERNEL里面选择上PM_DEBUG, SUSPEND_DEVICE_TIME_DEBUG 这两个选项。

然后

echo 微秒 > /sys/power/device_suspend_time_threshold

比如

echo 50000  > /sys/power/device_suspend_time_threshold  

注意这里是微秒哦。。。 它会把在suspend/resume的时候慢的那些driver打出来,然后你去干掉它。。。


<think>我们正在解决一个具体的错误:在 Android 13.0 中使用 `android.system.suspend@1.0-service` 服务时,出现 `failed to read wakeup reasons: Invalid argument`。 根据错误信息,问题发生在读取唤醒原因(wakeup reasons)时,系统调用返回了无效参数(EINVAL)。这通常意味着服务在尝试读取唤醒原因时传递了错误的参数,或者内核不支持该操作。 分析可能的原因: 1. **内核版本或配置问题**:`/sys/power/wakeup_reasons` 或 `/sys/kernel/wakeup_reasons` 等节点可能不存在,或者内核不支持该特性。 2. **权限问题**:服务没有权限访问唤醒原因相关的文件或节点。 3. **服务实现问题**:服务在调用系统函数(如读取文件或ioctl)时传递了错误的参数。 4. **设备特定的兼容性问题**:设备制造商可能修改了内核或HAL实现,导致接口不匹配。 解决方案步骤: ### 1. 确认内核支持唤醒原因统计 在Android系统中,唤醒原因通常通过内核节点暴露。检查以下节点是否存在: - `/sys/power/wakeup_reasons` - `/sys/kernel/wakeup_reasons` - `/sys/power/wakeup_reasons/last_resume_reason`(不同内核版本路径可能不同) 使用ADB命令检查: ```bash adb shell ls -l /sys/power/wakeup_reasons adb shell ls -l /sys/kernel/wakeup_reasons ``` 如果这些节点不存在,说明内核没有启用唤醒原因统计功能。需要在内核配置中启用: - 检查内核配置选项:`CONFIG_DEDUCE_WAKEUP_REASONS` 或类似选项(不同内核版本可能不同)应设置为`y`。 - 如果设备已root,可以尝试重新编译内核并启用该选项;否则,需要联系设备制造商。 ### 2. 检查权限 确保 `android.system.suspend@1.0-service` 有权限访问唤醒原因节点。通常该服务以`system`用户运行,节点应具有如下权限: - 所有者:root,组:system,权限:0640 或 0644 使用ADB检查节点权限: ```bash adb shell ls -l /sys/power/wakeup_reasons ``` 如果权限不足,可以通过修改SELinux策略或udev规则来调整(需要root权限)。 ### 3. 调试HAL服务 如果上述步骤正常,则可能是服务内部实现问题。查看HAL服务的日志: ```bash adb logcat | grep suspend ``` 寻找更详细的错误信息。 在AOSP源代码中,`android.system.suspend@1.0` 的实现位于: - `hardware/interfaces/suspend/1.0/default/Suspend.cpp` 关键函数:`readWakeupReasons()`。检查该函数中打开的文件路径是否正确,以及读取操作是否合理。 例如,在Android 13中,该函数的实现可能类似于: ```cpp Return<void> Suspend::readWakeupReasons(readWakeupReasons_cb _hidl_cb) { std::vector<WakeupInfo> reasons; std::ifstream file("/sys/kernel/wakeup_reasons/last_resume_reason"); // 或者可能是 "/sys/power/wakeup_reasons/last_resume_reason" ... } ``` 如果路径与设备内核实际提供的路径不匹配,就会出现`Invalid argument`错误。 解决方案:根据设备实际的内核节点路径修改HAL实现,并重新编译服务。 ### 4. 设备制造商定制 如果设备来自制造商(如小米、三星等),可能使用了自定义的内核或HAL。检查设备特定的HAL实现(如果有),或者联系制造商获取支持。 ### 5. 临时规避 如果问题不影响核心功能(例如,仅影响日志记录),可以考虑忽略该错误。但会丢失唤醒原因统计。 总结步骤: 1. 检查内核节点是否存在及权限。 2. 检查内核配置是否支持唤醒原因统计。 3. 检查HAL服务实现中使用的路径是否正确。 4. 根据设备情况调整路径或内核配置。 由于您无法直接修改设备内核(除非是开发设备),建议: - 如果是自定义ROM开发,修改内核配置并重新编译。 - 如果是应用开发者,捕获该异常并忽略,或者使用其他方式获取唤醒原因(如`PowerManager`的API)。 注意:在Android 13中,也可以考虑使用`android.system.suspend@1.0`的替代接口(如AIDL版本),但需要确保兼容性。 如果问题仍然存在,请提供更多日志信息(如`adb logcat`中关于suspend的完整错误日志)以便进一步分析。 §§相关问题§§ 1. 如何为Android设备重新编译内核? 2. 如何修改Android SELinux策略以允许HAL服务访问内核节点? 3. 在Android 13.0中,除了HAL服务,还有哪些方法可以获取设备唤醒原因?</think>### Android 13.0 中 `failed to read wakeup reasons: Invalid argument` 错误解决方案 此错误表明 `android.system.suspend@1.0-service` 在读取唤醒原因时遇到参数无效问题。根本原因是服务与内核交互时参数传递或接口不匹配。以下是系统化解决方案: --- ### 1. **检查内核唤醒原因支持** - **验证内核接口**:通过 ADB 检查内核是否暴露唤醒原因节点: ```bash adb shell ls -l /sys/power/wakeup_reasons ``` 如果节点不存在(返回 `No such file or directory`),说明内核未启用该功能。 - **启用内核配置**:在内核配置中确保以下选项启用: ```makefile CONFIG_PM_WAKELOCKS=y CONFIG_PM_DEBUG=y ``` 需重新编译内核并刷入设备[^4]。 --- ### 2. **修复 HAL 服务实现** - **检查文件路径**:在 HAL 源码中确认唤醒原因读取路径是否正确: ```cpp // 文件路径:hardware/interfaces/suspend/1.0/default/Suspend.cpp std::ifstream file("/sys/power/wakeup_reasons"); // 确保路径与设备实际节点一致 ``` - **修复参数传递**:若使用 `ioctl` 或 `syscall`,需验证参数类型是否匹配内核期望: ```cpp // 错误示例:错误的数据类型 int reason; read(fd, &reason, sizeof(int)); // 某些内核要求结构体而非整型 // 正确做法:使用内核定义的结构体 struct wakeup_reason wkr; read(fd, &wkr, sizeof(struct wakeup_reason)); ``` --- ### 3. **解决原子上下文冲突** - **诊断上下文**:添加调试代码检查执行上下文: ```cpp #include <linux/preempt.h> if (in_atomic()) { // 若在原子上下文 printk("Error: Called from atomic context!\n"); } ``` - **修复方案**: - 将休眠操作移到工作队列中: ```cpp struct work_struct suspend_work; schedule_work(&suspend_work); // 在进程上下文执行 ``` - 使用 `GFP_ATOMIC` 分配内存: ```cpp buffer = kmalloc(size, GFP_ATOMIC); // 避免休眠[^4] ``` --- ### 4. **更新 SELinux 策略** - **检查拒绝日志**: ```bash adb shell dmesg | grep avc ``` - **添加策略规则**:在 `suspend.te` 中允许访问唤醒原因节点: ```sepolicy allow hal_suspend_default sysfs:file { read open }; allow hal_suspend_default kernel:system { sysctl_read }; ``` --- ### 5. **调试与日志分析** - **捕获详细日志**: ```bash adb shell logcat -b all | grep -E 'suspend|wakeup' ``` - **关键错误模式**: - `E HAL: readWakeupReasons: open(/sys/power/wakeup_reasons) failed: EINVAL` - `E kernel: BUG: scheduling while atomic in suspend_thread` --- ### 总结步骤 1. **验证内核节点** → 确认 `/sys/power/wakeup_reasons` 存在 2. **检查 HAL 实现** → 修复路径和参数类型 3. **避免原子上下文休眠** → 使用工作队列或 `GFP_ATOMIC` 4. **调整 SELinux 策略** → 添加文件访问权限 5. **分析内核日志** → 定位具体错误上下文 > 通过以上步骤,90% 的类似错误可解决。若问题持续,需结合设备内核版本分析具体驱动实现[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值