LoginSignup
5
8

More than 5 years have passed since last update.

Linux Power Management実装ちょっと読んでみる(もくもく会の記録)

Posted at

この間、Linuxもくもく会に行ってきました。

2点後悔しました。晩飯を事前に食べておけばよかったという点がひとつ、もうひとつはもっとバッテリーの持ちが良いノートPCを買うべきです。
MBA持つ人が増えつつあるのもわかります...。
まあ、それは措いて。もくもく会でソース読んだ記録を残します。

ACPI周り

組み込みとかでこのあたりに関わった人は、色々と面倒なことになります(笑)。ちょっとバグるとストールする、まさにスペランカーのような世界が展開されます。
私は職業柄NetBSDがメインですが、Linuxはどうなってるのかなー、と気になった次第。

キーワードで思いつくのは...Power Managementモードの一つ、S3 Stateをキーワードにソースの在り処を探ってみます。

eria@eria-thinkpad:~/source/linux-3.19 % grep -rIi "S3 state" *
arch/x86/include/asm/cpufeature.h:#define X86_FEATURE_NONSTOP_TSC_S3 ( 3*32+30) /* TSC doesn't stop in S3 state */
drivers/ide/Kconfig:      machines ACPI support is required to properly handle ACPI S3 states.
kernel/power/Kconfig:     suspend-to-RAM state (e.g. the ACPI S3 state).

kernel/powerの下を覗けば良さそうです。

eria@eria-thinkpad:~/source/linux-3.19/kernel/power % ls
Kconfig         console.c       poweroff.c      suspend.c       wakelock.c
Makefile        hibernate.c     process.c       suspend_test.c
autosleep.c     main.c          qos.c           swap.c
block_io.c      power.h         snapshot.c      user.c
eria@eria-thinkpad:~/source/linux-3.19/kernel/power % pwd
/usr/home/eria/source/linux-3.19/kernel/power

さっそくソースをチラ見しましょう。(書き忘れましたが、ソースのバージョンは、3.19を基にしています。)
まずは、最初に見ろといわんばかりの、main.cを見ます。

kernel/power/main.c
#ifdef CONFIG_PM_SLEEP

/* Routines for PM-transition notifications */

static BLOCKING_NOTIFIER_HEAD(pm_chain_head);

int register_pm_notifier(struct notifier_block *nb)
{
    return blocking_notifier_chain_register(&pm_chain_head, nb);
}
EXPORT_SYMBOL_GPL(register_pm_notifier);

int unregister_pm_notifier(struct notifier_block *nb)
{
    return blocking_notifier_chain_unregister(&pm_chain_head, nb);
}
EXPORT_SYMBOL_GPL(unregister_pm_notifier);

int pm_notifier_call_chain(unsigned long val)
{
    int ret = blocking_notifier_call_chain(&pm_chain_head, val, NULL);

    return notifier_to_errno(ret);
}

/* 略 */

/* If set, devices may be suspended and resumed asynchronously. */
int pm_async_enabled = 1;

static ssize_t pm_async_show(struct kobject *kobj, struct kobj_attribute *attr,
                 char *buf)
{
    return sprintf(buf, "%d\n", pm_async_enabled);
}

static ssize_t pm_async_store(struct kobject *kobj, struct kobj_attribute *attr,
                  const char *buf, size_t n)
{
    unsigned long val;

    if (kstrtoul(buf, 10, &val))
        return -EINVAL;

    if (val > 1)
        return -EINVAL;

    pm_async_enabled = val;
    return n;
}

suspendとresume、BSD系のACPI実装読んだ経験から、Power Managementモード遷移時に呼ばれるフック関数と推定。
(main.c読んで、グローバル変数の別コードからの直アクセスは個人的に気になった・・・)

次に来るのは、ifdefのラベル名からデバッグコードと思われるので、省略します。

kernel/power/main.c
struct kobject *power_kobj;


/**
 * state - control system sleep states.
 *
 * show() returns available sleep state labels, which may be "mem", "standby",
 * "freeze" and "disk" (hibernation).  See Documentation/power/states.txt for a
 * description of what they mean.
 *
 * store() accepts one of those strings, translates it into the proper
 * enumerated value, and initiates a suspend transition.
 */

show()は単なる情報表示と思われ、読んでもあまり面白くなさそうです。なので、次のstore()を読みます。

kernel/power/main.c
static ssize_t state_store(struct kobject *kobj, struct kobj_attribute *attr,
               const char *buf, size_t n)
{
    suspend_state_t state;
    int error;

    error = pm_autosleep_lock();
    if (error)
        return error;

    if (pm_autosleep_state() > PM_SUSPEND_ON) {
        error = -EBUSY;
        goto out;
    }

    state = decode_state(buf, n);
    if (state < PM_SUSPEND_MAX)
        error = pm_suspend(state);
    else if (state == PM_SUSPEND_MAX)
        error = hibernate();
    else
        error = -EINVAL;

 out:
    pm_autosleep_unlock();
    return error ? error : n;
}

