一、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