LoginSignup
3
3

More than 5 years have passed since last update.

FreeBSDのPower Management系実装について(その1:まずはpowerdから)

Posted at

FreeBSDにはpowerdというデーモンがいる。
この実装を通じて、省エネに関連するカーネル実装を読んでみたくなったので、メモを残す。
もちろん、一度に書ききれないので、複数回に分けて気の向くまま書きたいと思う。

powerdは、manによると「system power control utility」である。
もう少し詳しく言うには、manの以下文を引用すれば十分だと思う。

The powerd utility monitors the system state and sets various power control options accordingly.  It offers power-saving modes that can be individually selected for operation on AC power or batteries.

つまり、AC電源の状態とバッテリーの状態に応じて、動作時に指定されたpower-saving modeにシステムを移行させるデーモンということである。
該当する状態ごとに挙動は指定できるのだが、詳細はmanにゆずる。

今回は最初なので、powerdが何をやっているのか概要を把握するため、ユーザプロセス側の実装を参照し、次回以降powerdが用いているカーネル周りの機能を見ていくことにする。

ソースはusr.sbin/powerd/powerd.cである。
まずはmain()からつらつらと。

    /* Poll interval is in units of ms. */
    poll_ival *= 1000;

    /* (筆者追加)sysctl経由でもろもろのパラメータを取得してくる。*/
    if (sysctlnametomib("kern.cp_times", cp_times_mib, &len))
    if (sysctlnametomib("dev.cpu.0.freq", freq_mib, &len))
    if (sysctlnametomib("dev.cpu.0.freq_levels", levels_mib, &len))

次に、コメントのとおり、何を使ってAC電源の状態を取得するか決めて、1回目のAC電源状態を行う。詳細は後述する。

    /* Decide whether to use ACPI or APM to read the AC line status. */
    acline_init();

    acline_read();

次に、現在のCPU周波数が起動時に指定された条件を満たさない場合、CPU周波数をその条件を満たすような値に変更する。

    /*
     * If we are in adaptive mode and the current frequency is outside the
     * user-defined range, adjust it to be within the user-defined range.
     */

        if (sysctl(freq_mib, 4, &curfreq, &len, NULL, 0) != 0) {
    /* 条件を満たした場合にのみ以下のようにset_freq()を呼ぶ */
            if (set_freq(freqs[numfreqs - 1]) != 0) {

そして、デーモンらしく無限ループに突入する。

    for (;;) {
/* 略 */
        /* たぶん何がしかのイベントを待つか、低周期で起きる */
        select(nfds, &fdset, NULL, &fdset, &timeout);
/* 略 */
        /* AC電源の状態を取得 */
        acline_read();

/* 略 */
        /* Read the current frequency. */
        if (idle % 32 == 0) {
            if ((curfreq = get_freq()) == 0)
                continue;
            i = get_freq_id(curfreq, freqs, numfreqs);
        }
        idle++;
        /* Always switch to the lowest frequency in min mode. */
        if (mode == MODE_MIN) {
/* 略 */
                if (set_freq(freq) != 0) {

        /* Always switch to the highest frequency in max mode. */
        if (mode == MODE_MAX) {
/* 略 */
                if (set_freq(freq) != 0) {
/* 略 */
        /* Adaptive mode; get the current CPU usage times. */
        if (read_usage_times(&load)) {

/* 略 */
        /* 
         * ごちょごちょと次にセットすべきfreq(CPU周波数)の値
         * を計算する。
         * 複雑な計算なのと、計算アルゴリズムには興味なしなので、略。 
         */
/* 略 */
        j = get_freq_id(freq, freqs, numfreqs);
        if (i != j) {
            idle = 0;
        /* ここで計算したCPU周波数をセット */
            if (set_freq(freqs[j]))
}

main()をざっと見することで、何となくpowerdが何をしているか雰囲気はわかろうかと思う。

次に、acline_init()について書く。この関数の機能は主に、aclineの状態取得方式を決定することである。

まずはACPI経由でAC電源の状態を取得できるか確認する。

    if (sysctlnametomib(ACPIAC, acline_mib, &acline_mib_len) == 0) {
        acline_mode = ac_sysctl;

POWERPCアーキテクチャの場合、PMUACとかいうのが使えるか試す。でもこのあたりよくわからないので、少なくとも今回は書かない。(次回以降調べて書くかもです)

最後にAPMDEVデバイスドライバとをopenすることで、APM経由でAC電源状態を取得できそうか試す。

#define APMDEV      "/dev/apm"

    } else if ((apm_fd = open(APMDEV, O_RDONLY)) >= 0) {

この一連の操作により、変数acline_modeに「AC電源を取得する方法を示す値」が格納される。ac_sysctlかac_apm,ac_devd(後述)のいずれかになる。

次に、acline_read()を見る。
この関数の機能は主に、AC電源の状態を取得することにある。

    if (acline_mode == ac_acpi_devd) {
    /* 「devd_pipe」経由でAC電源の状態を取得できるのであればそこから状態を読む。読むのに失敗すると、読み取り方法をac_sysctlに変える。*/

            (ptr = strstr(buf, "system=ACPI")) != NULL &&
            (ptr = strstr(ptr, "subsystem=ACAD")) != NULL &&
            (ptr = strstr(ptr, "notify=")) != NULL &&
            sscanf(ptr, "notify=%x", &notify) == 1)
            acline_status = (notify ? SRC_AC : SRC_BATTERY);

    /* これら文字列処理を見る限り、一定の約束事に従い、電力モードをもらうのだと推定。*/

/* sysctlの場合は、以下の処理のように、sysctlでAC電源状態を読み取り、AC電源なのかバッテリー駆動かのいずれであるかを把握する */
            if (sysctl(acline_mib, acline_mib_len, &acline, &len,
            NULL, 0) == 0)
            acline_status = (acline ? SRC_AC : SRC_BATTERY);
        else
            acline_status = SRC_UNKNOWN;

/* そして、このあと、ac_sysctlの場合は、devdへの接続を試みる。現時点ではdevdが何者かは不明 */

ちなみに、devdへの接続を試みているのは以下の箇所である。

 #define DEVDPIPE   "/var/run/devd.pipe"
    if ((devd_pipe = socket(PF_LOCAL, SOCK_STREAM, 0)) < 0) {
    if (connect(devd_pipe, (struct sockaddr *)&devd_addr,

次回以降見ていくのは、以下4点になる予定。
(1)devdとは何か。
(2)sysctlで読んでいる対象物の実体について
(3)CPU周波数を操作していると思われる関数set_freq()について
(4)PMUACやAPMについて。ただし、これらはどちらかといえば「主流の」方式ではないのでまずは(1)〜(3)を優先する。

3
3
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
3
3