linux4.9 aarch32
linux hotboot的流程会先freeze user task 然后在freeze kernel task,user task的 的freeze状态就是移除运行队列,kernel task的freeze的状态分为两种,一种是手动处理里freeze信号的task一种是不处理freeze信号的task
enter_state()
-->suspend_prepare(state);
-->suspend_freeze_processes();
static inline int suspend_freeze_processes(void)
{
int error;
error = freeze_processes();
/*
* freeze_processes() automatically thaws every task if freezing
* fails. So we need not do anything extra upon error.
*/
if (error)
return error;
error = freeze_kernel_threads();
/*
* freeze_kernel_threads() thaws only kernel threads upon freezing
* failure. So we have to thaw the userspace tasks ourselves.
*/
if (error)
thaw_processes();
return error;
}
freeze_process 是freeze userspace 的thread,freeze_kernel_threads是用来freeze kernel space 的threadpm_freezing 全局变量标志userspace 开始进入freezing的阶段,先将current的进程设置为suspend_task,该task负责将所有的其他userspace task freeze,调用try_to_freeze_tasks,在该函数中suspend_task会一直在while sleep中,调用freeze_task函数对request task 进入suspend,退出的条件有如下两个,一旦退出将调用thaw_process将freeze的进程重新解冻
1 (!todo || time_after(jiffies, end_time)),freeze_timeout_msecs = 20 * MSEC_PER_SEC,20s的时间内没有完成freeze动作则退出
2 (pm_wakeup_pending())系统当前有wakup event要处理,saved_count是保存的在开始进入try_to_susped的时刻的系统当前的注册的wakeup_event数量,如果在suspend的过程中又注册了wakeup_event的也会阻止userspace task进入suspend,主要代码是
split_counters(&cnt, &inpr); ret = (cnt != saved_count || inpr > 0);例如auto_sleep与wake_lock机制中的__pm_stay_awake-->wakeup_source_report_event-->wakeup_source_active-->inc combined_event_count
/*
* Combined counters of registered wakeup events and wakeup events in progress.
* They need to be modified together atomically, so it's better to use one
* atomic variable to hold them both.
*/
static atomic_t combined_event_count = ATOMIC_INIT(0);
freeze_task函数中会对user space 的thread 发送信号,这样当suspend_task 让出cpu的时候,其他的thread 从kernel space返回时发现有信号需要处理,do_signal-->get_signal-->try_to_freeze-->__refrigerator,冰冻的状态就是将task的状态设置成task_uninterruptable 和frozen然后schedule让出cpu,因此suspend_task不停的努力工作下,最终todo为0所有的user task 变成了refrigerator的状态
int freeze_processes(void)
{
/* Make sure this task doesn't get frozen */
current->flags |= PF_SUSPEND_TASK;
pr_info("Freezing user space processes ... ");
pm_freezing = true;
error = try_to_freeze_tasks(true);
/*
* Now that the whole userspace is frozen we need to disbale
* the OOM killer to disallow any further interference with
* killable tasks. There is no guarantee oom victims will
* ever reach a point they go away we have to wait with a timeout.
*/
if (error)
thaw_processes();
return error;
}
kernel space的task的freeze同样是调用的try_to_freeze_tasks函数,只是传递的参数为false,参数为false的时候会去处理workqueue对应代码if (!user_only) {wq_busy = freeze_workqueues_busy(); todo += wq_busy;} 要等到工作队列也完成freeze才认为所有的task 完成freeze。
对task发送suspend请求的函数freeze_task,也能从中看出针对kernel task的不同处理if (!(p->flags & PF_KTHREAD)) fake_signal_wake_up(p); else wake_up_state(p, TASK_INTERRUPTIBLE); userspace的task是通过信号的机制发送suspend request的,kernelspace的task是调用wakeup_state将当前处于interruptible的task唤醒放到运行队列中。
如果该task在等待信号量或者在主动休眠中比如down的实现是把task设置成task_uninterruptible的状态msleep也是同样设置为task_uninterruptible状态,这种task既然已经处于task_uninterruptible 状态,系统就不会再额外处理它
kernelspace的task 在创建的时候默认都是PF_NOFREEZE,不会被suspend_task处理,如果需要的话需要手动调用函数
set_freezable();
while(1){try_to_freeze(); ......... sleep();}
set_freezeable函数current->flags &= ~PF_NOFREEZE;,wake_up_states唤醒task后kernelspace task也就会进入refrigerator状态
/**
* freeze_kernel_threads - Make freezable kernel threads go to the refrigerator.
*
* On success, returns 0. On failure, -errno and only the kernel threads are
* thawed, so as to give a chance to the caller to do additional cleanups
* (if any) before thawing the userspace tasks. So, it is the responsibility
* of the caller to thaw the userspace tasks, when the time is right.
*/
int freeze_kernel_threads(void)
{
pr_info("Freezing remaining freezable tasks ... ");
pm_nosig_freezing = true;
error = try_to_freeze_tasks(false);
if (error)
thaw_kernel_threads();
return error;
}
static int try_to_freeze_tasks(bool user_only)
{
while (true) {
todo = 0;
for_each_process_thread(g, p) {
if (p == current || !freeze_task(p))
continue;
if (!freezer_should_skip(p))
todo++;
}
if (!user_only) {
wq_busy = freeze_workqueues_busy();
todo += wq_busy;
}
if (!todo || time_after(jiffies, end_time))
break;
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.
*/
usleep_range(sleep_usecs / 2, sleep_usecs);
if (sleep_usecs < 8 * USEC_PER_MSEC)
sleep_usecs *= 2;
}
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) {
for_each_process_thread(g, p) {
if (p != current && !freezer_should_skip(p)
&& freezing(p) && !frozen(p))
sched_show_task(p);
}
}
} else {
pr_cont("(elapsed %d.%03d seconds) ", elapsed_msecs / 1000,
elapsed_msecs % 1000);
}
return todo ? -EBUSY : 0;
}