次のページ 前のページ 目次へ

13. モジュール

この章では、Linux カーネルが、ファイルシステムなどの機能を、必要な時だけ 動的にロードする仕組みについて説明する。

Linux は、モノリシックなカーネル(monolithic kernel)を使用している。すなわ ちそれは、単一で巨大なプログラムであり、そこでは、カーネルのあらゆる機能別 コンポーネントが、すべての内部データ構造やルーチンにアクセスできる。 それとは反対の仕組みとして、マイクロカーネル(micro-kernel)構造がある。その 場合、カーネルの機能別部分は、個別のユニットに分割され、ユニット間での厳格な 通信メカニズムが設定される。モノリシックカーネルでは、新規コンポーネントを カーネルに追加するには、その設定プロセスを経由するので、やや時間がかか る。たとえば、NCR 810 SCSI の SCSI ドライバを使いたいのだが、まだそれをカーネル に組み込んでいないとする。その際は、新しいカーネルを設定し、ビルドすることで、 はじめて NCR 810 が使用できるようになる。 しかし、違う選択肢も存在する。Linux では、必要に応じて、オペレーティング システムのコンポーネントを動的にロードしたり、アンロードしたりできる。 Linux カーネルモジュール(module)とは、システム起動後ならいつでも動的にカーネル にリンクすることができるコード群である。それらは、必要がなくなればカーネルとの リンクを解消して削除することができる。大部分の Linux カーネルモジュールは、 デバイスドライバや、ネットワークドライバなどの仮想ドライバ、あるいはファイル システムなどである。

Linux カーネルモジュールは、insmodrmmod コマンドを 使用して、明示的にロードとアンロードが可能である。また、カーネル自身も、 カーネルデーモン(kerneld)によって、モジュールのロードとアンロードを 必要に応じて行うことができる。必要なときに動的にロードできるコードというのは、 カーネルのサイズを最小に抑え、それに柔軟性を与えるという意味で非常に魅力的な 機能である。わたしの現在の Intel カーネルはモジュールを広範に使っているので、 406 K バイトで済んでいる。VFAT ファイルシステムはたまにしか使わないため、 VFAT パーティションがマウントされた時に自動的に VFAT ファイルシステムがロード されるように、わたしのカーネルはビルドされている。VFAT パーティションをアンマウ ントしたとき、システムはわたしがもはや VFAT ファイルシステムモジュールを必要と していないことを検出して、システムからそれを削除する。新しいカーネルコードを 試すときでも、試行のたびにカーネルをリビルドして再起動をかける必要がないので、 その点でもモジュールは便利である。しかし、その見返りとして、カーネルモジュール を使うと若干のパフォーマンスの低下とメモリの消費が起こる。ローダブルモジュール にするために提供しなければならないコードが少量あり、そのコードと付加的なデータ 構造とが加わるために、メモリの消費量がやや増加する。また、あるレベルで 間接処理が導入されるので、モジュールの場合、カーネルリソースへのアクセスが やや非効率になる。

Linux モジュールは、いったんロードされると、通常のカーネルコードと同様に カーネルの一部となる。すなわち、他のカーネルコードと同一の権利と責任を持つ。 言い換えると、Linux カーネルモジュールは、すべてのカーネルコードやデバイス ドライバと同様に、カーネルをクラッシュさせることもできる。

モジュールが必要なカーネルリソースを使用するためには、まずそれらのリソースを 見つけだす必要がある。たとえば、モジュールが kmalloc() やカーネルメモ リ割り当てルーチンを呼び出す必要がある場合などを考えてほしい。ビルドされた時点 では、モジュールは、メモリ内のどこに kmalloc() があるのかを知らない。 したがって、モジュールがロードされたとき、カーネルは、モジュールが動き出す前 に、モジュール内の kmalloc() への参照をすべて解決しなければならない。 カーネルは、カーネルのシンボルテーブル内にカーネルリソースのすべてのリストを保 持しているので、モジュールのロード時に、モジュールからのそうしたリソースへの参 照を解決することができる。 Linux では、モジュールをスタック化できる。すなわち、あるモジュールは、他の モジュールのサービスを前提とし、そのサービスを利用できる。たとえば、VFAT ファイ ルシステムモジュールは、FAT ファイルシステムのモジュールのサービスを必要とす る。VFAT ファイルシステムは、多かれ少なかれ FAT ファイルシステムの拡張であるか らである。他のモジュールのサービスやリソースを必要とするモジュールというのは、 モジュールがカーネルそのもののサービスやリソースを必要とする状況と非常に類似 している。必要とするサービスが、先にロードされた他のモジュールの中だけに あるからである。個々のモジュールがロードされると、カーネルは、カーネルシンボル テーブルを変更して、新しくロードされたモジュールがエクスポートしたリソースや シンボルのすべてをそこに付け加える。これが意味するのは、次のモジュールがロード されたとき、そのモジュールは既にロードされているモジュールのサービスにアクセス 出来るということである。

