休眠失败排查

2023-05-16

1 休眠唤醒测试时休眠失败
大体情况:

内核:Linux 4.19
硬件:32bit ARM SOC
问题:某板子在休眠唤醒流程验证的过程中,休眠失败;
复现:echo standby > /sys/power/state
其中,异常日志如下:


# echo standby > /sys/power/state
PM: suspend entry (shallow)
PM: Syncing filesystems ... done.
Freezing user space processes ...
Freezing of tasks failed after 20.003 seconds (1 tasks refusing to freeze, wq_busy=0):
jempty_test    R runing task  ...
[] (__schedule) from [] (preempt_schedule_common+0x1c/0x2c)
[] (preempt_schedule_common) from [] (_cond_resched+0x34/0x48)
[] (_cond_resched) from [] (jempty_msgctl_message_ioctl+0x76c/0x938)
[] (jempty_msgctl_message_ioctl) from [] (vfs_ioctl+0x28/0x3c)
[] (vfs_ioctl) from [] (do_vfs_ioctl+0x90/0x84c)
[] (do_vfs_ioctl) from [] (ksys_ioctl+0x38/0x54)
[] (ksys_ioctl) from [] (ret_fast_syscall+0x0/0x54)
Exception stack (0x... to 0x...)
...
oom killer enabled.
Restarting tasks • • • done.
PM: suspend exit
sh: write error: Device or resource busy

2 休眠异常日志分析
好了,我们有了第一现场,开始分析问题:

2.1 根据现场 Log,找到出问题的代码位置
这里使用了 gdb+vmlinux,找到了异常代码的位置,也就是出错(jempty_msgctl_message_ioctl+0x76c)所在代码行。

