カーネルのソースを読みこなすためには,モジュールの仕組みを知る必要があります。カーネルの多くがモジュールでできているからです。デバイス・ドライバを例に,モジュールをコンパイルする仕組みやカーネルに付加する仕組みを見ていきましょう。

 カーネルとデバイス・ドライバは切っても切り離せない関係にあります。

 一つの理由は,カーネル・コードの一番多くの部分を占めているのがデバイス・ドライバだという事実です。

 もう一つは,デバイス・ドライバをカーネルに組み込む方法を理解することが,カーネル・ソースを読みこなす近道になるということです。カーネルは,「モジュール」という単位でプログラムを追加したり削除したりできます。デバイス・ドライバだけではなく,いろいろなプログラムをモジュールとしてカーネルに組み込めます。特にデバイス・ドライバの起動方法などを理解すると,モジュールの考え方が分かり,カーネルのソースも読みやすくなります。

 そこで今回は,カーネルがどうやってモジュールを起動しているのか,デバイス・ドライバに焦点を当てて,その仕組みや起動の様子を見てみましょう。

 Linuxが扱うデバイスにはさまざまなものがあります。その中でも皆さんが必ず使っているのがネットワーク・カードではないでしょうか。デバイス・ドライバには,カーネル内部に組み込む方法と,モジュール形式にする方法があります。何らかの理由で取り替えなければならないことになれば,カーネルに組み込んだ場合は再度カーネルをコンパイルし直す必要があります。これでは不便なので,モジュールとしてコンパイルしておき,使用するモジュールの指定を変更するだけで,別のネットワーク・カードに簡単に取り替えられます。

 パソコンを起動するとカーネルが起動され,その後,デバイス・ドライバのモジュールがロードされます。このときカーネルは,付加する必要のあるコンパイル済みのモジュールをロードします。どのようなモジュールを起動時にロードするかを指定しているのが,/etc/modprobe.confというファイルです。

 図1は,/etc/modprobe.confファイルの記述の一部です。ここでは,米Broadcom社の「tg3」というギガビット・イーサネット・カードが2枚使用されていることが分かります。これを米Intel社の「e100」というファースト・イーサネット・カードに取り替えるには,tg3 をe100に書き換えて再起動するだけで良いのです。


# cat /etc/modprobe.conf

alias eth0 tg3
alias eth1 tg3
alias usb-controller ohci-hcd
図1●modprobeファイルの記述

ドライバのソースの場所

 ネットワーク・カードのデバイス・ドライバのソース・プログラムは,カーネルのdrivers/netディレクトリにまとめられています。ファイル名からカードを推測できます。


# ls /usr/src/linux/drivers/net/*.c | grep tg3.c

/usr/src/linux/drivers/net/tg3.c

 通常,ネットワーク・カードのデバイス・ドライバは,カーネルのソースに含まれています。利用できるデバイス・ドライバが新たに加えられるだけでカーネルのバージョンがアップすることもあります。無線LANでは,自分でモジュールを組み込まなければならない場合もあります。

モジュールをカーネルに付加

 /etc/modprobe.confファイルに指定しておくと起動時に自動的にカーネルに付加されます。起動後,手動でモジュールをカーネルに付加するには,insmodコマンド,あるいはmodprobeコマンドを実行します。

 逆にrmmodコマンドを用いて,動作していないモジュールをカーネルから削除できます。

 では,デバイス・ドライバのモジュールがどのようにして,カーネルに付加されるかを見ていきましょう。

 デバイス・ドライバのソース・プログラムの最後に,付加の手順を記述する場所があります。このことを知っておくと,カーネル内部のすべてのモジュールを読む際の糸口になります。

 図2はtg3.cの場合です。デバイス・ドライバのソースでは,最後の部分でモジュールを付加したり削除したりする設定を書くのが通例です。このとき実行する関数が次の2種類です。


# cat -n /usr/src/linux/drivers/net/tg3.c

 10952 static int __init tg3_init(void)
 10953 {
 10954 return pci_module_init(&tg3_driver);
 10955 }
 10956
 10957 static void __exit tg3_cleanup(void)
 10958 {
 10959 pci_unregister_driver(&tg3_driver);
 10960 }
 10961
 10962 module_init(tg3_init);
 10963 module_exit(tg3_cleanup);
図2●デバイス・ドライバ「tg3.c」のソースの一部


付加する時:module_init()
削除する時:module_exit()

 カーネル全体を「module_init」で検索*1すると,すべてのモジュールを付加する部分を確認できます。

 この2つの関数は,引数で指定した関数を実行します。

 例えば,モジュールを付加するときは,tg3_init()関数が実行されます。カーネルに付加して起動時に最初に実行される関数が,module_init()関数なのです。

 この関数は10954行目にありますから,その中で呼んでいる「pci_module_init()」関数のソースを調べると, pci_register_driver()に再定義されていることが分かります。pci_register_driver()のソースは, drivers/pci/pci-driver.cです。その中を見ると,376行目以降でPCIカードを登録する処理を実行していているのが分かります。

 モジュールの組み込みに成功すると0を戻さなければなりません。それを知っていると,10954行目のpci_module_init()関数の戻り値が0でなければデバイス登録に失敗し,0ならば成功したことも分かります。