TSG 部報 第 200 号・夏休み号


Linux お気楽・極楽デバイスドライバ

Sumii *0

1. はじめに

 いまさら説明するまでもないと思いますが、LinuxというのはLinus Torvaldsさんが中心になって作られたフリーなPC UNIXです。今日はそのLinux用のデバイスドライバを作る方法の概略を紹介したいと思います。
 DOSならともかくUNIX系OSのデバイスドライバを書くとなると、何だか難しいように思えるかもしれませんが、少なくともLinuxに関してはいたって簡単ですので、心配する必要はありません。LinuxとC言語に関する基本的な知識があれば十分理解できる内容です。
 なお、この文書が対象とするカーネルのバージョンは2.0.0以上です。それ未満のバージョンでも基本的なところは同じですが、細かい点が微妙に異なるので、そのままではうまくいかない可能性があります。

2. とりあえずやってみる

 いろいろ説明する前に、まずは実際に例を試してみることにしましょう。次のソースを入力して「counter.c」というファイル名でセーブしてください。短いプログラムですから、たとえ手で入力してもすぐに終わりますよね。

 入力できたら、以下の操作を行ってコンパイルと組み込みをします。rootでないと実行できないコマンドも含まれていますが、この文章をお読みのみなさんはもちろんrootでしょう。:-)

 うまくいったら「cat /dev/counter」を繰り返し実行してみてください。どうですか、おもしろいでしょう?(つまらなかったらごめんなさい。)
 「どこか『デバイス』ドライバなんだ」という話もありますが、基本的な枠組みは本当のデバイスドライバでも同じですので、以下はこれを例として説明していきたいと思います。このように必ずしも実際のハードウエアを伴わないデバイスを仮想デバイスといいます(やや嘘)。

3. 今やったのは何?

 さて、ほとんど何の説明もせずに、いきなり実際にドライバをコンパイルして組み込んでみました。でも、これではわけがわかりません。先ほどの操作は、いったいどういう意味だったのでしょうか。順を追って説明していきます。

 counter.cをコンパイルして、counter.oを作ります。
 ポイントは、オブジェクトファイルのみを作って、実行ファイルは作らないという点です。実はこのドライバは「installable kernel module」という形式をとっています。「installable kernel module」というのは、カーネルを再構築したり、システムを再起動したりせずに、カーネルに新しい機能を追加するものです。ダイナミックリンクライブラリと似ていますが、ユーザプログラムが使用するライブラリではなく、カーネルに組み込まれるモジュールであるという点が異なります。あくまでモジュールなので、実行ファイルではなくオブジェクトファイルなのです。

 たったいま作成したモジュールを組み込みます。現在どういうモジュールが組み込まれているかは「lsmod」というコマンドでわかります。モジュールを削除するには「rmmod」というコマンドを使います。

 counterのためのデバイスファイルを作成します。メジャー番号63番、マイナー番号0番のキャラクタデバイスです。
 メジャー番号は割り当てが決まっているので、本来勝手に使ってはいけないものです。割り当ては、カーネルソースに付属しているDocumentation/devices.txtに書かれています。ただし、それをよく読むと

とあるので、60〜63番はこのような実験に使っても良いのです。

4. ソースの説明

 必要なヘッダファイルをインクルードします。カーネルに組み込むモジュールであることを表すために、__KERNEL__およびMODULEを#defineします。この#defineはヘッダファイルの中で参照されているので、#includeより前でしなければなりません。

 モジュール内部で使用する変数を宣言します。
 先頭の「static」は重要です。組み込み時の名前衝突を回避するために、モジュールの外部から参照されない名前は、すべてstaticにしなければいけません。
 各変数の意味は後になればわかります。

 デバイスファイル(/dev/counter)がopenされると呼ばれる関数を用意しておきます。何やら二つほど引数がありますが、今回は関係ないので気にしません。(^^;
 一度に多数openされるとややこしいので、busyという変数を使って排他制御を行ないます。「これじゃあ排他制御になってないじゃん」と思うかもしれませんが、Linuxではカーネルモードのプロセスはプリエンプトされないので問題ありません(と思う)。
 MOD_INC_USE_COUNTは、モジュールが使用中であることを示す値を一つ増やします。この値が0でなければモジュールを削除できないので、誤って使用中のモジュールを削除してしまうおそれがなくなります。

 デバイスファイルがcloseされると呼ばれる関数を用意しています。MOD_DEC_USE_COUNTは、MOD_INC_USE_COUNTの逆です。これを忘れると、モジュールがずっと使用中になって、削除できなくなってしまいます。

 デバイスファイルがreadされたとき呼ばれる関数を用意します。このドライバの中核部です(笑)。現在のcounterの値を10進数の文字列に直し、それをユーザプロセスが用意したバッファにコピーします。
 ここで注意しなければならないのは、ユーザプロセスが用意したバッファはユーザメモリ空間にあるので、普通のポインタを使ったメモリ操作ではなく、専用のマクロ(memcpy_tofs)を使って書き込みを行うということです。また、不当な領域に書き込みを行わないよう、あらかじめverify_areaという関数で、渡されたアドレスの正当性をチェックしなければいけません。
 なお、ここで使っているsprintfとstrlenは(名前と意味は同じでも)決して普通のライブラリ関数ではなく、モジュールのためにカーネルがエクスポートしている関数、ないしはヘッダに含まれているマクロです。

 デバイスファイルを操作したとき呼び出される関数を登録するための構造体です。次で説明するデバイスの登録のとき使用します。lseekやらmmapやらいろいろなインターフェースが用意されていますが、使わないところはNULLで構いません。

 モジュールを組み込んだときに呼び出される関数です。したがって、この関数はstaticにしてはいけません。また、名前もこの通りにしなければなりません。
 register_chrdevで、メジャー番号63番のcounterという名前のデバイスを登録します。ちなみに、現在登録されているデバイスの一覧は「cat /proc/devices」で見られます。

 モジュールを削除したときに呼び出される関数です。init_moduleと同様に、staticにしてはならず、名前も変えてはいけません。
 init_moduleで登録したデバイスを、unregister_chrdevで忘れずに削除しておきます。

5. モジュールのための関数とマクロ

 counterの説明は以上です。簡単ですね。でも、実際に何かのボードのデバイスドライバを書こうとしたら、さすがにこれだけではすみません。I/Oアクセスを行なったり、物理メモリやIRQを確保したりするにはどうすればいいのでしょうか?
 実は先ほどのsprintfやstrlenのように、カーネルがモジュールのために提供している関数や、ヘッダに含まれているマクロがたくさんあります。カーネルが提供する関数については「ksyms -a」で一覧が見られます。I/Oアクセスやリソース確保を行うときには、これらの関数・マクロを使います。
 残念ながら、これらの関数・マクロの説明はあまりありません。一部の重要な関数・マクロについては、manpagesの9章(!)に書かれていますが、載っている数は決して多くない上に、情報も古いのであまりあてになりません。ヘッダやカーネル、ドライバのソースを見て理解するしかないでしょう。
 もちろんネットワーク上にも情報があります。あまりちゃんと探したわけではありませんが、筆者が知っている範囲では

が参考になるでしょう。

6. おわりに

 何だかいろいろと偉そうなことを書きましたが、筆者自身つい最近知ったことが多いので、ひょっとしたら間違っているところもあるかもしれません。もし誤りがあったらメールやitalkで教えていただけると幸いです。:-)



[目次へ]

g541119@komaba.ecc.u-tokyo.ac.jp