系统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打印不完全一样。

原文地址:https://www.cnblogs.com/hellokitty2/p/14224548.html