Advent Calendar6日目です。はい。
どうもです。最近カーネルソースリーディング不足気味のakachochinです。
昨日は部屋の片付けなどで現実逃避家の用事どっぷりでした。そんなことより、Advent CalendarなんだからLinuxの話しろって?ごもっともです。
最近Power Managementのソースを読むことが多いのですが、その中でお手軽に読めそうなネタを見つけたので、お気楽に読んでみます。
バッテリー
ノートPCのようにバッテリーが搭載されているマシンでLinuxを動かした場合、/proc/acpi/batteryの下および/sys/devices/[ユーザ環境依存ディレクトリ]/power_supply/BAT0/alarmといったファイルがあります。
これらのファイルは、バッテリーの状態が読めるファイルであったり、alarm機能の設定をするためのファイルです。
(ここで、alarm機能の設定とは、「イベント通知のトリガとなるバッテリー残量のしきい値の設定ができる」(後述)という意味です。)
例えば、/proc/acpi/battery/BAT0の下にあるファイルを読んだ結果は以下のようになります。
eria@eria-ThinkPad-X201 /proc/acpi/battery/BAT0
% pwd
/proc/acpi/battery/BAT0
eria@eria-ThinkPad-X201 /proc/acpi/battery/BAT0
% ls
alarm info state
eria@eria-ThinkPad-X201 /proc/acpi/battery/BAT0
% cat alarm
alarm: 187 mAh
eria@eria-ThinkPad-X201 /proc/acpi/battery/BAT0
% cat info
present: yes
design capacity: 5200 mAh
last full capacity: 3478 mAh
battery technology: rechargeable
design voltage: 10800 mV
design capacity warning: 173 mAh
design capacity low: 18 mAh
cycle count: 0
capacity granularity 1: 1 mAh
capacity granularity 2: 1 mAh
model number: 42T4648
serial number: 43258
battery type: LION
OEM info: LGC
eria@eria-ThinkPad-X201 /proc/acpi/battery/BAT0
% cat state
present: yes
capacity state: ok
charging state: charging
present rate: 882 mA
remaining capacity: 2806 mAh
present voltage: 12532 mV
このあたり、ソースコードではどう実装されているのでしょうか。ちょっと読んで見るには良さそうなネタですね。
ソースを読む
まず、先の読出結果から適当な文字列をピックアップして文字列検索します。
すると、drivers/acpi/battery.c に表示処理があることがわかります。
ソースをざっと見ましょう。ソースファイルの下の方にmodule_initやmodule_exitがあるので、ドライバモジュールであることが分かります。
static int __init acpi_battery_init(void)
{
if (acpi_disabled)
return -ENODEV;
async_cookie = async_schedule(acpi_battery_init_async, NULL);
return 0;
}
/* 略 */
module_init(acpi_battery_init);
特にドライバの場合、全体的な概要をつかむために、初期化処理を読むことは有効です。
static void __init acpi_battery_init_async(void *unused, async_cookie_t cookie)
{
int result;
dmi_check_system(bat_dmi_table);
#ifdef CONFIG_ACPI_PROCFS_POWER
acpi_battery_dir = acpi_lock_battery_dir();
if (!acpi_battery_dir)
return;
#endif
result = acpi_bus_register_driver(&acpi_battery_driver);
#ifdef CONFIG_ACPI_PROCFS_POWER
if (result < 0)
acpi_unlock_battery_dir(acpi_battery_dir);
#endif
}
acpiが大きく関わっているようです。
ACPIとは
ACPIの規格書の最初に、簡潔に書かれています。
The Advanced Configuration and Power Interface (ACPI) specification was developed to establish
industry common interfaces enabling robust operating system (OS)-directed motherboard device
configuration and power management of both devices and entire systems.
PC上のデバイスなどのリソースを統一的なインタフェースで扱えるようにするための仕組みです。
特にデバイスなどの差異をOSで吸収したり、抽象化したりするのは非常に面倒です。また、デバイスの扱い、特に省エネ系処理は、魔界極めて面倒で難しい処理になります。
そうしたところをできる限り抽象化して少しでもシステム開発や管理を楽にする目的で作られています。
Wikipediaの記事や規格書、ネットで公開されているスライドが参考になります。
なお、Linuxでは、acpiバスドライバで機能が提供されています。(dev/acpi/bus.c)
acpiバスドライバ自体、結構なボリュームで規格書なしに読むことは無理だと言ってもいいでしょう。なので、今回は深入りしません。
バッテリーの処理に戻ります
procfs上のファイルなどが持つ機能から、大きく分けて2つの機能があります。
(1)バッテリーの状態を読み出す
(2)バッテリーの残量が一定レベルを下回る/上回るときにイベントを通知する
まず、(1)を見ましょう。
バッテリーの状態を読み出す
drivers/acpi/battery.cのacpi_battery_print_info()やacpi_battery_print_state()でパラメータの表示をしています。
コンソール出力の処理が多いため、ソースは引用しません。
では、バッテリーに関する情報をどこから読んでくるのでしょうか。実は、これら関数が呼ばれる前に、acpi_battery_update()が呼ばれています。
static int acpi_battery_update(struct acpi_battery *battery, bool resume)
{
int result, old_present = acpi_battery_present(battery);
result = acpi_battery_get_status(battery);
acpi_battery_get_status()の中からさらに複数の関数の呼び出しを経て、以下の処理に行き着きます。
acpi_status acpi_bus_get_status_handle(acpi_handle handle,
unsigned long long *sta)
{
acpi_status status;
status = acpi_evaluate_integer(handle, "_STA", NULL, sta);
if (ACPI_SUCCESS(status))
return AE_OK;
if (status == AE_NOT_FOUND) {
*sta = ACPI_STA_DEVICE_PRESENT | ACPI_STA_DEVICE_ENABLED |
ACPI_STA_DEVICE_UI | ACPI_STA_DEVICE_FUNCTIONING;
return AE_OK;
}
return status;
}
acpi_evaluate_integer(handle, "_STA", NULL, sta)とは何でしょうか。実はacpi系のドライバを理解する上でメソッドという概念が出てきます。
メソッドはオブジェクト指向プログラミングのメソッドとかなり近い概念です。
先に、「統一的なインタフェース」と書きました。デバイスの差異を吸収して抽象的なI/Fを呼び出すことで様々な機能を実現できるようなI/Fが用意されています。今回の処理では、_STAがメソッド名になります。
ACPIの規格書では、バッテリー関連のメソッドは「10.2.2 Battery Control Methods」に載っています。_STAを見ると、確かに状態を読み出す旨のことが書かれていますね。
ACPIの処理でアンダースコア付きのアルファベット3文字が出てきたら、メソッドの可能性をまず疑ってください。
イベントについて
では、次に
(2)バッテリーの残量が一定レベルを下回る/上回るときにイベントを通知する
機能について読んでみましょう。
イベント通知処理は、関数の直呼びでなく、関数ポインタを通じて間接的に事前登録した関数を呼び出すことがほとんどです。
そして、この手の関数ポインタは、何らかのフレームワークに引き渡す構造体内であらかじめ記述することが多いです。
こうした視点でソースを眺めると、以下の構造体を見つけられるでしょう。
static struct acpi_driver acpi_battery_driver = {
.name = "battery",
/* 略 */
.ops = {
.add = acpi_battery_add,
.remove = acpi_battery_remove,
.notify = acpi_battery_notify,
},
.drv.pm = &acpi_battery_pm,
};
acpi_battery_notifyというそれらしい関数があるようです。
static void acpi_battery_notify(struct acpi_device *device, u32 event)
{
/* 略 */
acpi_battery_update(battery, false);
acpi_bus_generate_netlink_event(device->pnp.device_class,
dev_name(&device->dev), event,
acpi_battery_present(battery));
acpi_notifier_call_chain(device, event, acpi_battery_present(battery));
/* 略 */
notifierはLinux Nofifier Chainというカーネル内モジュールが使うイベント通知の仕組みで、このページが参考になります。
また、netlinkはこのページが参考になります。Linux Notifier Chainと似てますが、ネットワーク的な何かを通して、モジュール間で通知しあう仕組みのようです。そして、モジュールはカーネル内のみでなく、ユーザプロセスでも問題なく通知を受け取れます。
つまり、一定の手続きを踏むことで、ユーザプロセスもバッテリーのイベントを知ることができます。
こうした規模の小さなドライバでも、ソースを読むことでLinuxが提供する諸々のフレームワークとその具体例を知ることができます。
イベントが起きたことをこのドライバはどう知るのか
先に書いたことは「イベントが起きたあとどうする」でした。イベントが起きたことをどう知るのかが重要です。
全部書くと、結構な分量になるので要点を書いていきます。
(1)先に書いたacpi_battery_update()では、必要な場合にacpi_battery_init_alarm()を呼び出します。
この関数を経由して、ACPIメソッドの「_BTP」が呼ばれます。_BTPは、「Sets the Battery Trip point, which generates an SCI when battery capacity reaches the specified point.」です。
static int acpi_battery_set_alarm(struct acpi_battery *battery)
{
/* 略 */
status = acpi_execute_simple_method(battery->device->handle, "_BTP",
battery->alarm);
/* 略 */
}
static int acpi_battery_init_alarm(struct acpi_battery *battery)
{
/* 略 */
return acpi_battery_set_alarm(battery);
}
つまり、「バッテリー残量が一定の値になったところで割り込みを挙げるようにお願いする」です。
ちなみに、先の英文内のSCIは割り込みです。
System Control Interrupt (SCI)
A system interrupt used by hardware to notify the OS of ACPI events. The SCI is an
active, low, shareable, level interrupt.
(2)イベント発生時にacpi_battery_notify()が呼ばれるようにするために、acpi_battery_init_async()で以下の処理を行います。
static void __init acpi_battery_init_async(void *unused, async_cookie_t cookie)
{
/* 略 */
result = acpi_bus_register_driver(&acpi_battery_driver);
/* 略 */
}
acpi_battery_init_asyncはモジュールのロード後の適当なタイミングで非同期に呼ばれます。
acpi_bus_register_driver()にacpi_battery_driver構造体を引き渡すことで、先に紹介した関数ポインタもacpiバスドライバに登録されます。
(3)SCI割り込み発生時に、acpiバスドライバ内関数acpi_bus_notify()が呼ばれます。そのときにバッテリーのドライバが登録したnotify関数ポインタを呼び出します。
/**
* acpi_bus_notify
* ---------------
* Callback for all 'system-level' device notifications (values 0x00-0x7F).
*/
static void acpi_bus_notify(acpi_handle handle, u32 type, void *data)
/* 略 */
driver = adev->driver;
if (driver && driver->ops.notify &&
(driver->flags & ACPI_DRIVER_ALL_NOTIFY_EVENTS))
driver->ops.notify(adev, type);
ちなみに、acpi_bus_notify()はacpiバスドライバの初期化時の以下処理で登録します。
static int __init acpi_bus_init(void)
{
/* 略 */
/*
* Register the for all standard device notifications.
*/
status =
acpi_install_notify_handler(ACPI_ROOT_OBJECT, ACPI_SYSTEM_NOTIFY,
&acpi_bus_notify, NULL);
/* 略 */
これ以上acpiを下に降りて行くと魔界小一時間で終わらないのでこの辺にします。
最後に
バッテリーのドライバ自体は1000行ちょいの小規模なソースでした。しかし、ACPI規格の概要・使われ方やLinuxが用意するフレームワークについても知ることができます。得られるものは結構大きいですね。
カーネルのソースを読むにはどこから始めたら良いのか、聞かれることがたまにあります。カーネルのソースを読む目的にも依るのですが、今回の記事のように、カーネルの小規模なドライバのソースを読むということも一つの方法ではないでしょうか。
それでは、Happy Kernel Hacking!