モジュールをアンロードしようとするとき、カーネルはそのモジュールが使用されて いないことを知る必要があり、モジュールにこれからアンロードされることを知らせる 方法をも必要とする。そして、その方法によって、モジュールは、カーネルから削除 される前に、カーネルメモリや割り込みなどの割り当てられたシステムリソースを 解放することが出来る。モジュールがアンロードされるとき、カーネルは、その モジュールがカーネルシンボルテーブルにエクスポートしたすべてのシンボルを 削除する。

適切に書かれなかったために、ロードされたモジュールはオペレーティングシステム をクラッシュさせることが出来るということの他にも、モジュールの使用は別の危険も もたらす。現在使用しているカーネルよりも、古いか新しいバージョンのカーネル用に ビルドされたモジュールをロードした場合、何が起こるだろうか?これが問題を 起こすのは、たとえばモジュールがカーネルルーチンをコールして、誤った引数を渡し た場合である。カーネルは、オプションとしてではあるが、モジュールのロード時に そのモジュールを厳格にバージョンチェックすることで、この問題が起こらないように している。

13.1 モジュールのローディング

図表(12.1)カーネルモジュールのリスト

カーネルモジュールをロードする方法はふたつある。ひとつは、insmod コマンドによって手動でカーネルに組み込む方法。もうひとつの、ずっと賢明なやり 方は、必要に応じてモジュールを組み込む方法である。これは、デマンドローディ ング(demand loading)と呼ばれる。ユーザがカーネル内に存在しないファイルシステム をロードする時など、モジュールの必要性をカーネルが感知したとき、カーネルは、 カーネルデーモン(kerneld)に適切なモジュールをロードするようリクエストする。

カーネルデーモンは、普通のユーザプロセスなのだが、スーパーユーザの権限を 持っている。通常はシステム起動時に実行が開始されるが、それが実行される と、カーネルデーモンはカーネルに対するプロセス間通信(IPC)のチャンネルを開く。 このリンクはカーネルによって使用され、kerneld に対して様々なタスク の実行を依頼するためのメッセージが送信される。
[see: include/linux/kerneld.h]
kerneld の主要な機能は、 カーネルモジュールのロードとアンロードだが、他のタスクの処理も可能である。 たとえば、必要なときにシリアル回線上で PPP リンクを開始し、必要がなくなったら それを切断するといったこともできる。kerneld 自身がそうしたタスクを 実行するのではなく、insmod などの必要なプログラムを起動してそれに 仕事をさせる。kerneld は、カーネルの代理人(agent)のような存在であり、 カーネルを代理して仕事のスケジューリングを行う。