つまり、stateがPM_SUSPEND_MAXなら、hibernate()が呼ばれ、そうでないのならpm_suspend()が呼ばれる実に簡単なコード。

今は措いて、main.cを先に進む。
コメントが長い。

kernel/power/main.c
/*
 * The 'wakeup_count' attribute, along with the functions defined in
 * drivers/base/power/wakeup.c, provides a means by which wakeup events can be
 * handled in a non-racy way.
 *

この記述で、LinuxのPower Management機能はkernelの機能だけでなく、ドライバが介在していそうなことがわかる。

kernel/power/main.c
 * If a wakeup event occurs when the system is in a sleep state, it simply is
 * woken up.  In turn, if an event that would wake the system up from a sleep
 * handled in a non-racy way.
 *
 * If a wakeup event occurs when the system is in a sleep state, it simply is
 * woken up.  In turn, if an event that would wake the system up from a sleep
 * state occurs when it is undergoing a transition to that sleep state, the
 * transition should be aborted.  Moreover, if such an event occurs when the
 * system is in the working state, an attempt to start a transition to the
 * given sleep state should fail during certain period after the detection of
 *
 * If a wakeup event occurs when the system is in a sleep state, it simply is
 * woken up.  In turn, if an event that would wake the system up from a sleep
 * state occurs when it is undergoing a transition to that sleep state, the
 * transition should be aborted.  Moreover, if such an event occurs when the
 * system is in the working state, an attempt to start a transition to the
 * given sleep state should fail during certain period after the detection of
 * the event.  Using the 'state' attribute alone is not sufficient to satisfy
 * these requirements, because a wakeup event may occur exactly when 'state'
 * is being written to and may be delivered to user space right before it is
 * frozen, so the event will remain only partially processed until the system is
 * woken up by another event.  In particular, it won't cause the transition to
 * a sleep state to be aborted.
 *
 * This difficulty may be overcome if user space uses 'wakeup_count' before
 * writing to 'state'.  It first should read from 'wakeup_count' and store
 * the read value.  Then, after carrying out its own preparations for the system
 * transition to a sleep state, it should write the stored value to
 * 'wakeup_count'.  If that fails, at least one wakeup event has occurred since
 * 'wakeup_count' was read and 'state' should not be written to.  Otherwise, it
 * is allowed to write to 'state', but the transition will be aborted if there
 * are any wakeup events detected after 'wakeup_count' was written to.
 */

超長いが、要するに「Wake -> sleepに遷移する途中のWakeupイベント...sleep遷移をアボートすべき」「Working state時にWakeupイベントが起きたら、sleepに遷移すべきでない」ということ。
要するにPower Managementの状態遷移時に、さらに状態遷移の要因となるイベントが起きたときのポリシーが述べられています。
ユーザがシステムを使うのだから、極力使えるような状態にすべし、というポリシーが読み取れますね。
コメントの続き読みます。

kernel/power/main.c
 * This difficulty may be overcome if user space uses 'wakeup_count' before
 * writing to 'state'.  It first should read from 'wakeup_count' and store
 * the read value.  Then, after carrying out its own preparations for the system
 * transition to a sleep state, it should write the stored value to
 * 'wakeup_count'.  

ここから伺えることは、「何らかのユーザプロセスが、Power Managementの制御に関わっている。そして、状態遷移のハンドリングはwakeup_countとstateを使ってユーザプロセスが行っているということ」が読み取れます。
仕様書がないソフトを読む際に、コメントは面倒臭がらずに読む。
特にOSSのコード内の長いコメントは残念SIerの「iに1を代入する」的なコメントと違い、何らかの重要な意味があると心得るところ、大事ですね。

