Linux を使っている人なら おそらく知っているモジュールです。
簡単にいえば、カーネルに接ぎ木するように機能を増やしていくための仕掛け・機能を仕込んだファイル です。
Linuxは、OSとしての機能を全部カーネルに組み込んだOS(モノリシックカーネル)のOSですが、機能ごとにモジュールという形で分割し、必要なときに組み込むことを可能にしています。
必須な部分のみをカーネルにつけておくことで、使わない部分にメモリを提供する必要がなくなりますし、従来は必須だったカーネルの再構築をすることなく、モジュールの組み込みだけで各種デバイスを使えるようにできます。
(なぜか、標準のモジュールがうまく動かなかったり:設定が悪いだけ?:するときは、面倒なんでカーネルの再構築して、モジュールではなく、直接組み込むこともありますが。)
自分でハードウェアのためのデバイスドライバを書くときは、普通はモジュールとして書きます。利点は
モジュールは 特別な二つの関数を含むオブジェクトファイルです。
簡単にいえば、 "gcc -c" で出来る ".o" ファイルです。
それ以外なにも制限はありません。
ただ、普段 C 言語で使うような関数類はほとんど使えません。
ある程度は互換性のある関数が準備されているのみです。
それというのも、モジュールはカーネルと一体になって動作するものなので、普通のプログラムで使うことの出来る libc(glibc) は使えませんし、システムコール(printfなどの間接的に使うものを含む)も使えないためです。
かといって、文字列操作( string.h )が使えないと不便ですし、デバッグするには最低限 printf にあたるものくらいほしいところです。
このあたりはみなさんわかってらっしゃるのでちゃんと用意されています。
最低限必要な関数とは
// gcc -c moduletest.c -Wall -Wstrict-prototypes -O -pipe -m486 // -pipe: 中間ファイルをつくらず // -Wall -Wstrict-prototypes: 最大限警告をうるさく // -m486: 486 用に最適化 (386ではなく) #define MODULE #define __KERNEL__ #include <linux/module.h> // printk をつかうためだけ :-) #include <linux/kernel.h> // jiffies をつかうためだけ :-) #include <linux/sched.h> 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 除去 #です。 おそらく、このページを読みつつ、これを試した方は
% dmsg | tail : module being installed at 2964643274 module being removed at 2964644075 %
# 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
# tail -f /proc/kmsg <4>module being installed at 2964828902 <4>module being installed at 2964831370 <4>module being removed at 2964835041
また、モジュールがちゃんと組み込まれていると、
% cat /proc/modules moduletest 208 0 (unused)のように、 /proc/modules で確認できます。
#define MODULE #define __KERNEL__ #include <linux/module.h> 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 <linux/module.h> #include <linux/kernel.h> #include <linux/sched.h> 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 <EMP>var= 0 str=default</EMP> # insmod moduletest <EMP>var=10 str=auau</EMP> ./moduletest.o: init_module: Device or resource busy # dmesg | tail -1 module being installed at 2968837709 <EMP>var= 10 str=auau</EMP>となります。 insmod するときに "var=10" という ような形で指定します。
jiffies は linux/sched.h で unsigned long として定義されている変数で、Linux のタイマ割り込みの周期毎に1ずつ増えます。 普通のx86用のLinuxで1秒に100増えます( asm/param.h の HZ で規定)。
カーネル内部で時間を使う場合にもっとも広く使われている変数です。
LINUX_VERSION_CODE はカーネルバージョンを表す数値として linux/version.h で定義されています。 見た目、得たいの知れない10進数値ですが、16進数に直すと2桁づつのバージョンになります(131594→0x2020A→2.2.10(A))。
バージョン依存のコードを書くときはこれで場合分けします。 特に、2.0系と2.2系のLinuxどちらでも動作するようにする場合は必須です。
応用として、もちろんデバイスドライバの作成に進むわけですが、そのほかにカーネルしか知り得ない情報を引き出すのに、使うこともできます。今回の例は、じつは jiffies という普段目にしないカーネル情報の引き出しをしています。
というわけで、次にすすみましょう。