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 という名前にする)が書けたら、次はそれをビルドして、組み込める形に変える。ただ make すればいいわけではない。
モジュールのビルドには、いま動いているカーネルときちんと整合させるための専用の手順が必要で、そのために 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を点けるドライバも、スイッチやセンサを読むドライバも、結局は「初期化で機器を準備し、必要な処理を行い、終了で後始末をする」という、同じ骨格の上に作られていく。
いちばん最初に体へ入れたこのリズムが、やがてより複雑なキャラクタ型デバイスドライバへ進むときの、しっかりした足場になってくれる。
まずは「組み込めば動き、外せば止まる」という当たり前を、自分の手で再現してみるところから始めよう。