モジュールをつくる

[| ]  最終更新: 2023/02/14 18:32:21

モジュール(module)とは?

モジュール

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 <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           除去
#
です。 おそらく、このページを読みつつ、これを試した方は printk などとprintf っぽいものがあるにも関わらず何も表示されていないことでしょう。 kterm などから実行しても画面には表示されません。 コンソール(テキスト)画面で実行すると、即表示されます。画面に出なくとも、あちこちに痕跡が残っています。 おそらく、確実なのは 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の値

init_moduleが0を返した場合正常に組み込まれると述べましたが、0以外の値を返すと

#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" という ような形で指定します。

べんりなもの

printk

printkprintfと同じような感覚でカーネル内で使用可能な関数です。 ただし、出力はカーネルのメッセージとしてなされるので、上で述べましたように コンソールに直接表示されたり、 dmsg, syslog, /proc/kmsg 経由でないと見えなかったりします。 それでも、出力できるだけ非常に便利です。 カーネル内は浮動小数点が使えないので、整数だけですが、%d %x %sなどが普通につかえます。

jiffies

jiffies linux/sched.h で unsigned long として定義されている変数で、Linux のタイマ割り込みの周期毎に1ずつ増えます。 普通のx86用のLinuxで1秒に100増えます( asm/param.h HZ で規定)。

カーネル内部で時間を使う場合にもっとも広く使われている変数です。

LINUX_VERSION_CODE

LINUX_VERSION_CODE はカーネルバージョンを表す数値として linux/version.h で定義されています。 見た目、得たいの知れない10進数値ですが、16進数に直すと2桁づつのバージョンになります(131594→0x2020A→2.2.10(A))。

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


まとめ

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

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

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



熊谷正朗 [→連絡]
東北学院大学 工学部 機械知能工学科 RDE
[| ]