Skip to main content
 首页 » 操作系统

Linux 系统suspend流程介绍

2022年07月19日28xxx_UU

一、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\n"); 
        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\n", ws->name); 
/* 若所有唤醒源都处于释放了,就打印最后一个释放锁的唤醒源的名字 */ 
pr_info("last active wakeup source: %s\n", 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("\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); 
 
        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\n", 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