linux user and kerne thread suspend 實質
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 = (例如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;
}