sysfsの実際

8月9日に、3回目のカーネル2.6テストバージョンである、カーネル2.6.0-test3がリリースされた。ChangeLogを見てもわかるように、今回のリリースでの修正点は多く、またsysfs関連についてもバグフィックスや、ドライバの対応が行われている。

カーネル2.6では、sysfsを使ったインタフェイスが推奨される一方で、従来のprocfsを使用したコードもサポートされるので、各デバイスごとのドライバのsysfsへの対応はあまり進んでいない。この事がかえって混乱を招いている例もある。sysfsへの対応には、どのくらいの作業が必要なのであろうか。今回は、sysfsの具体的なコード例を示しながら、対応方法について考えてみる。

カーネル2.6の問題点

Changelogに書かれているように、カーネル2.6.0-test3(以下test3)でいくつかの基本的なドライバがさらにsysfsに対応した。例えばVideo4Linux(V4L)のドライバもその一つである。しかし、この対応によりV4L関連のデバイスが一部使えなくなってしまったのも事実である。V4Lのドライバが従来のprocfsインタフェイスを捨てて、今回sysfsに変更したわけだが、その際に他のいくつかのデバイスドライバが行っていたようなアプローチはとらなかった。つまり、従来使用していたprocfsのインタフェイスを互換性維持のためには残さずに削除して、全面的にsysfsへ移行した。

これは、コードをよりシンプルにして、新しいアーキテクチャに統一していくという意味では正しい選択かも知れないが、V4Lのようなサードパーティーが開発しているデバイスドライバと密接な関係を持つモジュールでは、問題を起こしてている。

このtest3でのV4Lの変更の結果、usbインタフェイスのov511をはじめとするいくつかのビデオカメラ・ドライバが、「make modules_install」によるインストールができず、利用できなくなった。参照していた外部変数「video_proc_entry」が、videodev.cから完全に削除されてしまったのが理由である。この問題はいずれ、書き換えされて直るのであろうが、このようなsysfsへの対応によるトラブルは、今後も増えていくのではないだろうか。

実は、カーネル開発チームが書くドライバと、カーネル開発チームと離れて開発されている、ボランティアやサードパーティ製のドライバでは、明らかにsysfsの導入の熱意に温度差がある。今回のsysfsの導入では、ドライバ・モデルの根幹が大きくかわるため、従来型のドライバとの共存を認めるという、移行のための緩やかでスムーズなアプローチを選択したため、仕方が無いかもしれない。古いデバイスドライバのメンテナンスのために時間を費やすことが難しい、ボランティアやサードパーティ、あるいはデバイス提供メーカでは、Linuxだけで利益を得ている訳ではないので、書き直さなくて動くものは、なるべく触りたくないのは当然であろう。

ポーティングのすすめ

カーネル・ディレクトリのDocumentation/driver-modelには、少量だが重要な、新しいドライバ・モデルに関する解説文書が格納されている。詳細は、そちらの文書を参照して頂く事として、その中にあるporting.txtでは、従来のドライバをsysfsに対応させるための、ポーティングの手順が記述されている。手順は、ステップ0からステップ7まであるが、具体例に乏しいので、実際の移行作業では、すでにsysfsに対応しているscsi_debug.cなどのコード等を各自で参照するしかないと思われる。

また、これとは別に、Linuxに関する開発情報提供サイト「LWN」では、sysfs導入のための「Avoiding sysfs surprises」という記事を提供しているので、興味がある方は参照してみるとよい。

実際のコード

バスに接続する物理デバイスのポーティングのためには、確かにporting.txtのステップに従ってソースを書き換える必要があるが、実際にsysfsで何が、どのようにできるかを知りたいプログラマにとって、この大がかりな手順は適当では無いかも知れない。

そのためここで紹介するのは、/sys/class以下に「my_class」という新しいクラスを作成して、その下に「my_device」という自分のデバイスを登録して、デバイスの名前情報と、hotplugで使用するmajor/minor番号の「dev」ファイルを提供しているコードの例である。test3の環境で、ローダブル・モジュールとして組み込んで動作確認を行ったが、hotplugの実装を始め、sysfsの動作確認以外に不要なコードは極力排除しているので、実際のドライバとして使用する場合には、注意をされたい。

#include 
#include 
#include 
#include 

#define MY_MAJOR 330
struct my_device
{
        struct device *dev;
        char name[32];
        int minor;
        struct class_device class_dev;
};

static struct class my_class = {
        .name    = "my_class",
};

static struct my_device my = {
        .name = "my_device_name",
        .minor = 0,
};

static ssize_t show_name(struct class_device *cd, char *buf)
{
        struct my_device *pmd = container_of(cd, struct my_device, class_dev);
        return sprintf(buf,"%.*s\n",(int)sizeof(pmd->name),pmd->name);
}

static ssize_t show_dev(struct class_device *cd, char *buf)
{
        struct my_device *pmd = container_of(cd, struct my_device, class_dev);
        dev_t dev = MKDEV(MY_MAJOR, pmd->minor);
        return sprintf(buf,"%04x\n",(int)dev);
}

static CLASS_DEVICE_ATTR(name, S_IRUGO, show_name, NULL);
static CLASS_DEVICE_ATTR(dev,  S_IRUGO, show_dev, NULL);

int register_my_device(void)
{
        struct my_device *pmd = &my;
        pmd->class_dev.class = &my_class;
        strlcpy(pmd->class_dev.class_id, "my_device", BUS_ID_SIZE);
        class_device_register(&pmd->class_dev);
        class_device_create_file(&pmd->class_dev,
                                 &class_device_attr_name);
        class_device_create_file(&pmd->class_dev,
                                 &class_device_attr_dev);
        return 0;
}

void unregister_my_device(void)
{
        class_device_unregister(&my.class_dev);
}

static int __init mydev_init(void)
{
        class_register(&my_class);
        register_my_device();
        return 0;
}

static void __exit mydev_exit(void)
{
        unregister_my_device();
        class_unregister(&my_class);
}

MODULE_LICENSE("GPL");
module_init(mydev_init);
module_exit(mydev_exit);

sysfsの制限

前項の例のように、sysfsでは/sys以下に配置するカテゴリ(subsystem=ディレクトリ)を

block bus cdev class devices firmware

と目的別に固定して、各カテゴリを操作するために、例えばclassカテゴリ用であれば

  • register_class()
  • class_device_register()
  • class_device_create_file()

のような専用カーネル・コールを用意している。逆に例えば、これらのclassカテゴリ用のカーネル・コールを使用しないで、classカテゴリ以下を操作する事はできない。

実はこのサンプル・プログラムを作る前に、/sys以下に独自のディレクトリを作成して、その下に適当なファイルやディレクトリを操作するローダブル・モジュールのテスト・プログラムを試みたが、kobject_add()とkset_ 関連 のカーネル・コールがエクスポートされていないために、Undefined Symbolが多発してうまくいかなかった。

つまりsysfsでは、目的に応じたファイル構成を持ったディレクトリ・ツリーの、秩序ある維持ができる仕組みを、カーネルとして提供している。これは、野放し状態になった/procの反省として、デバイスドライバのプログラマに対して、sysfsの利便性の提供とともに、少し窮屈なインタフェイスを押し付けている訳である。ドライバのインタフェイスのための仮想ファイル・システムが自由に使えないのは困ると感じるプログラマは、今まで通りに/procを使えばよい。procfsは今のところ無秩序なままで、無くならない事になっている。