linux的休眠与唤醒调试_linux设置睡眠时间

[TOC]

问题描述:

调试linux的休眠与唤醒

日志

添加打印日志信息

分析步骤

第1步:打开相关内核配置

CONFIG_PM_DEBUG=y
CONFIG_PM_ADVANCED_DEBUG=y
CONFIG_PM_TEST_SUSPEND=y
CONFIG_PM_SLEEP_DEBUG=y

第2步:使能相关设置

echo N > /sys/module/printk/parameters/console_suspend
echo 1 > /sys/power/pm_print_times
echo 8 > /proc/sys/kernel/printk
echo N > /sys/module/printk/parameters/console_suspend
    作用:控制系统休眠 (suspend) 期间控制台是否输出日志
    说明:N表示禁用,Y表示启用。设置为N可以在系统休眠时停止控制台日志输出,减少休眠 / 唤醒过程中的干扰
echo 1 > /sys/power/pm_print_times
    作用:启用电源管理操作的时间记录功能
    说明:设置为1后,系统会记录各种电源管理操作(如休眠、唤醒)的时间点和持续时长,有助于调试电源管理相关问题,日志通常会输出到内核环缓冲区
echo 8 > /proc/sys/kernel/printk
    作用:调整内核控制台日志级别
    说明:printk的参数值范围是 0-7(数值越小优先级越高),这里设置为 8 比较特殊,实际上会使用默认的日志级别配置。该参数控制哪些级别的内核消息会被输出到控制台,常用于调试时调整日志详细程度

    echo 8 > /proc/sys/kernel/printk 或者 dmesg -n 8 修改的是第一个参数
    cat /proc/sys/kernel/printk
    4 4 1 7

    这四个数字分别代表以下含义:
    第一个数字(控制台日志级别):
    控制哪些级别的内核消息会被打印到控制台。只有消息级别小于该值时,才会显示在控制台。
    例如:值为 4 时,只会显示级别 0-3 的消息(紧急、警报、严重错误、错误)。
    第二个数字(默认消息级别):
    当内核消息未明确指定级别时,使用的默认级别。
    例如:值为 4 时,未指定级别的消息会被视为级别 4(警告)。
    第三个数字(最低控制台日志级别):
    用于设置控制台日志级别的下限(最小值)。
    用户不能将第一个数字设置得比该值更小(更严格)。例如:值为 1 时,控制台日志级别不能设置为 0。
    第四个数字(默认控制台日志级别):
    内核启动时的默认控制台日志级别,也作为恢复默认设置时的参考值。
  1. 可以查看休眠唤醒的时间
  1. 查看休眠唤醒的结果
  1. 休眠唤醒单元测试

代码片段

一个实际案例:在实际休眠唤醒时,使用了wait_event_interruptible函数,然后在休眠过程中,来了信号导致线程被唤醒,操作了重要的资源,导致休眠失败。然后用wait_event_freezable函数替代解决问题

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/mutex.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/suspend.h>

// 等待队列和条件变量
static wait_queue_head_t my_waitq;
static int condition = 0;

// 关键资源锁(休眠流程需要访问)
static DEFINE_MUTEX(suspend_mutex);

