Linux Power Managementのカーネル実装(freeze)を読む(その1)

## 今回はfreezeを調べてみます
[前回](http://qiita.com/akachochin/items/af9380af00746f5ee5b9)はLinuxのPower Managementの導入部分を調べてみました。
今回は、

```Documentation/power/states.txt
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()関数を見ます。
少し長いので、まず前半を見たいと思います。

```c:kernel/power/process.c
/* 
 * 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()を見ましょう。

```c:kernel/freezer.c
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状態である**と言えます。

```c:include/linux/freezer.h
/*
 * 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()を呼び出します。実装は以下のとおりです。

```c:kernel/freezer.c
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()の実装は以下のとおりです。

```c:include/linux/sched.h
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()を見ましょう。

```c:kernel/signal.c
/*
 * 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()の続きを読みます。

```c:kernel/signal.c
    /*
     * 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()を呼んでいます。

```c:kernel/sched/core.c
int wake_up_state(struct task_struct *p, unsigned int state)
{
    return try_to_wake_up(p, state, 0);
}
```

ここまでのところで、stateに渡される値はTASK_INTERRUPTIBLEです。
以下の実装を読みますが、単にスレッドを起こす(スレッドを実行可能状態にする)ような実装に見えます。ここまでのところで誰もfreezingにしていないように見えます。

```c:kernel/sched/core.c
/**
 * 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にしているのでしょうか。
それは次回。