insmod ユーティリティは、リクエストされたカーネルモジュールを 発見して、それをロードしなければならない。デマンドローディングでロードされた カーネルモジュールは通常 /lib/modules/kernel-version に保存される。 カーネルモジュールは他のプログラムと同じくリンクされたオブジェクトファイル なのだが、リロケータブル(relocatable)イメージとしてリンクされている点で異なる。 すなわち、特定の固定的なアドレスから実行されるようにリンクされたのではない イメージであるということである。それらは a.outELF か のいずれかのフォーマットを取る。insmod は、特権的なシステムコールを 発行して、カーネルによってエクスポートされたシンボルを見つけだす。
[see: sys_get_kernel_syms(), in kernel/module.c]
それらは、シンボル名と、そのアドレスなどの値とを含んだペアとして保存される。 カーネルがエクスポートしたシンボルテーブルは、カーネルが管理するモジュール のリストの最初の module データ構造体に保持 され、その構造体は、 module_list ポインタに よってポイントされる。
[see: include/linux/module.h]
カーネルのシンボルテーブルに追加されるのは、明示的に追加が指示されたシンボル だけである。シンボルテーブルは、カーネルのコンパイルとリンクがなされる時に 作成されるのだが、すべてのシンボルがモジュールにエクスポートされるわけでは ない。 たとえば、request_irq というシンボルがあるが、これは、ドライバが特定の システム割り込みを制御しようとする場合に呼び出されるカーネルルーチンである。 わたしの現在のカーネルでは、このシンボルは 0x0010cd30 という値を持っている。 エクスポートされたカーネルシンボルとその値を簡単に見るには、/proc/ksyms を見るか、ksyms ユーティリティを使えばよい。 ksyms ユーティリティを使えば、エクスポートされたすべてのカーネル シンボルを見ることも、ローダブルモジュールによってエクスポートされたシンボル だけを見ることもできる。insmod コマンドは、 モジュールを仮想メモリから読み出して、カーネルがエクスポートしているシンボル を使うことで、カーネルルーチンへの参照のなかでまだ解決されていない参照を 適宜解決(fixup)する。この解決は、メモリ内のモジュールイメージにパッチを当てる という形式を取っている。insmod は、シンボルのアドレスをモジュール内の 適切な場所に書き込む。

insmod によって、エクスポートされたカーネルシンボルに対するモジュー ルの参照が解決されると、insmod は、再度特権的なシステムコールを使っ て、新しいカーネルを保持するのに充分なスペースをくれるようカーネルに依頼する。 カーネルは、新規の module データ構造体と、 新しいモジュールを保持するのに充分なメモリ容量を割り当てて、それをカーネル モジュールリストの最後尾に置く。 その新しいモジュールは UNINITIALIZED とマークされる。
[see: sys_create_module(), in kernel/module.c]
図表(12.1)では、VFAT と FAT のふたつのモジュール がカーネルにロードされた後のカーネルモジュールのリストが示されている。 図表では示されていないが、リストの 先頭のモジュールは pseudo-module となっていて、これはカーネルの エクスポートされたシンボルのテーブルを保持するためだけにそこに置かれているもの である。lsmod コマンドを使えば、ロードされているすべてのモジュールと その依存関係を表示することができる。lsmod は単に /proc/modules を再構成しただけのもので、/proc/modules の方は、カーネルの module データ構造体のリストから作成されたものである。 モジュール用に割り当てられたメモリは、 insmod がモジュールにアクセス できるよう、insmod プロセスのアドレス空間にマップされる。 insmod は、モジュールを割り当てられたアドレス空間にコピーして モジュールの置き場所を変更することで、割り当てを受けたカーネルアドレス空間から モジュールが実行されるようにする。 こうした処理が必要なのは、異なる Linux システム上ではモジュールを同じアドレス にロードするよう要求できないだけでなく、同一システム上でも同じアドレスに 二度ロードするよう要求することはできないからである。このアドレスの変更の際 には、モジュールイメージが適切なアドレスを持つように修正がなされる。

新しいモジュールもカーネルにシンボルをエクスポートするので、insmod はエクスポートされるイメージのテーブルを作成しなければならない。すべての カーネルモジュールには、モジュール初期化と削除のルーチンが含まれていなければ ならず、それらのシンボルはわざとエクスポートされないのだが、insmod は、カーネルにそのルーチンのアドレスを渡さなければならないので、それらのアド レスを知っていなければならない。 これらすべてが上手くいった場合、insmod はモジュールを初期化する準備が 整ったため、特権的なシステムコールを発行して、カーネルに対して、モジュールの 初期化ルーチンと削除ルーチンのアドレスを渡す。
[see: sys_init_module(), in kernel/module.c]
新しいモジュールがカーネルに組み込まれるとき、カーネルの一連のシンボルが 更新されなければならず、新規モジュールによって利用されることになるモジュールに 対しても修正が加えられなければならない。 他のモジュールから依存されているモジュールは、そのシンボルテーブルの末尾にある 参照リストを管理しなければならず、その参照リストは自己の module データ構造体によってポイントされなければなら ない。 図表(12.1)では、VFAT ファイルシステムモジュール は、FAT ファイルシステムモジュールに依存している。したがって、FAT モジュール には、VFAT モジュールへの参照が含まれていなければならない。その参照は、VFAT モジュールがロードされたときに付け加えられる。 カーネルはモジュール初期化ルーチンを呼び出し、それが成功した場合、モジュールの インストールを行う。モジュールの削除ルーチンのアドレスは、そのモジュールの module データ構造体に保存され、モジュールがアンロードされるときに、 カーネルによって呼び出される。最後に、モジュールの状態が RUNNING にセットされ る。

