今回はfreezeを調べてみます
前回はLinuxのPower Managementの導入部分を調べてみました。
今回は、
state: Suspend-To-Idle
ACPI state: S0
Label: "freeze"
について調べてみようと思います。(7月のもくもく会のネタを放置していた・・・)
そもそもfreezeってなんだ。
Documentation/power/freezing-of-tasks.txt に書かれています。
正式な名前(?)はfreezing of tasksらしいです。
以下freezing-of-tasks.txtの前半2/3の要約を書きます。
後半1/3も面白いけど、freezing of tasksの実装含めた概要を知るためにはとりあえず前半2/3で十分だと思います。
-
freezing of tasksとは、システムのPower stateがhibernationもしくはsystem-wide suspendの場合に実施される、ユーザプロセスやいくつかのカーネルスレッドをfreeze状態にする仕組みです。
-
freeze状態を制御するために、プロセス構造体内のフラグにセットできる3つのフラグビットがあります。include/linux/sched.hに以下3つの定義があります。
定義名 | 意味 |
---|---|
PF_NOFREEZE | システムのPower modeがsuspendまたはhibernationの際に該当プロセスをfreeze状態にしない |
PF_FROZEN | このプロセスは既にfreeze状態 |
PF_FREEZER_SKIP | 該当プロセスをfreeze状態にしない。PF_NOFREEZEとの違いは今のところ不明... |
-
system_freezing_cntという変数もあり、これでfreeze移行の可否を決定します。この値はfreeze_processes()という関数経由で「freezeしたい」意思表示をして変えます。
-
freeze移行の意思表示をした場合、try_to_freeze_tasks()という関数が呼び出されます。これによって、全ユーザ空間プロセスに擬似シグナル(fake signal)が送出されます。また、このときカーネルスレッドを全て起こします(wake up)。
-
この擬似シグナルを受けたfreeze可能なユーザ空間プロセスは、try_to_freeze()呼び出しにより応答(react)しなければいけません。この応答によって、該当プロセスはfreeze状態に遷移します。
-
ユーザスレッド自信はtry_to_freeze()呼び出しについて特に意識する必要はありません。しかし、カーネルスレッドの場合、必要であれば各スレッドごとに明示的にfreeze状態遷移のコードを書く必要があります。
-
freeze状態を設けた目的は、「hibernationやるときにファイルシステムにアクセスされたくない」「hibernation結構RAM使うので、邪魔なのどいてくれ」「カーネルスレッドやデバイスドライバが、suspend途中もしくはsuspend後のドライバに干渉してほしくない」といったところです。(1番目と3番目は結構シビアです。)
何となく実装のイメージがつかめたのではないでしょうか。
まずは、main.cのfreeze系の処理をみる
要約に書かれているtry_to_freeze_tasks()関数を見ます。
少し長いので、まず前半を見たいと思います。
/*
* Timeout for stopping processes
*/
unsigned int __read_mostly freeze_timeout_msecs = 20 * MSEC_PER_SEC;
static int try_to_freeze_tasks(bool user_only)
{
struct task_struct *g, *p;
unsigned long end_time;
unsigned int todo;
bool wq_busy = false;
struct timeval start, end;
u64 elapsed_msecs64;
unsigned int elapsed_msecs;
bool wakeup = false;
int sleep_usecs = USEC_PER_MSEC;
do_gettimeofday(&start);
end_time = jiffies + msecs_to_jiffies(freeze_timeout_msecs);
if (!user_only)
freeze_workqueues_begin();
while (true) {
todo = 0;
read_lock(&tasklist_lock);
for_each_process_thread(g, p) {
if (p == current || !freeze_task(p))
continue;
if (!freezer_should_skip(p))
todo++;
}
read_unlock(&tasklist_lock);
/* 略 */
スレッドごとのforeachで呼び出されているfreeze_task()が重要な役割を担っていそうです。freeze_task()を見ましょう。
bool freeze_task(struct task_struct *p)
{
unsigned long flags;
/*
* This check can race with freezer_do_not_count, but worst case that
* will result in an extra wakeup being sent to the task. It does not
* race with freezer_count(), the barriers in freezer_count() and
* freezer_should_skip() ensure that either freezer_count() sees
* freezing == true in try_to_freeze() and freezes, or
* freezer_should_skip() sees !PF_FREEZE_SKIP and freezes the task
* normally.
*/
if (freezer_should_skip(p))
return false;
spin_lock_irqsave(&freezer_lock, flags);
if (!freezing(p) || frozen(p)) {
spin_unlock_irqrestore(&freezer_lock, flags);
return false;
}
if (!(p->flags & PF_KTHREAD))
fake_signal_wake_up(p);
else
wake_up_state(p, TASK_INTERRUPTIBLE);
spin_unlock_irqrestore(&freezer_lock, flags);
return true;
}
途中で呼び出されているfreezing()とfrozen()については以下の実装です。
frozen()の中で、タスク構造体のフラグにPF_FROZENが立っているか調べていることを押さえてください。つまり、タスク構造体のフラグにPF_FROZENが立っている場合、そのタスクはfreezing状態であると言えます。
/*
* Check if a process has been frozen
*/
static inline bool frozen(struct task_struct *p)
{
return p->flags & PF_FROZEN;
}
extern bool freezing_slow_path(struct task_struct *p);
/*
* Check if there is a request to freeze a process
*/
static inline bool freezing(struct task_struct *p)
{
if (likely(!atomic_read(&system_freezing_cnt)))
return false;
return freezing_slow_path(p);
}
続いて、freezingの対象がカーネルスレッドの場合とユーザスレッドの場合とで処理を分けている箇所があります。先ほどのDocumentation要約のとおりです。
ユーザスレッドの場合では、fake_signal_wake_up()を呼び出します。実装は以下のとおりです。
static void fake_signal_wake_up(struct task_struct *p)
{
unsigned long flags;
if (lock_task_sighand(p, &flags)) {
signal_wake_up(p, 0);
unlock_task_sighand(p, &flags);
}
}
また、signal_wake_up()の実装は以下のとおりです。
static inline void signal_wake_up(struct task_struct *t, bool resume)
{
signal_wake_up_state(t, resume ? TASK_WAKEKILL : 0);
}
更に続いてsignal_wake_up_state()を見ましょう。
/*
* Tell a process that it has a new active signal..
*
* NOTE! we rely on the previous spin_lock to
* lock interrupts for us! We can only be called with
* "siglock" held, and the local interrupt must
* have been disabled when that got acquired!
*
* No need to set need_resched since signal event passing
* goes through ->blocked
*/
void signal_wake_up_state(struct task_struct *t, unsigned int state)
{
set_tsk_thread_flag(t, TIF_SIGPENDING);
実は、ここが後に重要になります。シグナルを送出してはいませんが、タスク構造体に「ペンディングされているシグナルがある」という印をつけておきます。
ちなみに、TIF_SIGPENDINGの「TIF」はThread InFormation」の略らしいです。この値はCPUアーキテクチャ依存(arch)の下に定義があるが、名前の通り「Signal Pending」のようです。
signal_wake_up_state()の続きを読みます。
/*
* TASK_WAKEKILL also means wake it up in the stopped/traced/killable
* case. We don't check t->state here because there is a race with it
* executing another processor and just now entering stopped state.
* By using wake_up_state, we ensure the process will wake up and
* handle its death signal.
*/
if (!wake_up_state(t, state | TASK_INTERRUPTIBLE))
kick_process(t);
}
wake_up_state()の実装は以下のとおりです。単にtry_to_wake_up()を呼んでいます。
int wake_up_state(struct task_struct *p, unsigned int state)
{
return try_to_wake_up(p, state, 0);
}
ここまでのところで、stateに渡される値はTASK_INTERRUPTIBLEです。
以下の実装を読みますが、単にスレッドを起こす(スレッドを実行可能状態にする)ような実装に見えます。ここまでのところで誰もfreezingにしていないように見えます。
/**
* try_to_wake_up - wake up a thread
* @p: the thread to be awakened
* @state: the mask of task states that can be woken
* @wake_flags: wake modifier flags (WF_*)
*
* Put it on the run-queue if it's not already there. The "current"
* thread is always on the run-queue (except when the actual
* re-schedule is in progress), and as such you're allowed to do
* the simpler "current->state = TASK_RUNNING" to mark yourself
* runnable without the overhead of this.
*
* Return: %true if @p was woken up, %false if it was already running.
* or @state didn't match @p's state.
*/
static int
try_to_wake_up(struct task_struct *p, unsigned int state, int wake_flags)
{
/* 略 */
cpu = task_cpu(p);
if (p->on_rq && ttwu_remote(p, wake_flags))
goto stat;
/* 略 */
ttwu_queue(p, cpu);
stat:
ttwu_stat(p, cpu, wake_flags);
out:
raw_spin_unlock_irqrestore(&p->pi_lock, flags);
return success;
}
一体誰がスレッドをfreezingにしているのでしょうか。
それは次回。