モジュールをつくる
Linux を使っている人なら おそらく知っているモジュールです。 簡単にいえば、カーネルに接ぎ木するように機能を増やしていくための仕掛け・機能を仕込んだファイル です。 Linuxは、OSとしての機能を全部カーネルに組み込んだOS(モノリシックカーネル)のOSですが、機能ごとにモジュールという形で分割し、必要なときに組み込むことを可能にしています。 必須な部分のみをカーネルにつけておくことで、使わない部分にメモリを提供する必要がなくなりますし、従来は必須だったカーネルの再構築をすることなく、モジュールの組み込みだけで各種デバイスを使えるようにできます。
(なぜか、標準のモジュールがうまく動かなかったり:設定が悪いだけ?:するときは、面倒なんでカーネルの再構築して、モジュールではなく、直接組み込むこともありますが。)

自分でハードウェアのためのデバイスドライバを書くときは、普通はモジュールとして書きます。利点は

  • カーネルに直接組み込むためには、ドライバを書くだけではなく、その初期化ルーチンを読んでもらうようにカーネルのほうもいじらなければならず、面倒。
  • そもそも、組み込みにすると変更する度カーネルの再構築が必要。
  • 失敗しなければ(^^; 何度も組み込んだりはずしたり出来るのでデバッグが楽。
などです。 難点としては、DMAを使ったりするために、低いアドレスの物理的に連続したアドレスのメモリを確保しようとしても、ある程度システムが動き始めてからではうまくいかなくなるなどですが、普通は問題ないでしょう。
モジュールは 特別な二つの関数を含むオブジェクトファイルです。 簡単にいえば、"gcc -c" で出来る ".o"ファイルです。 それ以外なにも制限はありません。 ただ、普段 C 言語で使うような関数類はほとんど使えません。 ある程度は互換性のある関数が準備されているのみです。
それというのも、モジュールはカーネルと一体になって動作するものなので、普通のプログラムで使うことの出来る libc(glibc) は使えませんし、システムコール(printfなどの間接的に使うものを含む)も使えないためです。 かといって、文字列操作(string.h)が使えないと不便ですし、デバッグするには最低限 printf にあたるものくらいほしいところです。 このあたりはみなさんわかってらっしゃるのでちゃんと用意されています。

最低限必要な関数とはint init_module(void)void cleanup_module(void)です。 これらはモジュールの組み込みを行うinsmodと除去を行うrmmodの途中でよばれます。 int init_module(void)が0を返すと組み込みが行われます。

// gcc -c moduletest.c -Wall -Wstrict-prototypes -O -pipe -m486 // -pipe: 中間ファイルをつくらず // -Wall -Wstrict-prototypes: 最大限警告をうるさく // -m486: 486 用に最適化 (386ではなく) #define MODULE #define __KERNEL__ #include // printk をつかうためだけ :-) #include // jiffies をつかうためだけ :-) #include int init_module(void) { printk("module being installed at %lu\n",jiffies); return 0; } void cleanup_module(void) { printk("module being removed at %lu\n",jiffies); }; コメントにあるように gcc -c...でコンパイルしてください。 実際に組み込み・除去をしてみるには % gcc -c moduletest.c -Wall -Wstrict-prototypes -O -pipe -m486 % su rootじゃなきゃできません # insmod moduletest 組み込み # rmmod moduletest 除去 # です。 おそらく、このページを読みつつ、これを試した方は printk などとprintf っぽいものがあるにも関わらず何も表示されていないことでしょう。 kterm などから実行しても画面には表示されません。 コンソール(テキスト)画面で実行すると、即表示されます。画面に出なくとも、あちこちに痕跡が残っています。
  • dmesg を実行すると、最後にそれっぽいことが書いてある % dmsg | tail : module being installed at 2964643274 module being removed at 2964644075 %
  • /var/log/message に何かでてる # tail /var/log/messages : Mar 19 18:05:57 kumagai2 kernel: module being installed at 2964643274 Mar 19 18:05:58 kumagai2 kernel: module being removed at 2964644075
  • たまたま /proc/kmsgtail -fなんてしてたら なにかが出たりでなかったり # tail -f /proc/kmsg <4>module being installed at 2964828902 <4>module being installed at 2964831370 <4>module being removed at 2964835041
  • CTRL+ALT+F? を押して画面を切り替えてみたら、表示されていた。
おそらく、確実なのは dmesg です。 /var/log/messages/etc/syslog.confに設定されていないとでません。 /proc/kmsgは本来こういう目的のもののはずですが、なぜか確実にはでません。 観察するとsyslog と取り合いしてるような感じです。
(冗談でnice --20 tail -f /proc/kmsgすると表示されました(^^;。/var/log/message になにも出ないようなら、/proc/kmsgをみれば良いということでしょうか)

また、モジュールがちゃんと組み込まれていると、 % cat /proc/modules moduletest 208 0 (unused) のように、/proc/modulesで確認できます。 init_moduleが0を返した場合正常に組み込まれると述べましたが、0以外の値を返すと #define MODULE #define __KERNEL__ #include int init_module(void) { return 1; // でも -1 でもなんでも } void cleanup_module(void) { }; # insmod moduletest ./moduletest.o: init_module: Device or resource busy とエラーが返ります。 モジュールにすると便利なことに、組み込むときにパラメータを渡すことができる、というものがあります。 具体的には、整数値と文字列を渡すことができます。 動作はモジュール内の変数の初期値を書き換える、という形になります。 #define MODULE #define __KERNEL__ #include #include #include static int var=0; // 整数変数 static char *str="default"; // 文字列(ポインタ) #if LINUX_VERSION_CODE > 0x20115 // Linux 2.1.22 以降は登録が必要 MODULE_PARM(var, "i"); MODULE_PARM(str, "s"); #endif int init_module(void) { printk("module being installed at %lu var= %d str=%s\n",jiffies,var,str); return 1; // rmmod しないで済むように手抜き } void cleanup_module(void) { }; これを同じようにコンパイルして、insmodすると # insmod moduletest ./moduletest.o: init_module: Device or resource busy (注 init_module() で return 1; なのでエラーが返ってます) # dmesg | tail -1 module being installed at 2968823952 var= 0 str=default # insmod moduletest var=10 str=auau ./moduletest.o: init_module: Device or resource busy # dmesg | tail -1 module being installed at 2968837709 var= 10 str=auau となります。insmodするときに "var=10" という ような形で指定します。

printkprintfと同じような感覚でカーネル内で使用可能な関数です。 ただし、出力はカーネルのメッセージとしてなされるので、上で述べましたように コンソールに直接表示されたり、dmsg, syslog, /proc/kmsg経由でないと見えなかったりします。 それでも、出力できるだけ非常に便利です。 カーネル内は浮動小数点が使えないので、整数だけですが、%d %x %sなどが普通につかえます。 jiffieslinux/sched.hで unsigned long として定義されている変数で、Linux のタイマ割り込みの周期毎に1ずつ増えます。 普通のx86用のLinuxで1秒に100増えます(asm/param.hHZで規定)。

カーネル内部で時間を使う場合にもっとも広く使われている変数です。 LINUX_VERSION_CODEはカーネルバージョンを表す数値としてlinux/version.hで定義されています。 見た目、得たいの知れない10進数値ですが、16進数に直すと2桁づつのバージョンになります(131594→0x2020A→2.2.10(A))。

バージョン依存のコードを書くときはこれで場合分けします。 特に、2.0系と2.2系のLinuxどちらでも動作するようにする場合は必須です。

ここでは、ドライバをつくる前段階として、ドライバを組み込むための方法であるモジュールについて述べ、その作り方と簡単な例を示しました。 カーネルやinsmodは複雑なことをやっているのですが、つくるだけなら、このように非常に簡単です。

応用として、もちろんデバイスドライバの作成に進むわけですが、そのほかにカーネルしか知り得ない情報を引き出すのに、使うこともできます。今回の例は、じつは jiffies という普段目にしないカーネル情報の引き出しをしています。

というわけで、次にすすみましょう。