// 等待线程:使用wait_event_interruptible
static int waiting_thread(void *data)
{
    // printk(KERN_INFO "Waiting thread started (PID: %d)\n", task_pid_nr(current));
    //默认内核线程是不被冻结的
    if (current->flags & PF_NOFREEZE)
        printk(KERN_INFO "This thread is NOT freezable\n");
    else
        printk(KERN_INFO "This thread IS freezable\n");

    //告诉内核可以该线程可以被冻结,需要配合wait_event_freezable使用
    set_freezable();

    //current->flags |= PF_NOFREEZE;  //直接操作设置不可以被冻结
    //current->flags &= ~PF_NOFREEZE; //直接操作设置可以被冻结
    
    if (current->flags & PF_NOFREEZE)
        printk(KERN_INFO "set This thread is NOT freezable\n");
    else
        printk(KERN_INFO "set This thread IS freezable\n"); 

    while (!kthread_should_stop()) {
        /*
            在实际场景中遇到过这种情况: wait_event_interruptible,然后此时正好被唤醒,操作了
            重要的资源(关掉了sram的时钟,导致在休眠过程中出现了异常)

            所以使用wait_event_interruptible还是wait_event_interruptible,主要考虑下面的情况:

            简单说就是:你写的内核线程,要不要用 wait_event_freezable,核心看一个事儿 ——这线程会不会在系统休眠的时候 “添乱”。系统休眠要求所有 “关键角色” 都得 “暂停干活、松手资源”,不然休眠就卡那了。
            先搞懂一个前提:系统休眠怕啥?
            系统休眠时,内核会喊一句:“所有人停手!资源都放了!”(这就是 “冻结”)。如果有线程不听 —— 要么还在拿着锁 / 硬件资源不放,要么明明能停却不停 —— 休眠就会卡住,甚至直接失败(比如等半天这线程还没停,超时了)。
            wait_event_freezable 就是帮线程 “听话” 的工具:让线程在等事儿的时候,顺便能响应 “冻结” 指令,乖乖暂停。
            啥时候必须用?—— 线程会 “占着茅坑不拉屎”
            如果你的线程符合下面情况,必须用,不然休眠会崩:
            线程会 “长期摸鱼等事儿”:比如线程没啥事干,就挂在那等一个信号(比如等设备发消息、等数据到),一挂可能挂很久。
            线程手里有 “关键东西”:比如拿着设备的锁、占着 IO 资源(像读写硬盘、控制传感器)。
            这种线程要是不用 wait_event_freezable,而是用别的等事儿函数(比如 wait_event_interruptible),就会像 “戴着耳机听不见指挥”—— 内核喊 “冻结” 它不理,一直保持 “等事儿” 的状态。要是手里还拿着资源,直接就把休眠卡死后;就算没拿资源,内核等它半天没反应,也会超时失败。
            比如:一个监控设备状态的线程,平时就挂着等设备有动静,这种就必须用 wait_event_freezable,不然休眠时它还在那等,内核急死也没用。
            啥时候不用?—— 线程 “不添乱”
            如果你的线程是下面这些情况,不用也没事,不会影响休眠:
            线程是 “临时工”:干一票就走,比如启动后快速处理个数据,几毫秒就退出了。休眠的时候它早没了,根本不用管。
            线程 “自己会看眼色”:虽然一直在跑,但会主动检查 “要不要冻结”。比如线程在循环干活,每次循环完都问一句 “内核让我停吗?”(用 try_to_freeze() 函数),这种就算不用 wait_event_freezable,也会乖乖停。
            线程是 “特殊豁免户”:明确告诉内核 “我不能停”(用 set_freezable(false)),而且确实不占关键资源(比如 watchdog 线程,休眠时也得看着系统,不能停)。这种情况极少,一般用不到。
            
            总结:一句话判断
            如果你的线程会 “长期挂着等事儿”(不是干一会儿就走),那就必须用 wait_event_freezable;反之,不用也行。
        */
        // 阻塞等待:条件满足或被唤醒,如果在等待过程中,系统进行了休眠,则会不冻结线程,如果有wake_up信号过来,会继续往下执行
        //wait_event_interruptible(my_waitq, condition || kthread_should_stop());
        //如果在等待过程中,系统进行了休眠,则会冻结线程,即使有wake_up信号过来,也不会往下执行
        wait_event_freezable(my_waitq, condition || kthread_should_stop());
        printk(KERN_INFO "wait_event_interruptible done\n");

        // 2. 检查是否需要退出
        if (kthread_should_stop())
            break;

        // 3. 关键修复:检测系统是否正在休眠,若正在休眠则重新进入等待
        if (freezing(current)) {
            printk(KERN_INFO "Thread: System is freezing, re-enter wait...\n");
            condition = 0;  // 重置条件
            continue;  // 重新进入等待,避免持有锁
        }

         // 先获取锁处理必要操作
        mutex_lock(&suspend_mutex);
        // 若被唤醒(非停止)
        if (!kthread_should_stop()) {
            // 执行后续操作(持有锁)
            // msleep(2000); // 模拟处理逻辑,持有锁2秒
            // 重置条件(避免重复处理)
            condition = 0;
        }
        // 释放锁
        mutex_unlock(&suspend_mutex);
        // printk(KERN_INFO "Waiting thread released suspend_mutex\n");
    }

    return 0;
}

