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", ¬ify) == 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)を優先する。