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

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()からつらつらと。

```c
	/* 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電源状態を行う。詳細は後述する。

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

	acline_read();
```

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

```c
	/*
	 * 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) {
```

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

```c
	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電源の状態を取得できるか確認する。

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

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

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

```c
#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電源の状態を取得することにある。

```c
	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への接続を試みているのは以下の箇所である。

```c
 #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)を優先する。