• 系统suspend流程介绍


    一、auto suspend介绍


    二、auto sleep介绍

    三、关键函数介绍

    1. pm_wakeup_pending

    用于在整个休眠流程中时刻监测是否有唤醒事件产生,然后中止休眠流程,实现如下:

    bool pm_wakeup_pending(void)
    {
        unsigned long flags;
        bool ret = false;
    
        raw_spin_lock_irqsave(&events_lock, flags);
        if (events_check_enabled) {
            unsigned int cnt, inpr;
            
            /* cnt: 已处理的唤醒事件计数,inpr: 处于active状态的唤醒事件计数 */
            split_counters(&cnt, &inpr);
            /* saved_count的值就是待机流程初始,用户空间suspend进程写下来的读
             * 取/sys/power/wakeup_count的值。*/
            ret = (cnt != saved_count || inpr > 0);
            /* 赋值位置1 */
            events_check_enabled = !ret;
        }
        raw_spin_unlock_irqrestore(&events_lock, flags);
    
        if (ret) {
            pr_debug("PM: Wakeup pending, aborting suspend
    ");
            pm_print_active_wakeup_sources();
        }
    
        /*pm_abort_suspend在冻结进程的时候设置为0了*/
        return ret || atomic_read(&pm_abort_suspend) > 0;
    }

    (1) events_check_enabled 全局变量

    a. 何时赋值

    //赋值位置2:
    static int enter_state(suspend_state_t state) //kernel/power/suspend.c
    {
        ...
    Finish:
        events_check_enabled = false; //finish的时候设置为false
    }
    //赋值位置3:
    //wakeup_count_store(kernel/power/main.c) --> pm_save_wakeup_count
    bool pm_save_wakeup_count(unsigned int count) //drivers/base/power/wakeup.c
    {
        unsigned int cnt, inpr;
        unsigned long flags;
    
        /* 先赋值为假,若相对于读取wakeup_count时刻有
         * 新唤醒事件产生,则此函数也返回假。 */
        events_check_enabled = false;
        raw_spin_lock_irqsave(&events_lock, flags);
        split_counters(&cnt, &inpr);
        if (cnt == count && inpr == 0) {
            saved_count = count;
            /* 相对于读取wakeup_count时刻没有任何唤醒事件产生,
             * 就赋值为true,使能对唤醒事件的检测。*/
            events_check_enabled = true;
        }
        raw_spin_unlock_irqrestore(&events_lock, flags);
        return events_check_enabled;
    }
    
    static ssize_t wakeup_count_store(struct kobject *kobj,
                    struct kobj_attribute *attr,
                    const char *buf, size_t n)
    {
        ...    
        error = -EINVAL
        if (pm_save_wakeup_count(val))
            error = n;
        else
            /* 有唤醒事件产生就打印唤醒事件,然后此函数错误返回,
             * 用户空间的suspend进程得到一个错误的返回就中止此次
             * 休眠流程了。*/
            pm_print_active_wakeup_sources();
        ...
        return error;
    }

    b. 代表含义
    events_check_enabled 需要对唤醒事件进行检测的时候才需要设置true。何时需要检测呢,在新的休眠流程触发前不需要检测(赋值位置2)。只有在suspend流程中且可以继续休眠下去,此时需要继续检测,设置为true(赋值位置3)。若确认有新的唤醒事件产生,不需要再继续休眠流程了,此时赋值为false不再检测了(赋值位置1)。

    总结起来就是 在休眠流程中,此时需要继续检测,events_check_enabled的值才为true。

    (2) pm_abort_suspend 全局变量

    a. 何时赋值

    /* 这里设置pm_abort_suspend为1,表示要结束suspend流程 */
    void pm_system_wakeup(void) //drivers/base/power/wakeup.c
    {
        atomic_inc(&pm_abort_suspend);
        s2idle_wake();
    }
    
    void pm_system_cancel_wakeup(void) //drivers/base/power/wakeup.c
    {
        /* 如果大于0才减去1 */
        atomic_dec_if_positive(&pm_abort_suspend);
    }
    
    void pm_wakeup_clear(bool reset) //drivers/base/power/wakeup.c
    {
        pm_wakeup_irq = 0;
        if (reset)
            atomic_set(&pm_abort_suspend, 0); /*清0,表示允许继续待机*/
    }

    b. 代表含义

    内核中只要调用 pm_system_wakeup() 也能中止休眠,无需持锁。

    (3) pm_print_active_wakeup_sources 打印内容

    /* 若有处于atives状态中止休眠流程的唤醒源打印其名字 */
    pr_info("active wakeup source: %s
    ", ws->name);
    /* 若所有唤醒源都处于释放了,就打印最后一个释放锁的唤醒源的名字 */
    pr_info("last active wakeup source: %s
    ", last_activity_ws->name);

    2. try_to_freeze_tasks

    用户冻结用户空间进程和内核线程的,调用关系如下:

    suspend_prepare
        suspend_freeze_processes
            try_to_freeze_tasks
    static int try_to_freeze_tasks(bool user_only) //kernel/sched/core.c
    {
        struct task_struct *g, *p;
        unsigned long end_time;
        unsigned int todo;
        bool wq_busy = false;
        ktime_t start, end, elapsed;
        unsigned int elapsed_msecs;
        bool wakeup = false;
        int sleep_usecs = USEC_PER_MSEC; /*1000 = 1ms*/
    
        start = ktime_get_boottime();
    
        end_time = jiffies + msecs_to_jiffies(freeze_timeout_msecs); /*20s*/
    
        if (!user_only)
            freeze_workqueues_begin(); /*冻结workqueue*/
    
        while (true) {
            todo = 0;
            read_lock(&tasklist_lock);
            /*注意:这是一个双循环,“中断”将无法按预期进行。进程嵌套一个,线程嵌套一个*/
            for_each_process_thread(g, p) {
                /*
                 * freeze_task
                 * RETURNS:
                 * freeze_task(p): %false, if @p is not freezing or already frozen; %true, otherwise
                */
                if (p == current || !freeze_task(p)) /*正在被冻结中就返回真*/
                    continue;
    
                /*进程又没有设置PF_FREEZER_SKIP标志位*/
                if (!freezer_should_skip(p)) /*return p->flags & PF_FREEZER_SKIP;*/
                    todo++;
            }
            read_unlock(&tasklist_lock);
    
            if (!user_only) {
                wq_busy = freeze_workqueues_busy(); /*返回的是bool值*/
                todo += wq_busy;
            }
    
            /*while(true)退出的途径有两个,一个是这里的还有待冻结的,且超时20s都没有冻结完毕的*/
            if (!todo || time_after(jiffies, end_time))
                break;
    
            /*退出途径2: 检测到锁变化也会退出*/
            if (pm_wakeup_pending()) {
                wakeup = true;
                break;
            }
    
            /*
             * We need to retry, but first give the freezing tasks some
             * time to enter the refrigerator.  Start with an initial
             * 1 ms sleep followed by exponential backoff until 8 ms.
             */
            /**我们需要重试,但首先要给冷冻任务一些时间以进入冻结状态。
            从最初的1 ms睡眠开始,然后是指数补偿,直到8 ms。*/
            usleep_range(sleep_usecs / 2, sleep_usecs);
            if (sleep_usecs < 8 * USEC_PER_MSEC) /*单次睡眠等待最大时间是8ms*/
                sleep_usecs *= 2;
        }
    
        end = ktime_get_boottime();
        elapsed = ktime_sub(end, start);
        elapsed_msecs = ktime_to_ms(elapsed);
    
        if (todo) {
            pr_cont("
    ");
            pr_err("Freezing of tasks %s after %d.%03d seconds "
                   "(%d tasks refusing to freeze, wq_busy=%d):
    ",
                   wakeup ? "aborted" : "failed",
                   elapsed_msecs / 1000, elapsed_msecs % 1000,
                   todo - wq_busy, wq_busy);
    
            if (wq_busy)
                show_workqueue_state();
    
            if (!wakeup) {
                read_lock(&tasklist_lock);
                for_each_process_thread(g, p) {
                    /* 如果进程p不是触发休眠的进程,也没有PF_FREEZER_SKIP标志位,
                     * 还在冻结中,但是还没有冻住,就打印冻结失败进程的信息。 */
                    if (p != current && !freezer_should_skip(p) && freezing(p) && !frozen(p))
                        sched_show_task(p);
                }
                read_unlock(&tasklist_lock);
            }
        } else {
            pr_cont("(elapsed %d.%03d seconds) ", elapsed_msecs / 1000, elapsed_msecs % 1000);
        }
    
        return todo ? -EBUSY : 0;
    }
    void sched_show_task(struct task_struct *p) //kernel/sched/core.c
    {
        unsigned long free = 0;
        int ppid;
    
        if (!try_get_task_stack(p))
            return;
    
        /* 打印进程名,进程运行状态字符描述 */
        printk(KERN_INFO "%-15.15s %c", p->comm, task_state_to_char(p));
    
        if (p->state == TASK_RUNNING)
            /* 若是正在运行,还会在同一行打印出"running task"字符串出来 */
            printk(KERN_CONT "  running task    ");
    #ifdef CONFIG_DEBUG_STACK_USAGE
        free = stack_not_used(p);
    #endif
        ppid = 0;
        rcu_read_lock();
        if (pid_alive(p))
            ppid = task_pid_nr(rcu_dereference(p->real_parent));
        rcu_read_unlock();
        /* 还会在同一行打印出pid, ppid, 进程的flags */
        printk(KERN_CONT "%5lu %5d %6d 0x%08lx
    ", free, task_pid_nr(p), ppid,
            (unsigned long)task_thread_info(p)->flags);
    
        print_worker_info(KERN_INFO, p);
        show_stack(p, NULL);
        put_task_stack(p);
    }

    在这个冻结流程里面有个20秒超时时间的一个死循环,在这20s内不断尝试对还未冻结住的进程进行等待,若是20s超时和还没有冻住的话,就调用sched_show_task()打印出冻结失败进程的相关信息。

    注意:同为4.19的内核,这里面的log打印不完全一样。

  • 相关阅读:
    vue this触发事件
    jQuery获取地址栏中的链接参数
    vue 省市区三级联动
    图片文字css小知识点
    sticky footer 模板
    Django学习——用户自定义models问题解决
    Django学习——全局templates引用的问题
    Django的学习——全局的static和templates的使用
    selenium登录爬取知乎出现:请求异常请升级客户端后重试的问题(用Python中的selenium接管chrome)
    使用python远程连接数据库
  • 原文地址:https://www.cnblogs.com/hellokitty2/p/14224548.html
Copyright © 2020-2023  润新知