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
