kernel/power/main.c
static ssize_t wakeup_count_store(struct kobject *kobj,
                struct kobj_attribute *attr,
                const char *buf, size_t n)
{
/* 略 */
    if (sscanf(buf, "%u", &val) == 1) {
        if (pm_save_wakeup_count(val))
            error = n;
        else
            pm_print_active_wakeup_sources();
    }

基本は受け取ったsigned integerをwakeup_countというパラメータにセットするということね。これが何かは今後わかってくるでしょう。

kernel/power/main.c
#ifdef CONFIG_PM_AUTOSLEEP

このifdefで囲まれているコードは何だろうか。
やっていることは先に記載したshowとsetとあまり変わらない。
このような感じで、数個のifdefが並び、同じようなことが繰り返されている。

kernel/power/main.c
static struct attribute * g[] = {
    &state_attr.attr,
#ifdef CONFIG_PM_TRACE
    &pm_trace_attr.attr,
    &pm_trace_dev_match_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP
    &pm_async_attr.attr,
    &wakeup_count_attr.attr,
#ifdef CONFIG_PM_AUTOSLEEP
    &autosleep_attr.attr,
#endif
#ifdef CONFIG_PM_WAKELOCKS
    &wake_lock_attr.attr,
    &wake_unlock_attr.attr,
#endif
#ifdef CONFIG_PM_DEBUG
    &pm_test_attr.attr,
#endif
#ifdef CONFIG_PM_SLEEP_DEBUG
    &pm_print_times_attr.attr,
#endif
#ifdef CONFIG_FREEZER
    &pm_freeze_timeout_attr.attr,
#endif
    NULL,
};

これで同じような実装が続く謎が解けた。show()とset()を関数ポインタ使って呼び分けている。
これらI/Fをsysfsを用いてユーザランドに見せているのが、以下の初期化コードであると思われる。

kernel/power/main.c
static struct attribute_group attr_group = {
    .attrs = g,
};

struct workqueue_struct *pm_wq;
EXPORT_SYMBOL_GPL(pm_wq);

static int __init pm_start_workqueue(void)
{
    pm_wq = alloc_workqueue("pm", WQ_FREEZABLE, 0);

    return pm_wq ? 0 : -ENOMEM;
}

static int __init pm_init(void)
{
    int error = pm_start_workqueue();
    if (error)
        return error;
    hibernate_image_size_init();
    hibernate_reserved_size_init();
    hibernate_reserved_size_init();
    power_kobj = kobject_create_and_add("power", NULL);
    if (!power_kobj)
        return -ENOMEM;
    error = sysfs_create_group(power_kobj, &attr_group);
    if (error)
        return error;
    pm_print_times_init();
    return pm_autosleep_init();
}

sysfsは、以下のとおり、BSDのsysctlのようなものです。
http://ja.wikipedia.org/wiki/Sysfs

あと、kobjectも以下補足します。
http://wiki.bit-hive.com/north/pg/sysfs%A4%C8kobject
http://dev.ariel-networks.com/Members/ohyama/i-o-30b930b130e530fc30e9306e-sysfs-30a830f330c830ea306b306430443066306e307e30683081/

「sysfs のディレクトリファイルは kobject というデータ構造で表現され、各 kobject に属性 (attribute) 情報がぶら下がり、これがファイルという形でユーザに提供されています。」という記述から、先の推定は正しそうです。

Documentation

雰囲気は何となくつかめてきたが、更にそれを明確にしたいです。
そこで、先のコメント内で言及されていたDocumentationを読んでみます。

Documentation/power/states.txt
state:      Suspend-To-Idle
ACPI state: S0
Label:      "freeze"

これは、Linux内でサポートされているPower Stateの説明で、「Suspend-To-Idle」の説明です。ACPI stateはS0なので、ACPIから見ると省エネ状態ではありません。
つまり、Suspend-To-IdleはACPI全く関係ありません。

Documentation/power/states.txt
more energy to be saved relative to runtime idle by freezing user space and putting all I/O devices into low-power states

上記のfreezing user spaceの話はすごく気になります。単にidle-threadが動いてますよ的な話なのか、それともユーザランドのプロセスを本当に動けなく(=スケジューリングされなく)するのかはわかりません。

Documentation/power/states.txt
State:      Standby / Power-On Suspend
ACPI State: S1
Label:      "standby"

In addition to freezing user space and putting all I/O devices into low-power states, which is done for Suspend-To-Idle too, nonboot CPUs are taken offline and all low-level system functions are suspended during transitions into this state.

使ってないコアをoffline、low level system機能と止めるような記載があります。ブートに使われたコアのみを活かし、後のコアを停止するようにも見えます。

次は、S3 state。いわゆるSuspend-To-RAMで、平たく言えばCPUをOFFし、RAMをセルフリフレッシュモードといいうモードに遷移させ、RAMの内容を残すようにします。

あと、S4 stateはSuspend-To-Disk。RAMの内容をディスクに書いて電源を切るようなモードです。
ここに言及がある「firmware」や「another operating system」は現時点ではよくわかりません。

Documentation/power/states.txt
State:      Suspend-to-RAM
ACPI State: S3
Label:      "mem"

This state, if supported, offers significant power savings as everything in the system is put into a low-power state, except for memory, which should be placed into the self-refresh mode to retain its contents.  

State:      Suspend-to-disk
ACPI State: S4
Label:      "disk"

STD can be handled by the firmware or the kernel. If it is handled by the firmware, it usually requires a dedicated partition that must be setup via another operating system for it to use. Despite the inconvenience, this method requires minimal work by the kernel, since the firmware will also handle restoring memory contents on resume.

以下suspend-to-swapの仕組みのひとつである「swsusp」も気になります。特にswapとどのように統合され、違い(制約)は何かというところも興味深いです。

Documentation/power/states.txt
For suspend-to-disk, a mechanism called 'swsusp' (Swap Suspend) is used to write memory contents to free swap space. swsusp has some restrictive requirements, but should work in most cases. 

最後に、swsuspの制約回避のために、以下のようにsuspend-to-diskのほとんどをユーザプロセスにやらせる的な話もありますが・・・なんか魔窟的な匂いもします。

Documentation/power/states.txt
Alternatively, userspace can do most of the actual suspend to disk work, see userland-swsusp.txt.

最後に

Power Management系の処理は意外と文書が少ないような気がしたので、少しさわりを書きました。スキマ時間を見つけつつ読んでいきたいな、と思います。

5
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
8