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

モジュールのMakefileとビルド(kbuild)

モジュールのソースは普通の gcc ではビルドできない。動作中のカーネルに付属するビルド環境(kbuild)を使い、専用の Makefile 経由で .ko を作る。Makefile には obj-m := hello.o とビルド対象を書き、make -C /lib/modules/$(uname -r)/build M=$(pwd) modules でカーネル側のルールを借りてコンパイルする。uname -r で現在のカーネルに合わせるのが要点だ。

Hello World のソースを書いたとして、いつもの gcc hello.c でビルドしたらどうなるだろう。

答えは「うまくいかない」だ。カーネルモジュールは、ユーザ空間で動く普通のアプリとはまったく別の世界の住人で、リンクする相手も参照するヘッダも、動作中のカーネルそのものに合わせなければならない。単独の gcc では、その整合を取れないのだ。

💡
ポイントモジュールは普通の gcc ではビルドできない。動作中のカーネルに付属する専用のビルド環境(kbuild)を借りる。

そこで使うのが、カーネルに付属するビルドの仕組みだ。これは kbuild と呼ばれ、モジュールをそのカーネルと正しく噛み合う形にコンパイルしてくれる。

私たちは kbuild の細部を全部知る必要はない。小さな Makefile を1枚用意して「このソースをモジュールとしてビルドしてほしい」と伝え、あとの面倒な作業はカーネル側のルールに任せる、という分業になる。

この回では、その Makefile に何を書き、make を実行したとき裏で何が起きているのかを追っていく。

🧾 Makefile に書く、たった一行の要 — obj-m

モジュール用の Makefile で、まず欠かせないのが obj-m という指定だ。

これは「モジュール(module)としてビルドする対象はこれだ」とカーネルのビルド機構に教えるための行で、たとえば hello.c から hello.ko を作りたいなら、拡張子を .o にした形で obj-m := hello.o と書く。

obj-m := hello.o ……hello.c をモジュールとしてビルドせよ、の宣言。生成物は hello.ko になる。

なぜ .c でも .ko でもなく .o なのか。ビルドはソース(.c)を一度オブジェクト(.o)にコンパイルし、それをモジュール(.ko)にまとめる、という段階を踏むからだ。obj-m はその中間の .o を指し、kbuild が残りの段階を引き受けて最終的な .ko まで仕上げてくれる。

この一行が、Makefile の心臓部だと考えてよい。

🔗 なぜ自分のディレクトリから、カーネルのビルドを呼ぶのか

obj-m だけでは、ビルドは始まらない。実際にコンパイルを走らせる指示が要る。

モジュールのビルドで使うのが、make -C /lib/modules/$(uname -r)/build M=$(pwd) modules という、少し込み入った呼び出しだ。分解して見ると腑に落ちる。

まず -C は「そのディレクトリへ移動してから make を実行せよ」という指定で、行き先はカーネルのビルド用ファイル一式が置かれた場所だ。つまり、自分のソースの場所ではなく、カーネル側の Makefile(ルールの本体)を使う。

次の M=$(pwd) が「ただし、ビルドしたいソースがあるのはこのディレクトリ(今いる場所)だ」と教える部分で、カーネルのルールを借りつつ、対象は自分のモジュールに向けさせる。

🔗
たとえカーネルの build ディレクトリは設備の整った工場。M= は「加工してほしい素材は、うちのこの棚にある」という指示だ。

最後の modules が「モジュールをビルドする」という作業目標の指定になる。この三点セットで、カーネルの整った環境を借りて、自分の .ko を作れるわけだ。

🎚 uname -r が要になる理由

上のパスに現れる $(uname -r) を、なぜわざわざ埋め込むのか。ここが後々のトラブルを避ける勘所だ。

カーネルのビルド用ファイルは、カーネルのバージョンごとに分かれたディレクトリに置かれている。uname -r は、いま動作中のカーネルのバージョン名を返すコマンドで、これを Makefile の中で使うと、常に「今このマシンで動いているカーネル」に対応するディレクトリを自動的に指せる。

💡
ポイントモジュールはビルドしたカーネルのバージョンと厳密に噛み合う。uname -r で「今動いているカーネル」に自動で合わせる。

なぜそこまで厳密に合わせるのか。モジュールは、ビルドしたときのカーネルのバージョンと厳密に噛み合っていないと、組み込もうとした瞬間にカーネルから拒まれてしまうからだ。バージョン違いのモジュールを受け付けないのは、食い違ったコードがカーネルに入り込んで暴走するのを防ぐための、安全のしくみでもある。

だから決め打ちの数字を書かず、uname -r で自動的に合わせておくのが定石になる。

⚙ make と書いたとき、実際に走るもの

毎回あの長い呼び出しを手で打つのは大変だ。そこで Makefile には all: というまとまり(ターゲット)を用意し、その中にさっきの make -C ... modules を書いておく。

こうしておけば、あとは自分のソースのあるディレクトリで make と打つだけで、Makefile がその中身を実行し、裏で長い呼び出しが走る。

ソースのある場所で make と打つ。裏でカーネルの build ディレクトリを使ったビルドが走り、hello.ko が生成される。

同じように、後始末をする clean: というまとまりも用意し、その中に modules ではなく clean を目標にした似た呼び出しを書いておけば、make clean でビルドの中間生成物をまとめて掃除できる。

うまくいけば、ソースと同じディレクトリに .ko ファイルが姿を現す。これが、insmod で組み込める、モジュールの実体だ。

🧭 ビルドが通らないとき、どこを見るか

モジュールのビルドは、環境が整っていないと最初の一回でつまずきやすい。慌てず順に見ていこう。

よくある原因は、カーネルのビルド用ファイル一式がそもそもマシンに入っていない場合だ。/lib/modules/$(uname -r) の下に build というディレクトリがあるかを確かめ、なければ、そのカーネルに対応する開発用のパッケージ(カーネルヘッダなど)を導入する必要がある。

コツビルドが通らないときは、まず /lib/modules/$(uname -r) の下に build があるかを見る。無ければヘッダ一式が足りていない。

また、Makefile 内のインデントがタブでなければ make はエラーを出すし、obj-m の対象名とソースのファイル名が食い違っていても正しくビルドされない。

この Makefile と kbuild の作法は、これから作るどのキャラクタ型ドライバでも同じ土台になる。一度組めれば、あとはソースを差し替えて使い回していける。

この項目に出てくる用語

kbuildけーびるど
カーネルに付属するモジュール用のビルドの仕組み。
obj-mおぶじぇくとえむ
Makefile でモジュールとしてビルドする対象を指定する記述。

関連コマンド

make

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