1. 程式人生 > >linux user and kerne thread suspend 實質

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 = (

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;
}