// 唤醒线程:主动调用wake_up唤醒等待队列
static int waking_thread(void *data)
{
    while (!kthread_should_stop()) {
        // 第二次唤醒:设置condition后唤醒
        // printk(KERN_INFO "Waking thread: setting condition and calling wake_up()\n");
        condition = 1;
        wake_up(&my_waitq);
        msleep(10);
    }
    return 0;
}

// suspend回调函数:系统休眠时调用
static int my_suspend(struct device *dev)
{
    printk(KERN_INFO "Entering suspend callback: trying to acquire suspend_mutex...\n");
    
    // 尝试获取锁,可能被等待线程持有而阻塞
    mutex_lock(&suspend_mutex);
    printk(KERN_INFO "Suspend callback acquired suspend_mutex...\n");
    // 模拟保存设备状态
    msleep(2000);
    
    mutex_unlock(&suspend_mutex);
    printk(KERN_INFO "Leaving suspend callback\n");
    return 0;

    // return -EBUSY;
}

// resume回调函数:系统唤醒时调用
static int my_resume(struct device *dev)
{
    printk(KERN_INFO "Entering resume callback\n");
    msleep(3000);

    return 0;
}

// 设备电源管理操作结构体
static const struct dev_pm_ops my_pm_ops = {
    .suspend = my_suspend,
    .resume = my_resume,
};

// 平台驱动结构体
static struct platform_driver my_drv = {
    .driver = {
        .name = "wakeup_suspend_demo",
        .pm = &my_pm_ops,
    },
};

static struct platform_device *my_dev;
static struct task_struct *wait_task, *wake_task;

// 模块初始化
static int __init my_module_init(void)
{
    init_waitqueue_head(&my_waitq);

if(1) {
    // 创建等待线程
    wait_task = kthread_run(waiting_thread, NULL, "waiting_thread");
    if (IS_ERR(wait_task)) {
        printk(KERN_ERR "Failed to create waiting thread\n");
        return PTR_ERR(wait_task);
    }
    
    // 创建唤醒线程
    wake_task = kthread_run(waking_thread, NULL, "waking_thread");
    if (IS_ERR(wake_task)) {
        printk(KERN_ERR "Failed to create waking thread\n");
        kthread_stop(wait_task);
        return PTR_ERR(wake_task);
    }
}    
    // 注册平台设备和驱动
    my_dev = platform_device_register_simple("wakeup_suspend_demo", -1, NULL, 0);
    if (IS_ERR(my_dev)) {
        printk(KERN_ERR "Failed to register platform device\n");
        kthread_stop(wake_task);
        kthread_stop(wait_task);
        return PTR_ERR(my_dev);
    }
    
    if (platform_driver_register(&my_drv)) {
        printk(KERN_ERR "Failed to register platform driver\n");
        platform_device_unregister(my_dev);
        kthread_stop(wake_task);
        kthread_stop(wait_task);
        return -1;
    }
    
    printk(KERN_INFO "Module loaded. Test with: echo mem > /sys/power/state\n");
    return 0;
}

// 模块退出
static void __exit my_module_exit(void)
{
    platform_driver_unregister(&my_drv);
    platform_device_unregister(my_dev);
    kthread_stop(wake_task);
    kthread_stop(wait_task);
    printk(KERN_INFO "Module unloaded\n");
}

module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");

wait_event_interruptible 和 wait_event_freezable核心关键

图片

结论

输出结论

待查资料问题

  • 问题 1:?
  • 问题 2:?

参考链接

  • 官方文档
原文链接:,转发请注明来源!