この章では、Linux カーネルが、ファイルシステムなどの機能を、必要な時だけ 動的にロードする仕組みについて説明する。
Linux は、モノリシックなカーネル(monolithic kernel)を使用している。すなわ ちそれは、単一で巨大なプログラムであり、そこでは、カーネルのあらゆる機能別 コンポーネントが、すべての内部データ構造やルーチンにアクセスできる。 それとは反対の仕組みとして、マイクロカーネル(micro-kernel)構造がある。その 場合、カーネルの機能別部分は、個別のユニットに分割され、ユニット間での厳格な 通信メカニズムが設定される。モノリシックカーネルでは、新規コンポーネントを カーネルに追加するには、その設定プロセスを経由するので、やや時間がかか る。たとえば、NCR 810 SCSI の SCSI ドライバを使いたいのだが、まだそれをカーネル に組み込んでいないとする。その際は、新しいカーネルを設定し、ビルドすることで、 はじめて NCR 810 が使用できるようになる。 しかし、違う選択肢も存在する。Linux では、必要に応じて、オペレーティング システムのコンポーネントを動的にロードしたり、アンロードしたりできる。 Linux カーネルモジュール(module)とは、システム起動後ならいつでも動的にカーネル にリンクすることができるコード群である。それらは、必要がなくなればカーネルとの リンクを解消して削除することができる。大部分の Linux カーネルモジュールは、 デバイスドライバや、ネットワークドライバなどの仮想ドライバ、あるいはファイル システムなどである。
Linux カーネルモジュールは、insmod
や rmmod
コマンドを
使用して、明示的にロードとアンロードが可能である。また、カーネル自身も、
カーネルデーモン(kerneld
)によって、モジュールのロードとアンロードを
必要に応じて行うことができる。必要なときに動的にロードできるコードというのは、
カーネルのサイズを最小に抑え、それに柔軟性を与えるという意味で非常に魅力的な
機能である。わたしの現在の Intel カーネルはモジュールを広範に使っているので、
406 K バイトで済んでいる。VFAT ファイルシステムはたまにしか使わないため、
VFAT パーティションがマウントされた時に自動的に VFAT ファイルシステムがロード
されるように、わたしのカーネルはビルドされている。VFAT パーティションをアンマウ
ントしたとき、システムはわたしがもはや VFAT ファイルシステムモジュールを必要と
していないことを検出して、システムからそれを削除する。新しいカーネルコードを
試すときでも、試行のたびにカーネルをリビルドして再起動をかける必要がないので、
その点でもモジュールは便利である。しかし、その見返りとして、カーネルモジュール
を使うと若干のパフォーマンスの低下とメモリの消費が起こる。ローダブルモジュール
にするために提供しなければならないコードが少量あり、そのコードと付加的なデータ
構造とが加わるために、メモリの消費量がやや増加する。また、あるレベルで
間接処理が導入されるので、モジュールの場合、カーネルリソースへのアクセスが
やや非効率になる。
Linux モジュールは、いったんロードされると、通常のカーネルコードと同様に カーネルの一部となる。すなわち、他のカーネルコードと同一の権利と責任を持つ。 言い換えると、Linux カーネルモジュールは、すべてのカーネルコードやデバイス ドライバと同様に、カーネルをクラッシュさせることもできる。
モジュールが必要なカーネルリソースを使用するためには、まずそれらのリソースを
見つけだす必要がある。たとえば、モジュールが kmalloc()
やカーネルメモ
リ割り当てルーチンを呼び出す必要がある場合などを考えてほしい。ビルドされた時点
では、モジュールは、メモリ内のどこに kmalloc()
があるのかを知らない。
したがって、モジュールがロードされたとき、カーネルは、モジュールが動き出す前
に、モジュール内の kmalloc()
への参照をすべて解決しなければならない。
カーネルは、カーネルのシンボルテーブル内にカーネルリソースのすべてのリストを保
持しているので、モジュールのロード時に、モジュールからのそうしたリソースへの参
照を解決することができる。
Linux では、モジュールをスタック化できる。すなわち、あるモジュールは、他の
モジュールのサービスを前提とし、そのサービスを利用できる。たとえば、VFAT ファイ
ルシステムモジュールは、FAT ファイルシステムのモジュールのサービスを必要とす
る。VFAT ファイルシステムは、多かれ少なかれ FAT ファイルシステムの拡張であるか
らである。他のモジュールのサービスやリソースを必要とするモジュールというのは、
モジュールがカーネルそのもののサービスやリソースを必要とする状況と非常に類似
している。必要とするサービスが、先にロードされた他のモジュールの中だけに
あるからである。個々のモジュールがロードされると、カーネルは、カーネルシンボル
テーブルを変更して、新しくロードされたモジュールがエクスポートしたリソースや
シンボルのすべてをそこに付け加える。これが意味するのは、次のモジュールがロード
されたとき、そのモジュールは既にロードされているモジュールのサービスにアクセス
出来るということである。
モジュールをアンロードしようとするとき、カーネルはそのモジュールが使用されて いないことを知る必要があり、モジュールにこれからアンロードされることを知らせる 方法をも必要とする。そして、その方法によって、モジュールは、カーネルから削除 される前に、カーネルメモリや割り込みなどの割り当てられたシステムリソースを 解放することが出来る。モジュールがアンロードされるとき、カーネルは、その モジュールがカーネルシンボルテーブルにエクスポートしたすべてのシンボルを 削除する。
適切に書かれなかったために、ロードされたモジュールはオペレーティングシステム をクラッシュさせることが出来るということの他にも、モジュールの使用は別の危険も もたらす。現在使用しているカーネルよりも、古いか新しいバージョンのカーネル用に ビルドされたモジュールをロードした場合、何が起こるだろうか?これが問題を 起こすのは、たとえばモジュールがカーネルルーチンをコールして、誤った引数を渡し た場合である。カーネルは、オプションとしてではあるが、モジュールのロード時に そのモジュールを厳格にバージョンチェックすることで、この問題が起こらないように している。
図表(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.out
か ELF
か
のいずれかのフォーマットを取る。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 にセットされ
る。
モジュールは 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)の数を表してい
る。上記の例では、vfat
と msdos
モジュールはどちらも fat
モジュールに依存しているので、fat
のカウントは 2 となって
いる。vfat
と msdos
モジュールは両方ともそれに依存する 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 とマーク
され、カーネルモジュールのリストから削除される。(削除されたモジュールが依存
していた)他のモジュールは、削除モジュールがもはや依存するものではなくなった
ので、参照リストを修正しないといけない。
削除モジュールが必要としていたカーネルメモリはすべて割り当てを解放される。