13.2 モジュールのアンロード

モジュールは rmmod コマンドを使って削除することが可能であるが、 オンデマンドでロードされたモジュールは使用されなくなったとき kerneld によって自動的にシステムから削除される。アイドルタイマー(idle timer)が 時間切れになった際はいつも、kerneld はシステムコールを発行し、 オンデマンドでロードされたモジュールのなかで使用されなくなったものすべてを システムから削除するようリクエストする。 タイマーの値は、kerneld を始動した時にセットされる。わたしの kerneld のタイマー設定は 180 秒にしてある。 たとえば、iso9660 CD-ROM をマウントして、その iso9660 ファイルシステムが ローダブルモジュールであった場合、CD-ROM がアンマウントされてからしばらく すると、iso9660 モジュールはカーネルから削除される。

他のカーネルの一部が、あるモジュールに依存している間は、そのモジュールは アンロードできない。たとえば、ひとつ以上の VFAT ファイルシステムをマウントして いる場合、VFAT モジュールはアンマウントできない。lsmod の出力 を見れば、個々のモジュールが依存関係についてもカウントを持っていることが分か る。

Module:        #pages:  Used by:
msdos              5                  1
vfat               4                  1 (autoclean)
fat                6    [vfat msdos]  2 (autoclean)

カウントは、そのモジュールに依存しているカーネル実体(entity)の数を表してい る。上記の例では、vfatmsdos モジュールはどちらも fat モジュールに依存しているので、fat のカウントは 2 となって いる。vfatmsdos モジュールは両方ともそれに依存する 1 つの 実体を持っているが、その実体とはマウントされたファイルシステムである。 もし、わたしがもうひとつ VFAT ファイルシステムをマウントしたとすると、 vfat モジュールのカウントは 2 になるだろう。モジュールのカウントは、 イメージの最初のロングワード(longword)の中に保持されている。

そのフィールドには、AUTOCLEAN と VISITED フラグも保持されるので、やや 情報過多ともいえるフィールドになっている。これらふたつのフラグは、デマンド ローディングでロードされたモジュールに対して使用される。 そうしたモジュールが AUTOCLEAN とマークされるのは、システムが そのモジュールを自動的にアンロードする対象と認識できるようにするためである。 VISITED フラグは、ひとつ以上のシステムコンポーネントがそのモジュールを 使っていることを示している。VISITED フラグは、他のコンポーネントがその モジュールと使うときは必ずセットされる。オンデマンドでロードされたが使用され なくなったモジュールを削除するかどうかを kerneld がシステムに尋ねた ときは、システムはシステム上のすべてのモジュールを見回して、使用されなくなった という条件に該当するものを捜す。システムは AUTOCLEAN とマークされていて RUNNING 状態にあるモジュールだけを見る。該当するモジュールが VISITED フラグを クリアした場合、システムはそのモジュールを削除するが、モジュールがフラグをクリ アしない場合は、システムがその VISITED フラグをクリアして、システム上の次の モジュールの調査に移る。

モジュールがアンロード可能な場合、そのモジュールの削除ルーチンが呼び出され、 そのルーチンが、モジュールに割り当てられたカーネルリソースを解放する。
[see: sys_delete_module(), in kernel/module.c]
その module データ構造体は DELETED とマーク され、カーネルモジュールのリストから削除される。(削除されたモジュールが依存 していた)他のモジュールは、削除モジュールがもはや依存するものではなくなった ので、参照リストを修正しないといけない。 削除モジュールが必要としていたカーネルメモリはすべて割り当てを解放される。


次のページ 前のページ 目次へ