(gdb) list *(jempty_msgctl_message_ioctl+0x76c)
 0xc0407034 is in jempty_msgctl_message_ioctl (...:191)
 186             ret = -EFAULT;
 187            }
 188 else {
 189            spin_unlock_irqrestore(&tdev->read_lock, flags);
 190            if (tdev->block_read) {
 191                    wait_event_interruptible(tdev->read_waitq, s_mbox_data_ready);
 192                    goto re_read;
 193            } else {
 194                    rx_msg->len = 0;

异常位置在 Driver 中的第 191 行,这里该模块在等待另一个 CPU 核的中断唤醒它,即 wake_up_interruptible,唤醒以后进行下一步处理;所以此时该进程是陷入了内核态,并一直处于内核态,没有机会退出。

更多定位代码行位置的方法请阅读历史文章:如何快速定位 Linux Panic 出错的代码行。

2.2 分析跟用户态进程唤醒相关的内核代码
关于休眠唤醒的全部流程就不细细分析了,这里仅分析该进程休眠失败的原因。

根据现场 Log 来看,这是进入 standby 失败的现场,导致 standby 失败的原因是进程(jempty_test)拒绝 freeze;这里需要进一步看一下 freeze 进程的 code,才能找到出现这个问题的具体原因,来到代码。

首先,对应的 call stack 是酱紫的:``

pm_suspend ->
    suspend_freeze_processes ->
        try_to_freeze_tasks->
            freeze_task(到这里进行本案的用户空间进程的冻结)

接下来分析 try_to_freeze_tasks。

static int try_to_freeze_tasks(bool user_only)
{
    if (!user_only)   // 这里用来区别,是用户进程还是内核线程;
        freeze_workqueues_begin();
    while (true) {
        todo = 0;
        read_lock(&tasklist_lock);
        for_each_process_thread(g, p) {    // 遍历所有任务
            if (p == current || !freeze_task(p)) // 冻结除了本进程以外的其他进程
                continue;
            if (!freezer_should_skip(p))    // 执行到这块说明任务冻结失败
                                            // jempty_test 也执行到这块,todo++
                todo++;
        }
        read_unlock(&tasklist_lock);
        /*********/
        if (!todo || time_after(jiffies, end_time)) // 冻结完成所有进程或者超时退出
            break;
        /*********/
    }
    end = ktime_get_boottime();
    elapsed = ktime_sub(end, start);
    elapsed_msecs = ktime_to_ms(elapsed);
    if (todo) {  // todo 不为0,证明有进程冻结失败,dump 失败信息,jempty_test 就是在这里跪了
        pr_cont("\n");
        pr_err("Freezing of tasks %s after %d.%03d seconds "
               "(%d tasks refusing to freeze, wq_busy=%d):\n",
               wakeup ? "aborted" : "failed",
               elapsed_msecs / 1000, elapsed_msecs % 1000,
               todo - wq_busy, wq_busy);
        /*************/
    } else {
        pr_cont("(elapsed %d.%03d seconds) ", elapsed_msecs / 1000,
            elapsed_msecs % 1000);    // 所有进程冻结成功,并输出花费时间
    }
    return todo ? -EBUSY : 0;
}

接着分析 freeze_task:

bool freeze_task(struct task_struct *p)
{
    /********/
    if (freezer_should_skip(p))
        return false;
    spin_lock_irqsave(&freezer_lock, flags);
    if (!freezing(p) || frozen(p)) {
        spin_unlock_irqrestore(&freezer_lock, flags);
        return false;
    }
    if (!(p->flags & PF_KTHREAD)) { // 至此,根据 flags 区别内核进程和用户进程
        fake_signal_wake_up(p);     // 用户空间进程冻结,jempty_test 执行这块
                                    // 发送一个虚假的信号去唤醒该进程
    } else {
        wake_up_state(p, TASK_INTERRUPTIBLE); // 内核空间进程冻结,不做分析
    }
    spin_unlock_irqrestore(&freezer_lock, flags);
    return true;
}

酱的话,进程 jempty_test 快要冻结了,但是还需要分析下 fake_signal_wake_up,那么 call stack 是:

fake_signal_wake_up->
    signal_wake_up->
        signal_wake_up_state

然后分析 signal_wake_up_state:

void signal_wake_up_state(struct task_struct *t, unsigned int state)
{
    set_tsk_thread_flag(t, TIF_SIGPENDING);  // 这里设置 SIGPENDING 标志位,
                                             // 说明该进程有延迟的信号要等待处理,
                                             // 当进程返回到用户空间的时候,
                                             // 会处理信号,进而 freeze 该进程
    /*
* TASK_WAKEKILL also means wake it up in the stopped/traced/killable
* case. We don't check t->state here because there is a race with it
* executing another processor and just now entering stopped state.
* By using wake_up_state, we ensure the process will wake up and
* handle its death signal.
*/
    if (!wake_up_state(t, state | TASK_INTERRUPTIBLE)) { // 设置进程状态并唤醒
        kick_process(t);
    }
}

2.3 结论
到了这里的话,jempty_test 进程不能唤醒的原因已经大白于世了,由于 jempty_test 调用的驱动的原因,使得 jempty_test 长期陷入内核态,进程不能返回用户空间,去检查 SIGPENDING,导致该进程不能被 freeze;

3 提出解决方案
3.1 Workaround
首先提出的 Workaround 方案是将 Driver 调用到的接口更改为 wake_up_interruptible_timeout,增加超时机制,使得调用该接口的用户进程有机会返回到用户空间,进而冻结该进程;

diff --git a/drivers/mailbox/jempty-msgctl.c b/drivers/mailbox/jempty-msgctl.c
index d3415b0..dfc3ff1 100755
--- a/drivers/mailbox/jempty-msgctl.c
+++ b/drivers/mailbox/jempty-msgctl.c
@@ -176,7 +176,6 @@ jempty_mu_read_shmem(struct jempty_msgctl_device *tdev, struct mu_transfer *rx_m
        s_mbox_data_ready = false;
        spin_unlock_irqrestore(&tdev->read_lock, flags);
 */
-re_read:
        spin_lock_irqsave(&tdev->read_lock, flags);
        if (!kfifo_is_empty(&tdev->recv_fifo[rx_msg->mu_id])) {
                size = kfifo_out(&tdev->recv_fifo[rx_msg->mu_id],
@@ -188,8 +187,9 @@ jempty_mu_read_shmem(struct jempty_msgctl_device *tdev, struct mu_transfer *rx_m
        } else {
                spin_unlock_irqrestore(&tdev->read_lock, flags);
                if (tdev->block_read) {
-                       wait_event_interruptible(tdev->read_waitq, s_mbox_data_ready);
-                       goto re_read;
+                       wait_event_interruptible_timeout(tdev->read_waitq, s_mbox_data_ready, HZ * 5);
+                       rx_msg->len = 0;
+                       return 0;
                } else {

3.2 Solution

这里解决的关键应该是在代码休眠过程中确保 wait_event_interruptible() 的条件满足,不过按照当前实现,如果没有 mbox data 过来,条件肯定是没法满足。那是不是可以加一个条件呢,比如说判断是否正在休眠?类似这样:

-                       wait_event_interruptible(tdev->read_waitq, s_mbox_data_ready);
+                       wait_event_interruptible(tdev->read_waitq, in_suspend || s_mbox_data_ready);

当前普通 Linux 系统一般都是用户主动发起休眠,不会自动休眠,主动发起休眠以后,用户不再使用系统,这个时候用户态确实没必要再监测数据,所以,加个 in_suspend 判断是合理的,in_suspend 满足后程序退出数据监测。

更进一步地,驱动需要实现 dev_pm_ops,实现相应的 suspend 和 resume 函数,进行相应的休眠和恢复支持,这里可以用来简单的控制 in_suspend 状态的更新。而上层应用在唤醒后需要能够自动重启数据监控。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

休眠失败排查 的相关文章

随机推荐

  • Jekyll博客中添加分类与多目录存放博客的方法

    categories Frontend tags Frontend HTML 写在前面 最近发现一个问题 博客数量越来越多了 都放在 posts下实在是有点不方便 于是想着分个类 Google 了一圈 找到了一篇不错的博客 如下 Jekyl
  • 推荐三本wpf的书

    1 葵花宝典WPF 2 WPF深入浅出 3 WPF编程宝典 个人粗略浏览了一遍 xff0c 第二本收获比较多 xff0c 第三本比较全面 xff0c 第一本相对来说没那么枯燥 xff0c 前两本我有pfd的资源文件 xff0c 需要的留言我
  • C++实现A钱买A鸡问题

    总时间限制 10000ms 单个测试点时间限制 1000ms 内存限制 131072kB 描述 A钱买A鸡 的问题 xff1a 3文钱可以买1只公鸡 xff0c 2文钱可以买1只母鸡 xff0c 1文钱可以买3只小鸡 xff0c 要用A文钱
  • equals 和 hashCode 的区别

    1 equals 和 hashCode 的区别 equals 和 hashcode 这两个方法都是从 Object 类中继承过来的 hashCode xff1a 计算出对象实例的哈希码 xff0c 并返回哈希码 xff0c 又称为散列函数
  • (踩坑)windows下的linux子系统迁移至非系统盘

    踩坑如下 xff1a 先在微软应用市场下载linux然后安装完 xff0c 再做目录链接会出现linux启动失败问题 先做目录链接会导致应用市场下载linux失败 xff0c 如下图 xff1a 正确操作如下 xff1a 注意两个路径 xf
  • 2、Zabbix 添加主机和监控项

    一 修改用户密码 1 zabbix默认会监控zabbix server本机 xff0c 如果不想监控可以在 xff08 配置 主机 xff09 里禁用掉 2 zabbix的用户都属于某个用户组 xff0c 而权限的控制都是通过用户组的 xf
  • Ubuntu进入文件夹路径及查看文件夹目录

    在Ubuntu中 xff0c 我们进入了一个文件夹 xff0c 如何看这个文件夹此时的路径呢 xff1f 通过Ctrl 43 L 可以看到路径 xff0c 然后Ctrl 43 C复制路径 再通过命令行中cd 路径 进入刚刚的文件夹 如何查看
  • WebService实例

    一 发布webservice服务 1 编写服务接口 package com nari test webservice import javax jws WebMethod import javax jws WebParam import j
  • C#使用selenium写爬虫提高速度的关键

    这段时间一直在搞爬虫 xff0c 学了一段时间之后 xff0c 最后还是使用的selenium模拟浏览器来进行爬取 就来记录一下自己踩的坑 一开始在网上找提升selenium爬虫速度的方法 xff0c 都是说什么多线程 xff0c 关闭图片
  • coreldraw2022直装版下载 永久免费使用 附安装教程( 仅限 win 10 用户 )

    CorelDRAW2022又被大家伙简称为cdr2022 xff0c 这是由加拿大Corel公司制作推出的一款老牌图形平面设计软件 xff0c 当然虽然该软件是好早之前就有了 xff0c 但是本次小编要介绍的是该系列最新的2022版本 在该
  • win10/11下WSL 图形界面安装配置指南

    win10 11下WSL 图形界面安装配置指南 一 首先安装WSL xff08 这里安装的是Ubuntu 20 04 LTS xff09 二 MobaXterm安装 xff1a 神器MobaXterm xff0c 能同时支持XShell和X
  • WSL安装迁移以及可能会遇到的问题

    可能需要的命令 xff1a 查看ubuntu版本 xff1a lsb release a 修改文件参数 xff1a G WSL grant 34 rx OI CI F 34 查看一下wsl的版本 xff0c PowerShell 命令行并输
  • namespace “std“没有成员“function“

    添加头文件 include lt functional gt 确保C 43 43 版本为C 43 43 11或更高
  • Verilog 有限状态机

    状态机基本概念 状态机 xff0c 全称是有限状态机 xff08 Finite State Machine xff0c 缩写为FSM xff09 xff0c 是一种在有限个状态之间按一定规律转换的时序电路 xff0c 可以认为是组合逻辑和时
  • 深度学习之神经网络(二)

    文章目录 深度学习之神经网络 xff08 二 xff09 一 神经网络起源 xff1a 线性回归 xff08 一 xff09 概念 xff08 二 xff09 一个线性回归问题 xff08 三 xff09 优化方法 xff1a 梯度下降 二
  • 怎样将github上的代码下载到本地并运行?

    一 直接下载 点击右下角的Download Zip xff0c 可以直接下载项目的压缩包到你的电脑上 xff08 比如我先在github上搜索了一个vue 的项目 xff09 二 通过git clone下载 PS 使用git clone下载
  • kali搭建php环境

    service apache2 start service mysql start 然后再 var wwwxi下面直接写就好了 做个小demo吧 lt php servername 61 34 localhost 34 username 6
  • linux忽略依赖强制安装软件

    1 强制安装 deb文件 dpkg i force overwrite xxx deb 强制安装软件 dpkg i force all xxx deb 不顾一切的强制安装软件 2 强制安装 rpm文件 span class token fu
  • 深入理解equals和hashCode关系和区别

    深入理解equals和hashCode关系和区别 直入主题 xff1a 区别 xff1a 1 他们判断对象相同的方式不一样 xff1a 2 他们判断对象是否相等的准确率不一样 xff1a 改写equals时总是要改写hashcode分享一波
  • 休眠失败排查

    1 休眠唤醒测试时休眠失败 大体情况 xff1a 内核 xff1a Linux 4 19 硬件 xff1a 32bit ARM SOC 问题 xff1a 某板子在休眠唤醒流程验证的过程中 xff0c 休眠失败 xff1b 复现 xff1a