カーネル・ソースの読解が難しい理由の一つは「構造体の多用」にあります。構造体は,複数の種類のデータをまとめるものですが,カーネルでは,関数の先頭番地を登録する手法が多く見られます。この手法を使えば,モジュールを簡単に差し替えられます。デバイス・ドライバを例に,その様子を見ていきましょう。

 パソコンのネットワーク・カードを異なるものに取り替えると,新しいデバイス・ドライバのモジュールをカーネルに組み込む必要があります。でも,カーネルのソース・コードを書き変える必要はありません。

 デバイス・ドライバは,デバイスを操作するハードウエアに依存する処理と,ハードウエアに依存しない処理とに分けられます。これらをそれぞれ下位層,上位層の2つに分けて,それぞれの層の間でデータをやり取りする仕組みになっています。

 2 つの層を接続するインタフェースを共通化しておけば,カードを付け替えたときも下位層だけを取り替えればよいことになります。このインタフェース部分に,構造体が使われています。関数を含んだ構造体でソフトウエア的に,2つの層を接続するわけです。この階層構造は,カーネル内部のあらゆるところで利用されています。すばらしい手法なのですが,カーネルの読解を難しくする大きな要因にもなっています。

 上位層のプログラムで使われている関数の名前は,どのカードでも共通に使える「共通関数名」になっています。一方,下位層のプログラムで使われる関数は,実在する関数名が使われます。そこで,構造体に着目して上位層が呼び出す共通関数名と,下位層の実在する関数名の関連を調べます。

 動作原理を解説するための擬似プログラムを作成しました。

 構造体を定義しているのがプログラム1(図1)です。

図1●プログラム1「my.h」
図1●プログラム1「my.h」

 上で「my_pci_driver」という構造体の型を定義し,下でmy_pci_driverに「tg3_card」と「e100_card」の実体を登録して,初期値をセットしています。ここで,プログラム2(図2)とプログラム3(図3)の関数名をセットしています。


1 #include <stdio.h>
2 int tg3(void)
3 {
4  printf("私はtg3です.\n");
5  return(0);
6 }
図2●プログラム2「tg3.c」


1 #include <stdio.h>
2 int e100(void)
3 {
4  printf("私はe100です.\n");
5  return(0);
6 }
図3●プログラム3「e100.c」

 そして,プログラム4(図4)で,上位層と下位層をソフトウエア的に接続します。構造体に,下位層の「tg3_card」という関数の先頭アドレスを「&」で設定します。その後,設定したカード名を表示します。

図4●プログラム4「pci.c」
図4●プログラム4「pci.c」
[画像のクリックで拡大表示]

 取りあえず,次のようにコンパイルして,実行してみてください。


# gcc -c pci.c

# gcc -c tg3.c

# gcc -c e100.c

# gcc -o GO pci.o tg3.o e100.o

# ./GO

Card=TG3私はtg3です.
====================
Card=Intel e100
私はe100です.

 実行結果を見ると,ネットワーク・カード名「TG3」とそのカードの実関数tg3()の実行結果「私はtg3です.」が表示されています。

 ここで,実関数を呼び出す上位層のpci.cの9行目と17行目に着目してください。


9  my_network_card->nikkei_linux();
17 my_network_card->nikkei_linux();

 両方とも同じmy_network_card-?ikkei_linux()という関数を使っています。

 これは,7行目で&tg3_card,15行目で&e100_card構造体をmy_network_cardに定義しているからです。プログラム4を見れば分かるように,付加するモジュールの接続部分に共通のmy_pci_driver構造体を使用することで,上位層のプログラムは下位層のデバイス・ドライバに依存しないで,すべて「my_network_card-?ikkei_linux」で操作することができます(図5)。

図5●上位層と下位層を接続するソフトウエア手法
図5●上位層と下位層を接続するソフトウエア手法
構造体の箱の書き方は第4回を参照。
[画像のクリックで拡大表示]

 こうすることで,各社は独自にデバイス・ドライバを並行して開発できるようになります。カーネル・モジュールやApache HTTP Serverで使われるモジュールも同じ手法です。接続部分の構造体を共通にして公開することで,世界中の開発チームが並行してモジュールを作成できるようになります。