🐧 Linux 総合学習プラットフォーム
デバイスドライバ実装 ・ 上級

Hello World モジュールの流れ(概念)

最初のドライバ学習は、メッセージを出すだけの「Hello World モジュール」を作る流れで全体像をつかみます。C言語で初期化関数(module_init で登録)と終了関数(module_exit で登録)を書き、printk でログを出すのが骨格です。これを make でビルドすると .ko ファイルができ、insmod で組み込むと初期化関数が走って dmesg にメッセージが出ます。rmmod で外すと終了関数が走ります。「組み込み=初期化、取り外し=後始末」という対の構造を体感するのが目的です。

ドライバ学習の最初の一歩は、メッセージを出すだけの小さなモジュールを実際に作ってみることです。世間でよく使われる「Hello World」というお試しプログラムの、カーネルモジュール版だと考えてください。機能としてはログを1行出すだけですが、これを作って動かすことで、モジュール開発の全体像——書く、ビルドする、組み込む、ログを見る、外す——をひととおり一周できます。いきなりLEDやセンサの制御に挑むより、まずこの素朴な題材で「組み込みと取り外しがどう動くのか」を体で覚えるほうが、遠回りに見えて結局は確実です。ここでは具体的なコードの一行一行よりも、それぞれの段階で何が起きているのか、という流れのほうに重点を置いて見ていきます。

ソースコードの骨格

モジュールのソースコードは、普通のCプログラムとは形が違います。最大の違いは、main 関数が存在しないことです。代わりに用意するのが、組み込まれたときに一度だけ走る初期化関数と、取り外されるときに走る終了関数の2つです。そして、この2つの関数について「これが入口の初期化関数、これが出口の終了関数です」とカーネルに知らせる、登録のための記述を添えます。初期化関数を登録するのが module_init という記述、終了関数を登録するのが module_exit という記述です。さらに、このモジュールがどんなライセンスで配布されるのかを宣言する記述も付け加えます。骨格として必要なのはこれくらいで、あとは初期化関数の中に、ログを出す printk を1行入れておけば、記念すべき最初のモジュールとして十分に成立します。

__init と __exit の意味

初期化関数と終了関数には、それぞれ目印となる印を付けるのが習わしです。初期化関数に付ける __init という印は、「この関数は組み込みのときに一度使うだけなので、初期化が済んだら、その関数が占めていたメモリは解放してしまってよい」という意味を持ちます。終了関数に付ける __exit という印も、同じように後始末に関わる目印です。カーネル空間で使えるメモリは、システム全体で共有される貴重な資源なので、一度きりしか使わないコードをいつまでも抱えておかず、役目を終えたら手放す、という考え方が徹底されています。これらの印は、その方針を実現するための、カーネルへの気配りのようなものです。最初のうちは仕組みを細かく理解する必要はなく、「一度しか使わない関数だと宣言しておくと、カーネルがメモリを上手にやりくりしてくれるのだ」という程度に捉えておけば十分です。

ビルドして .ko を作る

ソースコード(たとえば hello.c という名前にします)が書けたら、次はそれをビルドして、組み込める形に変えます。モジュールのビルドには、いま動いているカーネルときちんと整合させるための専用の手順が必要で、そのために Makefile という指示書を一緒に用意します。この Makefile には、「このソースをモジュールとしてビルドせよ」という指定と、システムで動作中のカーネルに付属する、ビルド用のファイル一式を参照する記述を書きます。このファイル一式は、カーネルのバージョンごとに分かれたディレクトリの下に置かれているため、現在のバージョンを返す uname -r を Makefile の中で使って、正しいディレクトリを自動的に指すようにするのが定石です。準備ができたら、あとは make というコマンドを実行するだけです。すると、ソースがコンパイルされ、モジュールの実体である .ko ファイル(この例なら hello.ko)が出来上がります。アプリの実行ファイルと違い、できあがるファイルの拡張子が .ko になっているのが、うまくいったことの目印です。

組み込みと初期化

出来上がった hello.ko を、insmod を使ってカーネルに組み込みます。組み込みはシステム全体に関わる操作なので、sudo insmod hello.ko のように管理者権限を付けて実行します。組み込みが成功すると、登録しておいた初期化関数が自動的に呼び出されて走り、その中に書いた printk が動きます。そこで dmesg を見ると、printk が出したメッセージが、カーネルリングバッファに記録された形で表示されているのが確認できます。「モジュールを組み込んだら、まさにその瞬間に初期化関数が走る」という対応関係を、ここで初めて自分の目で実際に確かめられるわけです。この手応えが、ドライバ開発の最初の小さな達成感になります。

取り外しと後始末

動作の確認が済んだら、今度は rmmod を使ってモジュールを取り外します。sudo rmmod hello のように、拡張子を付けないモジュール名で指定するのが約束です。取り外すと、今度は登録しておいた終了関数のほうが自動的に呼び出されて走ります。終了関数の中にも printk で別れのメッセージを入れておけば、それが dmesg に現れ、終了処理が確かに動いたことを確認できます。ここで見えてくるのが、「組み込み=初期化、取り外し=後始末」という、きれいな対の構造です。組み込みのときに何かを準備したら、取り外しのときにはそれをきちんと片付ける——この対応関係を崩さないことが、後始末のしっかりしたドライバを作るための、基本の姿勢になります。

この流れが土台になる

Hello World モジュールを通してつかんだ一連の流れ——ソースを書き、make でビルドして .ko を作り、insmod で組み込んで初期化関数を走らせ、dmesg でログを確かめ、rmmod で外して終了関数を走らせる——は、これから先に作るどんなドライバでも変わらない共通の土台です。LEDを点けるドライバも、スイッチやセンサを読むドライバも、結局は「初期化で機器を準備し、必要な処理を行い、終了で後始末をする」という、同じ骨格の上に作られていきます。いちばん最初に体へ入れたこのリズムが、やがてより複雑なキャラクタ型デバイスドライバへ進むときの、しっかりした足場になってくれます。まずは「組み込めば動き、外せば止まる」という当たり前を、自分の手で再現してみるところから始めましょう。

この項目に出てくる用語

カーネルモジュールかーねるもじゅーる
あとからカーネルに着脱できる拡張機能。多くのドライバはこの形。
カーネル空間かーねるくうかん
カーネルやドライバが動く特権領域。アプリのユーザ空間とは分離される。
dmesgでぃーめっせーじ
カーネルが出すメッセージ(カーネルリングバッファ)を表示するコマンド/仕組み。

関連コマンド

insmodrmmoddmesguname

▶ 学習アプリでこの続きを学ぶ・演習する