演算データの型を変換したり,代入時にデータの型を変換したりするときに「キャスティング」という手法を用います。キャスティングは, char,int,long,float,double,あるいは構造体の型を相互に変換する手法です。カーネルでは,構造体型に変換する手法が多く見受けられます。
まず,キャスティングの例を見てみましょう。図1のプログラムをコンパイルして実行すると,
|
図1●キャスティングのプログラム例 |
|
となります。
最初のprintf関数は,整数型定数である7と2の演算結果を浮動小数点型に変換しています。整数型で割り算すると,小数点以下を切り捨て,7÷2=3となり,この整数値3を浮動小数点型の3.0に変換します。
次のprintf関数は,整数型の7を浮動小数点型の7.0に変換し,整数型の2を浮動小数点型に変換した2.0で割ります。浮動小数点型で割り算すると,答えは3.5となります。
このように,整数型データを別の浮動小数点型に変換することをキャスティングといい,変換する型名を括弧で囲んで(変換する型)のように指示します。カーネル内部では,構造体型にキャスティングする方法がよく使われています。単純な型変換ではなく,構造体型に変換することで,取り扱うデータに意味付けができます。
それではカーネル内部で構造体型にキャスティングする実例を探してみましょう。そのために「=(struct」の文字列をカーネル・ソースから検索します。なお,検索には,これまで紹介してきた検索ツール「mygrep」を用いるなどしてください*1。
するとどうでしょう! カーネル・ソースのいたるところにキャスティング処理があることが分かります。カーネル全般で使われているこの手法を,今回はネットワークのヘッダー処理に焦点を当てて解説します。
カーネル内部でネットワークから受信したパケットのデータを第3層のIPヘッダーと,第4層のUDPヘッダーの構造体でキャスティングする具体例を取り上げながら,キャスティングのすばらしさを体験しましょう。
ヘッダー構造体に押し込む
ネットワーク回線上を流れるパケット信号は,「80A50035002EED6E」という無味乾燥な16進数の列で表現します。この連続した16進数値列をUDPヘッダーとして処理するときに,UDPヘッダー構造体型でキャスティングすると,UDPヘッダー構造体の「型に押し込む」ことができます。
カーネル内部で処理するネットワーク関連のヘッダーは,次に示すような種類があります。
|
最初に最も単純なUDPヘッダー構造体から確認しましょう。UDPヘッダーは,RFC768において図2に示すように定義されています。前述のパケット信号をこの構造体でキャスティングすると,
図2●UDPヘッダーの構造 [画像のクリックで拡大表示] |
|
というように,各数値に意味が出てきてUDPパケットのヘッダー情報を明確に取り扱うことができます。ここで,
|
と分かるわけです。このように,キャスティングは押し寿司のように「型に押し込むための手法」です。カーネル全般に数多く使われています。
もう少し具体的に見ていきましょう。
UDPヘッダーの定義を構造体で記述したものは,/usr/src/linux/include/linux/udp.hファイルにあります(図3)。
|
図3●UDPヘッダーを定義した構造体 /usr/src/linux/include/linux/udp.hファイルにある。__u16は符号無しの16ビットのこと。 |
今,回線上を流れているパケット信号(16進数値列)を「80A50035002EED6E」とします。これがUDPヘッダー部分の情報と仮定して,信号から受信先のポート番号を取り出す擬似的なカーネル・プログラム「UDP.c」を作成しました(図4)。
|
図4●UDPヘッダーからポート番号を抜き出す擬似プログラム「UDP.c」 |
コンパイルするときに,-Iでカーネルの/usr/src/linux/includeディレクトリに保管されているヘッダー・ファイルを利用するように指定します。カーネル・ソースを組み込んでいないLinuxマシンではincludeファイルが無いという警告が出るかもしれません。ここでは,実行ファイル名を「UDP」として,次のように/usr/includeでなく,カーネルのヘッダー・ファイルを使ってコンパイルします。
|
早速,実行してみましょう。このときカレント・ディレクトリにはPATHが設定されていませんから,「./実行形式ファイル名」と入力します。
|
このプログラムのキーポイントは25行目です。パケットの20番目以降のデータをUDPヘッダー構造体でキャスティングしている部分です。
あて先ポート番号を26行目で表示しました。実行結果にあるように,0035が3500と表示されました。これは,ネットワークのバイト順とパソコンのバイト順が異なるからです。そこで27行目で,htons()関数によりネットワーク・バイト順に変換します。16進の「0035」は10進の53番ポートであることが分かります。
53番ポートがどのようなサービスを行うポートかをgrepコマンドを用いて/etc/servicesファイルで見ると,
|
と「domain」なっているので,DNSの名前解決のUDPパケットであることが分かります。