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

9. デバイスドライバ

オペレーティングシステムの目的のひとつは、ユーザからシステムのハードウェア の特殊性を隠すことである。たとえば仮想ファイルシステムは、土台となる物理 デバイスとは無関係に、マウントされたファイルシステムに統一的な視野を提供する。 この章では、Linux カーネルがシステム上の物理デバイスをどのように管理している のかを解説する。

CPU だけがシステムで唯一の情報処理デバイスではなく、すべての物理デバイスが 独自のハードウェア制御装置を持っている。キーボード、マウス、シリアルポートは Super I/O チップで制御されており、IDE ディスクは IDE コントローラで、SCSI ディスクは SCSI コントローラで制御されている。個々のハードウェアコントローラは 独自のコントロールレジスタ(control registers)とステータスレジスタ(status registers)(あわせて CSRs)を持っている。それらレジスタの仕様はデバイスごとに異 なっており、Adaptec 2940 SCSI コントローラの CSRs は NCR 810 SCSI コントローラ のそれとは別物である。CRSs はデバイスの始動と停止、初期化、そして問題診断 (diagnose)のために利用される。システム上のハードウェアコントローラを管理する コードは、個々のアプリケーションに実装されるのではなく、Linux カーネルの中に 保持されている。 ハードウェアコントローラを操作あるいは管理するソフトウェアはデバイスドライバ (device driver)と呼ばれている。Linux カーネルのデバイスドライバは、本質的に 特権を与えられ、メモリに常駐する、低レベルハードウェアの処理ルーチンから成る 共有ライブラリである。担当するデバイスの特殊性に対処するのが、Linux のデバイス ドライバの役割である。

Un*x の基本的特徴のひとつは、デバイスの操作を抽象化していることである。 すべてのハードウェアデバイスは、通常ファイルのようのように見える。それらは、 ファイル操作で使用されるのと同じ標準的なシステムコールを利用して、オープン、 クローズ、書き込み、読み出しが可能である。システム上のあらゆるデバイスは、 デバイススペシャルファイルによって表現されていて、たとえば、システム上の最初の IDE ディスクは /dev/hda と表される。

ブロック(ディスク)デバイスとキャラクタデバイスに関しては、それらのデバイス スペシャルファイルは mknod コマンドによって作成され、デバイスを記述 するためにメジャー番号( major number)とマイナー 番号(minor number)とのデバイス番号が使用される。ネットワークデバイスも デバイススペシャルファイルによって表現されるが、それらは Linux がシステム上の ネットワークコントローラを見つけて初期化するときに Linux によって作成される。 同一のデバイスドライバによって制御されるすべてのデバイスコントローラは、共通の メジャー番号を持つ。マイナーデバイス番号が使用されるのは、デバイスドライバが 異なるデバイスやそのデバイスのコントローラを区別するためである。たとえば、プラ イマリ IDE ディスク上の個々のパーティションは異なるマイナー番号を持つ。それゆ え、プライマリ IDE ディスクの第二パーティション /dev/hda2 は、メジャー 番号 3 とマイナー番号 2 を持つ。 Linux は、システムコールで(たとえばブロックデバイス上のファイルシステムをマウン トするなどして)渡されたデバイススペシャルファイルをそのデバイスのデバイスドライ バにマップする。その際は、デバイスのメジャー番号といくつかのシステムテーブル、 たとえば chrdevs というキャラクタ デバイステーブルなどを使用する。
[see: fs/devices.c]

Linux は 3 種類のハードウェアデバイスをサポートしている。キャラクタデバイス (character device)、ブロックデバイス(block device)、ネットワークデバイス( network device)である。キャラクタデバイスは、バッファなしで直接読み書き するデバイスであり、たとえばシステムのシリアルポート /dev/cua0/dev/cua1 などがそうである。ブロックデバイスとは、ブロックサイズの 倍数、典型的には 512 か 1024 バイトでのみ読み書きするデバイスである。 ブロックデバイスはバッファキャッシュ経由でアクセスされ、ランダムなアクセス、 すなわちデバイス上のどこにあるブロックでも読み書きできるアクセス方法が利用 されることが多い。ブロックデバイスへのアクセスは、それぞれに対応した デバイススペシャルファイルを経由しても可能であるが、ファイルシステム経由で アクセスするほうがより一般的である。マウントされるファイルシステムをサポート できるのはブロックデバイスだけである。ネットワークデバイスへのアクセスは、 「ネットワーク」の章で解説する BSD ソケットインターフェイスまたはネットワークサブシステム経由で行なう。

Linux カーネル内には多種多様のデバイスドライバがある(これは、Linux の長所 のひとつである)が、それらすべてが以下の一般的な属性を共有している。

カーネルコード (kernel code)

デバイスドライバはカーネルの一部なので、カーネル内の他のコードと同様に、もし 上手く動かないとシステムに深刻な影響を与える。出来の悪いドライバはシステムを クラッシュさせる場合すらあり、その結果ファイルシステムが破壊されデータが喪失 することもあり得る。

カーネルインターフェイス (kernel interfaces)

デバイスドライバは、Linux カーネルやそれが所属するサブシステムに対して標準イン ターフェイスを提供しなければならない。たとえば端末ドライバは Linux カーネルに ファイルへの I/O インターフェイスを提供し、SCSI デバイスドライバは SCSI サブ システムに対してインターフェイスを提供し、そのサブシステムが次に Linux カーネル に対してファイル I/O とバッファキャッシュインターフェイスを提供する。

カーネルメカニズムとサービス

デバイスドライバは、メモリ割り当てや割り込みの伝達、待ち行列などの 標準的なカーネルサービスを利用する。

動的ロードが可能(loadable)

Linux のデバイスドライバの大部分は必要なときにオンデマンドでカーネルモジュール としてロードが可能であり、必要が無くなればアンロードもできる。これによって、 カーネルの適用能力が高くなり、システムリソースの利用効率も上がる。

設定の柔軟性

Linux のデバイスドライバはカーネル内に組み込むことも可能である。どのデバイス を組み込むかは、カーネルのコンパイル時に設定できる。

動的な設定(dynamic)

システムが起動して個々のデバイスドライバが初期化されると、ドライバは制御すべ きハードウェアデバイスを捜す。特定のデバイスドライバにより制御されるはずの デバイスが存在しなかったとしても問題はない。その場合、デバイスドライバは、 単に余分なだけであり、少量のシステムメモリが占有されることを除いて如何なる 悪影響も及ぼさない。

9.1 ポーリングと割り込み

たとえば「読み取りヘッダをフロッピーディスクのセクタ 42 へ動かせ」といった コマンドをデバイスが受け取ると、デバイスドライバは次のいずれかの方法、つまり デバイスをポーリング(polling)するか、あるいは割り込み(interrupt)を使うことに よって、そのコマンドが完了したかどうかを確認する。

通常、デバイスのポーリングとは、デバイスのステータスレジスタが変化して、 リクエストの実行を完了したことが示されるまで、デバイスのステータスレジスタを 頻繁に読み出す操作を意味している。 デバイスドライバはカーネルの一部であるので、もし、ドライバがポーリングし続ける とすると、システムに悪影響を与える。というのも、その場合、デバイスがリクエスト を完了するまで、カーネル内では何も実行されなくなるからである。 しかし、実際にポーリングを行うデバイスドライバは、システムタイマーを使用して、 一定時間経過後に、カーネルにデバイスドライバ内のルーチンを呼び出してもらうよう にしている。 このシステムタイマーのルーチンは、コマンドのステータスをチェックする。Linux の フロッピードライバは、まさにこの仕組みを使っている。だが、タイマーを使った ポーリングでは、精々大ざっぱな制御しかできない。もっと効率の良い方法は、 割り込み(interrupt)を使うことである。

割り込みによって駆動されるデバイスドライバとは、ハードウェアデバイスが サービスを要求するとき、ハードウェアデバイスがハードウェア割り込みをかける ことで、制御されているものである。 たとえばイーサネットドライバは、イーサネットがネットワークからパケットを 受信したときに割り込みをかける。Linux カーネルは、ハードウェアデバイスから正し いデバイスドライバへとその割り込みを伝達できなければならない。 これを実現するために、デバイスドライバは、割り込みの処理方法をカーネルに登録す る。デバイスドライバが登録するのは、割り込み処理ルーチンのアドレスと占有したい 割り込み番号である。どの割り込みがデバイスドライバに使用されているか、 割り込みのタイプ( type of interrupts)が それぞれいくつあるかを確認するには、/proc/interrupts を見ればよい。

 0:     727432   timer
 1:      20534   keyboard
 2:          0   cascade
 3:      79691 + serial
 4:      28258 + serial
 5:          1   sound blaster
11:      20868 + aic7xxx
13:          1   math error
14:        247 + ide0
15:        170 + ide1

割り込みリソースに関する要求は、ドライバの初期化の際に処理される。 システム上の割り込みのいくつかは固定されているが、それは IBM PC アーキテクチャ の遺産である。したがって、たとえばフロッピーディスクコントローラは常に割り込み 6 を使用する。それ以外の割り込み、たとえば PCI デバイスからの割り込みは、起動 時に動的に割り当てられる。その場合、デバイスドライバは、まず、制御している デバイスの割り込み番号(IRQ)を検出した上で、その番号を占有する旨要求 しなければならない。PCI デバイスの割り込みに関して、Linux は、標準的な PCI BIOS のコールバックをサポートする ことで、システム上のデバイスに関する、IRQ 番号等の情報を判断している。

割り込みが CPU 自体に伝達される方法はアーキテクチャによって異なるが、大部分 のアーキテクチャでは、割り込みの伝達は、システム上で他の割り込みの発生を停止さ せる特別なモードで行われる。したがって、デバイスドライバは、割り込み処理ルーチ ン内でできる限り簡潔な処理をすべきである。そうすれば、Linux カーネルは割り込み 処理をすぐに終了して、割り込みが起こる前に実行していたことに戻ることができる。 割り込みを受信した結果多くの処理をしなければならなくなった場合、デバイスドライ バは、カーネルのボトムハーフハンドラ( bottom half handler)かタスクキュー( task queue) を使用してルーチンをキューイングし、後で呼び出すという方法を取ることができる。

9.2 ダイレクトメモリアクセス(Direct Memory Access, DMA)

割り込み駆動型のデバイスドライバを使ってハードウェアデバイスと情報をやり 取りすることが有効なのは、データ量が比較的少ないときである。たとえば 9600 baud のモデムはミリ秒(1/1000 秒)あたりおよそ一文字を送信可能である。割り込み遅延 (interrupt latency)、すなわちハードウェアデバイスが割り込みを発信してから デバイスドライバの割り込み処理ルーチンが呼び出されるまでの時間が短い場合 (たとえば 2 ミリ秒)、データ転送に要するシステム全体へのインパクトは非常に小さ い。9600 baud のモデムのデータ転送は、0.002% の CPU 処理時間しかかからな い。しかし、ハードディスクコントローラやイーサネットのような高速なデバイスの 場合、データ転送レートはずっと高くなる。SCSI デバイスは、最大で毎秒 40 M バイト の情報を転送できる。

ダイレクトメモリアクセス( DMA)はこの問題を解決するため に発明された。DMA コントローラによって、デバイスはプロセッサの介在なしでシステムメモリとの データのやり取りが可能になる。ISA の DMA コントローラは 8 個の DMA チャンネル を持っていて、そのうち 7 個はデバイスドライバによって使用することができる。 個々の DMA チャンネルは、16 ビットのアドレスレジスタ(address register)と 16 ビットのカウントレジスタ(count register)を利用して、デバイスとメモリ間での 転送を行っている。データ転送を始める際、デバイスドライバは DMA チャンネルのアド レスレジスタとカウントレジスタを設定し、同時に読み出しか書き込みかのデータ転送 の方向を設定する。そしてデバイスドライバは、デバイスに対して、デバイス側の準備 が整い次第 DMA データ転送を始めることを告げる。転送が完了したとき、デバイスは PC に割り込みをかける。転送が行われている間、CPU は自由の他のことが出来る。

デバイスドライバは DMA を使用する際に注意しなければならないことがある。 何よりも先ず、DMA コントローラは仮想メモリについては何も知らず、システムの物理 メモリだけにしかアクセスできないことである。したがって、DMA 転送で書き込まれ たり読み出されたりするメモリは、物理メモリ上の連続したブロックでなければなら ない。これは、DMA を使ってプロセスの仮想アドレス空間へ直接アクセスできないこと を意味する。しかし、メモリ上のプロセスの物理ページをロックして、DMA 処理の間に それらがスワップデバイスにスワップアウトされないようにすることができる。 次に、DMA コントローラは物理メモリの全域にアクセスできるわけではないということ である。DMA チャンネルのアドレスレジスタは、DMA アドレスの最初の 16 ビットを 表し、次の 8 ビットは DMA のページレジスタ(page register)から来ている。 これは、DMA リクエストは、メモリの下位 16 M バイトに制限されていることを意味 する。

DMA チャンネルは 7 本しかない希少資源であり、デバイスドライバ間で共有が できない。割り込みの場合のように、デバイスドライバは、どの DMA チャンネルを使う か決定できなければならない。割り込みの場合と同様、デバイスのなかには固定された DMA チャンネルを持つものがある。たとえばフロッピーディスクは常に DMA チャンネ ル 2 を使用する。デバイスによっては、 DMA チャンネルをジャンパによって設定する ことができる。多くのイーサネットデバイスはこのテクニックを使用する。より柔軟な デバイスでは、(CSRs 経由で)どの DMA チャンネルを使うかを伝えられるようになって いて、その場合、デバイスドライバは単に空の DMA チャンネルを選んで使用することが できる。

Linux は DMA チャンネルの利用状況を監視するために、DMA チャンネルごとにひと つある dma_chan データ構造体の、配列を 使用する。 dma_chan データ構造体にはフィールドがふたつだけあり、DMA チャンネルを使用しているデバイスを記述した文字列へのポインタと、その DMA チャンネルがすでに割り当てされているかどうかを表すフラグが含まれている。 /proc/dmacat した際にプリントされるのは、この dma_chan データ構造体の配列である。

9.3 メモリ

デバイスドライバは注意深くメモリを使用しなければならない。デバイスドライバは Linux カーネルの一部であるので、仮想メモリは使用できない。割り込みの受信や ボトムハーフハンドラやタスクキューハンドラの実行によって、デバイスドライバが 起動されるたびに、カレントプロセスが変化する。デバイスドライバは、実行中で あったプロセスに代わって処理をする場合であっても、特定の実行中のプロセスに 依存できない。カーネルの他の部分と同様に、デバイスドライバはデータ構造体を 使用して、現在制御しているデバイスを監視する。それらのデータ構造体はデバイス ドライバの一部として静的に割り付けられることも可能だが、それはカーネルを 必要以上に大きくするだけでリソースの無駄使いになる。大部分のデバイスドライバ は、ページ単位に分割されていないカーネルメモリを割り当てて、データを保持して いる。

Linux はカーネルメモリの割り当てと解放のためのルーチンを提供しているので、 デバイスドライバはそのルーチンを使用する。カーネルメモリは 2 の n 乗ごとの範囲 で割り当てられる。たとえば、デバイスドライバが少量のメモリしか要求しない場 合でも 128 や 512 バイトを割り当てる。デバイスドライバが要求するバイト数は、次 のブロックサイズの値にまで切り上げられる。これによって、小さな空きメモリの ブロックをより大きなブロックに再結合しやすくなるので、カーネルメモリの解放と 統合が容易になる。

カーネルメモリがリクエストされた際、Linux カーネルは非常に多くの余計な仕事 をしなければならない場合がある。空きメモリの量が不足する場合、物理ページは 破棄されるかスワップデバイスに書き込まれる必要がある。通常、Linux は、要求した プロセスを待ち行列に入れて、充分なメモリが確保されるまでそれをサスペンド状態に する。しかし、デバイスドライバ(あるいは Linux カーネルコード)のなかにはサスペン ドできないプロセスもあるので、そうした場合、すぐにメモリを確保できないときは、 カーネルメモリ割り当てルーチンは、リクエストの処理に失敗することもある。 デバイスドライバが DMA を使用して割り当てられたメモリに対して書き込みや読み出し をしたい場合、ドライバは、そのカーネルメモリを DMA 転送が可能であると指定する こともできる。この場合、システム上で DMA 転送が可能なメモリの構成がどうなって いるのか理解しなければならないのは、デバイスドライバではなく、Linux カーネル である。

9.4 デバイスドライバとカーネルとのインターフェイス

Linux カーネルとデバイスドライバーとの相互通信は、標準的な方法で行われなけれ ばならない。キャラクタデバイス、ブロックデバイス、ネットワークデバイスなど 個々のデバイスドライバのクラス(class)は、クラス共通のインターフェイスを提供して いて、カーネルがそれらにサービスをリクエストする際は個々のクラスのインターフェ イスを使用する。 各クラスのデバイスやデバイスドライバはしばしばかなり性質の異なることがあるが、 共通のインターフェイスがあることで、それらを全く同じものとして扱うことができ る。たとえば、SCSI と IDE ディスクとは非常に異なった動作をするが、Linux カーネ ルはそれら両方に対して同じインターフェイスを使用することができる。

Linux は動的な設定にも柔軟に対応する。Linux は、起動のたびに異なった物理デバ イスを検出するかもしれないので、そのために様々なデバイスドライバを必要とする。 Linux では、カーネルビルドの際の設定によって、デバイスドライバをカーネル に組み込むことができる。しかし、起動時に初期化される際、組み込まれたデバイス ドライバは、制御すべきハードウェアを見つけられないことがある。また、カーネルに 組み込まれない場合でも、ドライバが必要なときは、カーネルモジュールとしてそれを ロードすることもできる。 このような動的なデバイスドライバの性質に対処するために、デバイスドライバは初期 化される際にカーネルに対して自分の情報を登録する。Linux は、それらへのインター フェイスの一部として、登録されたデバイスドライバのテーブルを管理している。 それらのテーブルには、そのクラスのデバイスへのインターフェイスをサポートする ルーチンおよび情報へのポインタが含まれている。

キャラクタデバイス

図表(8.1) キャラクタデバイス

キャラクタデバイス(character device)は、Linux の中で最もシンプルなデバイス である。それらはファイルとしてアクセスできるので、まるでそのデバイスがファイル と全く同じであるかのように、アプリケーションは、標準システムコールを使用して それらをオープンし、読み出しや書き込みをし、クローズする。 これは、デバイスとしてモデムを使い、PPP デーモンで Linux システムをネット ワークに接続する場合でも同じである。キャラクタデバイスが初期化されるとき、その デバイスドライバは、自分自身を Linux カーネルに登録するのだが、その際は、 device_struct データ構造体による配列 chrdevs にエントリを追加する。 デバイスのメジャー番号(たとえば、4 は tty デバイス)は、 この配列へのインデックスとして使用される。デバイスの メジャー番号は、予め決められている。
[see: include/linux/major.h]

chrdevs 配列の個々のエントリである device_struct データ構造体には、 ふたつの要素がある。登録されたデバイスドライバの名前へのポインタと、ファイル操 作ルーチン群へのポインタである。 このファイル操作ルーチン群は、それ自体が当該デバイスのキャラクタデバイス ドライバ内にあるルーチンのアドレスであり、その個々のルーチンが、open, read, write, close などファイルへの特定の操作を処理する。 キャラクタデバイスに関する /proc/devices の内容は、chrdevs 配列から取得される。

キャラクタデバイスを表すキャラクタ型スペシャルファイル(たとえば /dev/cua0)がオープンされるとき、カーネルは適切な設定を行い、正しいキャラ クタデバイスドライバのファイル操作ルーチンが呼び出されるようにしなければなら ない。通常の ファイルやディレクトリの場合と同様に、個々のデバイススペシャルファイルは VFS inode によって表される。 キャラクタ型スペシャルファイルの VFS inode には、すべてのスペシャルファイルと 同様に、デバイスに関するメジャー番号とマイナー番号が含まれている。 この VFS inode は、デバイススペシャルファイルの名前が問い合わせされる際に、 実ファイルシステム内の情報を元にして、EXT2 などの基本ファイルシステム によって作成される。
[see: ext2_read_inode(), in fs/ext2/inode.c]

個々の VFS inode は一連のファイル操作ルーチンと関連付けられるので、それら VFS inode の性質は、その inode が表現しているファイルシステムオブジェクトの 種類によって異なったものとなる。 キャラクタ型スペシャルファイルを表す VFS inode が作成される際はいつも、デフォル トのキャラクタデバイス操作ルーチンがそのファイルの操作ルーチンとして設定され る。
[see: def_chr_fops, in fs/devices.c]
[see: chrdev_open(), in fs/devices.c]
このデフォルトのルーチンは、ファイルのオープンというひとつだけのファイル操作を 含むものである。アプリケーションによってキャラクタファイルがオープンされたと き、その汎用のファイルオープン操作ルーチンは、デバイスのメジャー番号を chrdevs 配列へのインデックスとして使用し て、その特定のデバイス用のファイル操作ルーチン群を取ってくる。またそれは、 当該キャラクタスペシャルファイルを記述する file データ構造体を設定し、そのファイル操作ルーチンへのポインタが当該デバイ スドライバのファイル操作関数群を差すようにする。こうして、アプリケーションの ファイル操作ルーチンはすべてマップされ、そのキャラクタデバイスに対する一連の ファイル操作ルーチンの呼び出しに備える。

ブロックデバイス

ブロックデバイスの場合も、ファイルにアクセスするときのようにアクセスできる 仕組みがサポートされている。オープンされたブロック型スペシャルファイルを操作 するために適切な一連のファイル操作ルーチンを提供する際、そのために使用される メカニズムは、キャラクタデバイスの場合と全く同じである。Linux は、 blkdevs 配列によって、登録された一連のブロック デバイスを管理している。
[see: fs/devices.c]
それは、 chrdevs 配列と同様に、デバイス のメジャー番号を使って索引付けされている。そのエントリも device_struct データ構造体である。 キャラクタデバイスと違う点は、ブロックデバイスにはクラスがあることである。 SCSI デバイスがそれであり、IDE デバイスもそうである。Linux に自分自身を 登録するのも、カーネルにファイル操作ルーチンを提供するのも、そうしたクラス である。 ブロックデバイスのクラスに対するデバイスドライバは、そのクラスにクラス固有の インターフェイスを提供する。それゆえ、たとえば SCSI デバイスドライバは、SCSI サブシステムに対してクラス固有のインターフェイスを提供しなければならず、 SCSI サブシステムはそれを使ってそのデバイスに対するファイル操作をカーネルに 提供する。

すべてのブロックデバイスドライバは、通常のファイル操作インターフェイスに 加えて、バッファキャッシュへのインターフェイスを提供しなければならない。個々の ブロックデバイスドライバは、 blk_dev_struct データ構造体の配列である blk_dev 内に自分のエントリを作成する。
[see: drivers/block/ll_rw_blk.c]
[see: include/linux/blkdev.h]
この配列へのインデックスもまたデバイスのメジャー番号である。blk_dev_struct データ構造体は、リクエストルーチンのアドレスと request データ構造体のリストへのポインタによって構成される。 個々の request 構造体は、デバイスドライバにデータブロックを読み書き させようとする、バッファキャッシュからのリクエストを表現している。

図表(8.2) バッファキャッシュブロックデバイスリクエスト

バッファキャッシュが登録済みデバイスに対してデータブロックを読み書きしよ うとする度に、バッファキャッシュはその blk_dev_struct に対して request データ構造体を付け加える。図表(8.2)では、個々のリクエストが ひとつの以上の buffer_head データ構造体 へのポインタを持っていて、それぞれがデータブロックの読み書きのリクエストとなっ ていることが示されている。 buffer_head データ構造体は、(バッファキャッシュによって)ロックされ るので、そのバッファへのブロック操作の完了を待っているプロセスが存在すること がある。個々の request 構造体は、 all_requests リストというスタティックなリストから割り当てられる。 リクエストが空のリクエストリストに付け加えられた場合、ドライバのリクエスト処理 関数が呼び出され、リクエストキューの処理を始める。そうでない場合、ドライバは単 にリクエストリストにあるすべての request を処理する。

デバイスドライバがあるリクエストの処理を完了すると、ドライバは、 request 構造体から個々の buffer_head 構造体を削除し、それらが更新されたことをマークして、 ロックを解除しなければならない。buffer_head のロック 解除はブロック操作の完了を待っているすべてのプロセスを目覚めさせる。たとえば、 ファイル名を解決するために、EXT2 ファイルシステムは、そのファイルシステムを 保持するブロックデバイスから次の EXT2 ディレクトリエントリーを含んだデータ ブロックを読み出さなければならないとする。そのプロセスは、読み出そうとする ディレクトリエントリを含む buffer_head 上でスリープ状態にあるが、 デバイスドライバがそのプロセスを目覚めさせる。(操作が完了すると) request データ構造体は利用可能とマークされるので、次のブロックリクエストの際に 使用される。

9.5 ハードディスク

ディスクドライブは、データを回転ディスクプラッタ(platter)上に保持すること で、メモリよりもデータを長期的に保存する仕組みを提供する。データの書き込みには 小さなヘッドがプラッタの表面にある微細な粒子を磁化する。データはヘッドによって 読み出され、そのヘッドが特定の微細な粒子の磁化の有無を検出している。

ディスクドライブは一枚以上のプラッタから構成される。個々のプラッタは丹念に 磨かれたガラスかセラミック合金から成り、酸化鉄の非常に薄い層でコートされて いる。プラッタはセンタースピンドル(central spindle)に取り付けられ、モデルによっ て違いがあるが、3000 から 10,000 の間の一定の速度で回転する。 これと比較した場合、フロッピーは 360 RPM でしか回転しない。 ディスクの読み書きヘッドはデータの読み書きを担当し、プラッタ 一枚に一組、その両面に装着されている。読み書きヘッドはプラッタ表面に物理的に 接触することなく、非常に薄い(1/1千万 インチ)空気の層をクッションに浮いている。 読み書きヘッドはアクチュエータによってプラッタの表面を移動する。すべての 読み書きヘッドは一体となっていて、プラッタ表面上をすべてが同時に動く。 (訳注: RPM, Revolutions Per Minute, 分あたりの回転数)

プラッタの個々の表面は、トラック(track)とよばれる細い同心円に分割されてい る。トラック 0 が一番外側のトラックであり、最も大きな番号のトラックがセンター スピンドルに最も近い位置にある。シリンダー(cylinder)とは、同一番号のトラックを すべて一組にした概念である。それゆえ、ディスク内のすべてのプラッタの両面にある 5 番トラック全体が、シリンダー 5 と呼ばれる。シリンダ数はトラック数と同じ なので、ディスクジオメトリは、シリンダの方を使って記述される。個々のトラック はセクタ(sector)に分割される。セクタはハードディスクで読み書き可能な最も小さな データ単位であり、ディスクブロックサイズでもある。一般的なセクタサイズは 512 バイトであり、セクタサイズはディスクのフォーマット時、通常はディスクが製造 された時に設定される。

ディスクは通常、シリンダ(cylinder)、ヘッド(head)、セクタ(sector)という ジオメトリによって記述される。たとえば起動時に、Linux は IDE ディスクを次の ように記述する。

hdb: Conner Peripherals 540MB - CFS540A, 516MB w/64kB Cache, CHS=1050/16/63

上記は、ディスクが 1050 シリンダ(トラック)、16 ヘッド(8 プラッタ)、そして トラックごとに 63 セクタを持つことを意味する。セクタもしくはブロックサイズが 512 バイトの場合、ディスクは 529200 キロバイト (1 キロバイト = 1024 バイト として) の記憶容量を持つことになる。これはディスク情報として表示されるサイズ である 516 メガバイトと一見異なっているように見えるが、529200 / 1024 は約 516.8 となり、実は合っている。 (訳注: ちなみに 540MB というのは 1MB = 1000 x 1000 バイトで計算した場合の 数値である。) ディスクのなかには、自動的に不良セクタを検出して、そのセクタの周辺にあるセクタ を適切に利用するために、ディスクのインデックスを付け直すものもある。

ハードディスクはさらにパーティションに分割できる。パーティションは特別な 目的のために割り当てられたセクタの大規模なグループである。ディスクを パーティションに分割することで、そのディスクを様々なオペレーティング システムや様々な目的に利用することが可能になる。多くの Linux システムは、 単一ディスク上に 3 つのパーティションを持つ。ひとつは DOS ファイルシステムを 持ち、ふたつ目が EXT2 ファイルシステム、3 つ目がスワップパーティションを持つ。 ハードディスクのパーティションは、パーティションテーブルによって記述 されている。その個々のエントリは、パーティションの最初と最後の位置をヘッド、 セクタ、シリンダ番号によって記述している。fdisk でフォーマットされ た、DOS フォーマットのディスクの場合、4 つのプライマリディスクパーティション が存在するが、4 つ全部のエントリがパーティションテーブルで使用される必要はな い。fdisk によってサポートされているパーティションタイプには、 プライマリ、拡張(extended)、論理(logical)の 3 種類がある。拡張パーティション は実際のパーティションではなく、それらは任意の数の論理パーティションを含む。 拡張パーティションと論理パーティションとは、4 つのプライマリパーティションと いう限界を回避するために発明された。次に示すのは、ふたつのプライマリ パーティションを含むディスクに対する fdisk の出力である。

Disk /dev/sda: 64 heads, 32 sectors, 510 cylinders
Units = cylinders of 2048 * 512 bytes

   Device Boot   Begin    Start      End   Blocks   Id  System
/dev/sda1            1        1      478   489456   83  Linux native
/dev/sda2          479      479      510    32768   82  Linux swap

Expert command (m for help): p

Disk /dev/sda: 64 heads, 32 sectors, 510 cylinders

Nr AF  Hd Sec  Cyl  Hd Sec  Cyl   Start    Size ID
 1 00   1   1    0  63  32  477      32  978912 83
 2 00   0   1  478  63  32  509  978944   65536 82
 3 00   0   0    0   0   0    0       0       0 00
 4 00   0   0    0   0   0    0       0       0 00

上記では、第一パーティションは、シリンダもしくはトラック 0 、ヘッド 1 、 そしてセクタ 1 から始まり、シリンダ 477 、セクタ 32 、そしてヘッド 63 まで の領域になっている。トラックあたり 32 セクタで、 64 ヘッドあるので、この パーティションは、シリンダ数に合致した容量を持つ。fdisk は、 デフォルトでシリンダの境界線上で区切って、パーティションを割り当てる。 fdiskは、最外周のシリンダ(0)から出発して、内側方向に広げていき、 回転軸に向かって 478 シリンダから構成される領域を割り当てている。 第二パーティションはスワップパーティションであり、次のシリンダ(478)から スタートしてディスクの最内周のシリンダに及んでいる。

図表(8.3) ディスクの連結リスト

初期化の過程で Linux はシステム上のハードディスクのトポロジーをマップする。 まず、ハードディスクの数と、そのタイプを調べる。さらに、Linux は個別のディスク がどのようにパーティション分割されているかを発見する。これらはすべて、 gendisk データ構造体のリストによって表現され、 それが gendisk_head リストポインタによって ポイントされる。 個々のディスクサブシステム、たとえば IDE ディスクサブシステムは、初期化される 際、gendisk データ構造体を生成して、検出したディスクを記述する。それ と同時に、ディスクサブシステムは、そのファイル操作ルーチンを登録し、そのエント リを blk_dev データ構造に付け加える。 個々の gendisk データ構造体は、ユニークなデバイスメジャー番号を持ち、 その番号は、ブロックスペシャルデバイスのメジャー番号と一致する。 たとえば、SCSI ディスクサブシステムは、単一の gendisk エントリ("sd")を作成し、それに SCSI ディスクデバイス全体のメジャー番号である、メジャー 番号 8 を付ける。図表(8.3)では、ふたつの gendisk エントリがあり、最初 はすべての SCSI サブシステムのものであり、ふたつめが IDE ディスクコントローラの ものとなっている。後者は、プライマリ IDE コントローラである ide0 であ る。

ディスクサブシステムは初期化の過程で gendisk エントリを作成するが、それらは Linux によってパーティションチェックの際に 使用されるだけである。 それとは別に、個々のディスクサブシステムは、それぞれ独自のデータ構造体を管理 することで、デバイスのメジャー番号とマイナー番号を物理ディスク内のパーティショ ンにマップしている。ブロックデバイスがバッファキャッシュやファイル操作ルーチン 経由で読み書きされるときはいつも、カーネルは、ブロックスペシャルデバイスファイ ル(たとえば /dev/sda2)に書かれたメジャー番号を使って、その操作を 適切なデバイスへと導く。そして、デバイスのマイナー番号を実(real)物理デバイスに マップしているのが、個別のデバイスドライバやデバイスサブシステムである。

IDE ディスク

今日 Linux システム上で利用される最も一般的なディスクは、IDE (Integrated Disk Electronics)ディスクである。IDE とは、SCSI のような I/O というよりも、 むしろディスクインターフェイスである。 個々の IDE コントローラはふたつまでのディスクをサポートできる。一方がマスターデ ィスク(master disk)であり、他方がスレイブディスク(slave disk)である。 マスターとスレイブの機能は通常、ディスクのジャンパピンで設定される。システム上 の最初の IDE コントローラはプライマリコントローラと呼ばれ、次のコントローラは セカンダリコントローラ等と呼ばれる。IDE はディスクとの間で毎秒 3.3 M バイトの データ転送能力を持っており、IDE の最大ディスクサイズは 528 M バイトである。 拡張 IDE 、もしくは EIDE(Extended IDE) は、最大ディスクサイズを 8.4 G バイトに 拡大し、データ転送能力も毎秒 16.6 M バイトに上げたものである。IDE と EIDE ディスクは SCSI ディスクよりも安価であるので、現在の PC にはひとつ以上の オンボード IDE コントローラが含まれている。

Linux はコントローラを見つけた順番に IDE ディスクに名前を付ける。プライマリ コントローラのマスターディスクは /dev/hda であり、スレイブディスクは /dev/hdb である。/dev/hdc はセカンダリ IDE コントローラの マスターディスクとなる。IDE サブシステムは Linux カーネルに IDE コントローラ を登録し、ディスクを登録しない。プライマリ IDE コントローラのメジャー番号は、 3 であり、セカンダリ IDE コントローラのメジャー番号は 22 である。これは、システ ムにふたつの IDE コントローラがある場合、 blk_devs blkdevs 配列には 3 と 22 のインデックスがついた IDE サブシステムのエントリが 存在することを意味する。IDE ディスクのブロックスペシャルファイルはこの番号の 割り当てを反映しているので、プライマリ IDE コントローラに接続されたディスクで ある /dev/hda/dev/hdb は、どちらもメジャー番号 3 を持つ。 カーネルはメジャー番号をインデックスとして使用しているため、それらのブロック スペシャルファイル上の IDE サブシステム操作ルーチンを使ったファイル操作や バッファキャッシュ操作は、まず、IDE サブシステムへと導かれる。 そうしたリクエストが為されたとき、リクエストがどの IDE ディスクに対するもの なのかを判別するのは、IDE サブシステムの役目である。 そのために、IDE サブシステムは、デバイスの特別な識別子としてマイナー 番号を使用する。その識別子には、リクエストを正しいディスクの正しい パーティションに送るための情報が含まれている。プライマリ IDE コントローラ上 のスレイブ IDE ディスクである /dev/hdb のデバイス識別子は、 (3,64)(メジャー番号 3 、マイナー番号 64)である。 そのディスクの最初のパーティション(/dev/hdb1)のデバイス識別子は、 (3,65) である。

IDE サブシステムの初期化

IDE ディスクは、IBM PC の歴史と深く関わっている。それらの時代を通じて IDE デバイスに対するインターフェイスは何度も変更された。それによって、 IDE サブシステムの初期化は、それが登場した当初よりも複雑なものになっている。

Linux がサポートできる IDE コントローラの最大数は 4 つである。個々の コントローラは、 ide_hwifs 配列にある ide_hwif_t データ構造体によって表される。 個々の ide_hwif_t データ構造体はふたつの ide_drive_t 構造体を含んでおり、各々がマスターとスレイブの IDE ドライブのサポートに備えたものとなっている。IDE サブシステムの初期化の過程で、 Linux はまず、システムの CMOS メモリにディスクに関する情報があるかどうかを 確認する。これは、PC の電源が切られたときもバッテリ駆動のバックアップメモリに よってその内容が失われないようになっている。この CMOS メモリは実際にはシステム のリアルタイムクロックの中にあり、そのクロックは PC の電源が入っているかどうか に関わらず、実行されている。その CMOS メモリの場所は BIOS を使って設定が可能 であり、それが Linux に対してどのような IDE コントローラやドライブが検出され たかを告げる。Linux は検出されたディスクのジオメトリを BIOS から取ってきて、 その情報を利用して当該デバイスの ide_hwif_t データ構造体を設定する。 より現代的な PC では、PCI (E)IDE コントローラに含まれる Intel の 82430 VX のような PCI チップセットを使用する。そして、システム上にあるそうしたチップ セット用の PCI 固有の検出ルーチンをコールする。

個々の IDE インターフェイスやコントローラが発見されたら、その ide_hvif_t 構造体が、そのコントローラやそれに接続 されたディスクの状態を反映するために設定される。 設定の過程で、IDE ドライバは、I/O メモリ空間に存在する IDE コマンドレジスタに コマンドを書き込む。プライマリ IDE コントローラ上のコントロールレジスタと ステータスレジスタのデフォルトの I/O アドレスは、0x1F0 から 0x1F7 である。 これらのアドレスの設定は、初期の IBM PC の慣習によって決まっている。 IDE ドライバは、個々のコントローラを Linux のブロック バッファキャッシュと VFS に登録し、それを blk_dev blkdevs 配列にそれぞれ付け加える。IDE ドライブは適切な割り込み制御も要求する。 ここでもまた、その割り込み番号は慣習的に決まっており、プライマリ IDE コント ローラが 14 で、セカンダリコントローラが 15 である。しかし、それらは、他の すべての IDE の詳細と同様に、カーネルへのコマンドラインオプションによって 上書き変更が可能である。また IDE ドライバは、起動中に発見された個々の IDE コントローラについて、 gendisk のリストに gendisk のエントリを付け加える。このリストは後で、起動時に見つかったす べてのハードディスクのパーティションテーブルを発見するために使用される。

SCSI ディスク

SCSI (Small Computer System Interface)バスは、効率的な一対一(peer-to-peer) のデータバスであり、ひとつ以上のホストを含む 8 個のデバイスをひとつのバスで サポートする。個々のデバイスは固有の識別子を持つ必要があり、通常それはディスク 上のジャンパピンで設定される。バス上にある任意のふたつのデバイス間で 同期あるいは非同期のデータ転送が可能であり、32 ビット幅のデータ転送により最大 毎秒 40 M バイトの転送速度が得られる。SCSI バスは、デバイス間でデータとステート (状態)の情報の両方を転送する。イニシエータ(initiater)とターゲット(target)間の 一回の転送中に最大 8 つの異なるフェイズ(phase)を取ることがある。現在の SCSI バスのフェイズは 5 本の制御線から、確認できる。8 つのフェイズとは次のような ものである。

BUS FREE

バスを制御しているデバイスは存在せず、トランザクションは現在行われていない。

ARBITRATION

SCSI デバイスが SCSI バスを制御しようとしていて、SCSI 識別子に対応した ピンを有効にしている。SCSI 識別子の最高値が優先される。

SELECTION

ARBITRATION を通じてデバイスが SCSI バスの制御に成功したとき、そのデバイスは SCSI のターゲットに対して、コマンドを送る旨のリクエストを SCSI バスの制御線で 伝えなければならない。それをするために、ターゲットの SCSI 識別子に対応した ピンを有効にする。

RESELECTION

SCSI デバイスは、リクエスト処理中に接続を中止するかもしれない。その場合、 ターゲットは、イニシエータに再接続できる。すべての SCSI デバイスがこのフェーズ をサポートしているわけではない。

COMMAND

6,10, もしくは 12 バイトの命令を、イニシエータからターゲットに送ることが できる。

DATA IN, DATA OUT

これらのフェーズにおいて、データはイニシエータとターゲット間で転送される。

STATUS

このフェーズに入るのは、コマンドがすべて実行され、ターゲットがイニシエータに 対して成功か失敗を示すステータスバイトを送ることができる場合である。

MESSAGE IN, MESSAGE OUT

イニシエータとターゲット間での追加情報の転送。

Linux SCSI サブシステムは、ふたつの基本要素から成り、それぞれがデータ構造体 によって表される。

host

SCSI ホスト(host)とは物理的なハードウェアである SCSI コントローラを指す。 NCR810 PCI SCSI コントローラは SCSI ホストの典型である。Linux システムが 同種の SCSI コントローラを複数持つ場合、個々のコントローラは、個別の SCSI ホストとして表現される。これは、SCSI デバイスドライバは同種のコントローラを 複数制御できることを意味する。SCSI ホストは、ほとんどいつも SCSI コマンドの イニシエータである。

Device

SCSI デバイスの中で最も一般的なのが SCSI ディスクであるが、SCSI 規格はもっと 多種類のデバイスをサポートしている。テープ、CD-ROM それに汎用 SCSI デバイス などである。SCSI デバイスはほとんどいつも SCSI コマンドのターゲットである。 それらのデバイスは異なった取り扱いが必要であり、たとえば CD-ROM やテープなどの リムーバルメディアの場合、Linux はそのメディアが取り出されたかどうか確認できな ければならない。 異なるディスクタイプには異なるメジャー番号が与えられ、それによって Linux は ブロックデバイスへのリクエストを適切な SCSI タイプに伝達することができる。

SCSI サブシステムの初期化

SCSI サブシステムの初期化は、SCSI バスやデバイスの動的な性質を反映して、 非常に複雑なものになっている。Linux は SCSI サブシステムをブート時に初期化 する。システム上の SCSI コントローラ(SCSI ホストと呼ばれる)を検出し、 コントローラごとの SCSI バスを検査して、バス上のデバイスをすべて検出する。 その上で、それらのデバイスを初期化して、Linux カーネルの残りの部分が、 通常ファイルの操作ルーチンやバッファキャッシュブロックデバイスの操作ルーチンを 経由して、デバイスを利用することが出来るようにする。 この初期化には 4 つのフェイズがある。

まず Linux は、カーネル構築時にカーネルに組み込まれた SCSI ホストアダプタ もしくはコントローラのうち、制御すべきハードウェアを持つのはどれなのかを見 つけだす。 カーネルに組み込まれた個々のホストは、 builtin_scsi_host 配列内に Scsi_Host_Template エントリを持つ。 Scsi_Host_Template データ構造体にはルーチンへのポインタが含まれてい て、それによって SCSI ホストに接続された SCSI デバイスの検出といったその SCSI ホスト固有の処理が実行される。 それらのルーチンは、SCSI サブシステムによっても呼び出されて、その設定を行ったり もする。それらは、そのホストタイプをサポートする SCSI デバイスドライバの一部で ある。 検出された個々の SCSI ホストのうち、実際に SCSI デバイスが接続されていたもの は、自分の Scsi_Host_Template データ構造体を、アクティブな SCSI ホスト のリストである scsi_hosts リスト に付け加える。 検出されたホストタイプの個々の SCSI コントローラは、 scsi_hostlist リスト内にある Scsi_Host データ構造体によって表現される。たとえば、NCR810 PCI SCSI コントローラを持つシステムは、リスト内に、コントローラごとにひとつ、合計 ふたつの Scsi_Host エントリを持つ。個々の Scsi_Host 構造体 は、そのデバイスドライバを表している Scsi_Host_Template をポイントして いる。

図表(8.4) SCSI データ構造

SCSI ホストがすべて見つかったら、次に SCSI サブシステムは、個々の SCSI ホス トにどういった SCSI デバイスが繋がれているのか見つけ出さなければならない。SCSI デバイスには 0 から 7 までの番号が振られ、個々のデバイス番号もしくは SCSI 識別子はそれが繋がれた SCSI バス上においてユニークでなければならない。 通常、SCSI 識別子はデバイス上のジャンパピンで設定される。SCSI 初期化コードは、 SCSI バス上の個々の SCSI デバイスを検出するとき、それらに TEST_UNIT_READY コマ ンドを送信する。デバイスが応答すると、ENQUIRY コマンドが送信されて、そのデバイ スの識別子が読み出される。これによって Linux は、そのベンダ名、デバイスモデル、 リビジョン名を取得する。 SCSI コマンドは Scsi_Cmnd データ構造体によって表現され、それらがこの SCSI ホストに対するデバイスドライバ に渡されるときは、その Scsi_Host_Template データ構造体内にあるデバイスドライバルーチンが 呼び出される。発見されたすべての SCSI デバイスは Scsi_Device データ構造体によって表現され、その 個々の構造体がその親である Scsi_Host を ポイントしている。すべての Scsi_Device データ構造体は scsi_devices リストに付け加えられる。図表(8.4) では、主要なデータ構造体がどのような相互関係にあるのかが示されている。

SCSI デバイスには、ディスク、テープ、CD 、汎用の 4 種類のタイプがある。 それらの SCSI タイプの各々が異なるメジャーブロックデバイスタイプとしてカーネル に独立して登録される。しかし、それらは、その種の SCSI デバイスタイプがひとつ以 上見つかった場合のみ登録がなされる。SCSI ディスクのような個々の SCSI タイプは 独自のデバイステーブルを管理している。そしてそれらのテーブルを利用してカーネル の(ファイルやバッファキャッシュに対する)ブロック操作ルーチンを正しいデバイス ドライバや SCSI ホストに導く。 個々の SCSI タイプは Scsi_Device_Template データ構造体によって表現される。これには、 その SCSI デバイスのタイプと様々なタスクを実行するルーチンのアドレスに関する 情報が含まれる。SCSI サブシステムはそれらのテンプレートを使用して個々の SCSI タイプのデバイスに対する SCSI タイプルーチンを呼び出す。言い換えると、もし SCSI サブシステムが SCSI ディスクデバイスを追加したい場合、その SCSI ディスク タイプの追加用ルーチンが呼び出される。ひとつ以上のそのタイプの SCSI デバ イスが見つかった場合、Scsi_Type_Template データ構造体(訳注: Scsi_Device_Templateか?)が scsi_devicelist リストに付け加え られる。

SCSI サブシステムの初期化における最後のフェーズは、登録された Scsi_Device_Template の個々に対する 終了用関数を呼び出すことである。SCSI ディスクタイプごとに、その関数が、検出され た SCSI ディスクすべてをまとめて、それらのディスクジオメトリを記録する。また、 それは、すべての SCSI ディスクを表現している gendisk データ構造体を 図表(8.3)で示し たディスクの連結リストに付け加える。

ブロックデバイスのリクエストを伝達する

Linux が SCSI サブシステムの初期化を完了すると、SCSI デバイスが利用可能に なる。個々のアクティブな SCSI デバイスのタイプは、Linux がブロックデバイス リクエストをそれに導けるように、カーネルに登録される。そうしたリクエストには blk_dev を経由したバッファキャッシュ リクエストや blkdevs を経由したファイ ル操作リクエストなどがある。 では、ひとつ以上の数の EXT2 ファイルシステムのパーティションを持つ SCSI ディス クを例に取ると、その EXT2 パーティションがマウントされているとき、カーネルの バッファリクエストはどのようにして適切な SCSI ディスクに渡されるのだろうか?

SCSI ディスクパーティションに対するデータブロックの読み書きリクエストが 為されると、 request 構造体が blk_dev 配列内にあるその SCSI ディスクの current_request リストに付け加えられる。 request リストが処理されている場合、バッファキャッシュは何もする必要 がないのだが、そうでない場合は、 SCSI サブシステムを促してそのリクエストキュー を処理させなければならない。システム上の個々の SCSI ディスクは Scsi_Disk データ構造体によって表現されている。 その構造体は rscsi_disks 配列に 保存されていて、その配列は SCSI ディスクパーティションのマイナー番号の 一部を使用してインデックス付けされている。たとえば、/dev/sdb1 は メジャー番号 8 とマイナー番号 17 を持っているが、これはインデックス 1 を 生成する。個々の Scsi_Disk データ構造体には、このデバイスを表現 する Scsi_Device データ構造体へのポイン タが含まれている。その構造体が次に Scsi_Host 構造体をポイントしており、それがそのデバイスの「オーナー」である。 バッファキャッシュからの request データ構造体は、SCSI デバイスに送信されるべき SCSI コマンドを記述している Scsi_Cmnd 構造体に変換され、これがそのデバイスを表す Scsi_Host データ構造体上のキューに置かれる。いったん適切なデータブロックが 書き込まれたり読み出されたりすると、そうしたことが個別の SCSI デバイスドライバ によって処理される。

9.6 ネットワークデバイス

Linux のネットワークサブシステムに関する限り、ネットワークデバイスとは、 データパケットを送受信する実体のことである。通常これはイーサネットカードなどの 物理デバイスである。しかし、データを自分に送信するために使用されるループバック デバイス(loopback device)といったソフトウェアのネットワークデバイスもある。 個々のネットワークデバイスは device データ 構造体によって表現される。
[see: include/linux/netdevice.h]
ネットワークデバイスコントローラは、カーネル起動時のネットワーク初期化過程で、 Linux に制御しようとするデバイスを登録する。device データ構造体には、 デバイスに関する情報と、サポートされる多様なプロトコルがデバイスのサービスを 利用する際に使う関数のアドレスとが含まれる。 これらの関数は大部分がネットワークデバイスを使用したデータ転送に関するものであ る。デバイスは、受信したデータを適切なプロトコル層に渡すために標準的なネット ワークサポート機能を使用する。送受信されたすべてのデータ(パケット)は、 sk_buff データ構造体によって表現されるが、それは柔軟 な構造体なので、ネットワークプロトコルのヘッダの簡単な追加や削除を可能にする。 ネットワークプロトコル層がネットワークデバイスを使用する方法、それらが sk_buff データ構造体を使用してデータを受け渡しする方法については、 「ネットワーク」の章で詳しく解説する。本章では、 device データ構造体とネットワークデバイスが検出され初期化される方法と に集中する。

device データ構造体にはネットワークデバイ スに関する次のような情報が含まれている。

名前 (name)

mknod コマンドを使ってデバイススペシャルファイルを作成するブロック デバイスやキャラクタデバイスとは異なり、ネットワークデバイスのスペシャル ファイルは、システムのネットワークデバイスが検出され初期化される際に自動的に 出現する。その名前は標準的なもので、個々の名前はそのデバイスのタイプを表して いる。同タイプのデバイスが複数ある場合は、0 から順に番号付けされる。したがっ て、複数のイーサネットデバイスは、/dev/eht0, /dev/eth1, /dev/eth2 等となる。一般的なネットワークデバイスをいくつか示す。

/dev/ethN  イーサネットデバイス

/dev/slN   SLIP デバイス

/dev/pppN  PPP デバイス

/dev/lo    ループバックデバイス

バス情報 (bus information)

これは、デバイスドライバがデバイスを制御するために必要な情報である。 irq 番号はそのデバイスが使用する割り込み番号である。 base_addr は I/O メモリ内にあるコントロールレ ジスタとステータスレジスタのアドレスである。 dma はそのネットワークデバイスが使用する DMA チャンネルの番号である。これらすべての情報はブート時のデバイスが初期化される際 に設定される。

インターフェイスフラグ(interface flag)

これらは、ネットワークデバイスの特徴と能力とを示している。

IFF_UP           インターフェイスが起動され実行中である。

IFF_BROADCAST    デバイスのブロードキャストアドレスが有効である。

IFF_DEBUG        デバイスのデバッギングが有効である。

IFF_LOOPBACK     これは、ループバックデバイスである。

IFF_POINTTOPOINT これは、一対一(point to point)のリンクである。(SLIP と PPP)

IFF_NOTRAILER    ネットワークトレイラー(network trailer)なし。(訳注: 用語集)

IFF_RUNNING      リソースが割り当てられた。

IFF_NOARP        ARP プロトコルをサポートしない。

IFF_PROMISC      プロミスク受信モードのデバイスは、パケットの送信先アドレス
                 とは無関係にすべてのパケットを受信する。

IFF_ALLMULTI     IP マルチキャストフレームを受信する。

IFF_MULTICAST    IP マルチキャストフレームの受信が可能。

プロトコル情報

個々のデバイスは、ネットワークプロトコル層によってどのように利用されるのかを 示している。

最大転送単位 (mtu)

そのネットワークで送信可能な最大パケットのサイズ。ただし、それに付加される リンク層のヘッダ部分は含まれない。この最大値は、IP などのプロトコル層が送信 パケットの適正サイズを選択する際に利用される。

ファミリー (family)

ファミリーとは、そのデバイスがサポートできるプロトコルファミリーを指す。すべて の Linux ネットワークデバイスでサポートされるファミリーは、AF_INET、すなわち インターネットアドレスファミリーである。

タイプ (type)

ハードウェアインターフェイスタイプとは、ネットワークデバイスが取り付けられた メディアを表している。Linux のネットワークデバイスがサポートするメディアの タイプは多種に渡る。それには、イーサネット、X.25、トークンリング、SLIP、PPP、 アップルローカルトークなどが含まれる。

アドレス (Address)

device データ構造体は、IP アドレスを含めて、そのネットワークデバイス に関係するいくつかのアドレスを保持する。

パケットキュー (Packet Queue)

これは、ネットワークデバイス上で転送を待っている sk_buff パケットのキューである。

サポート関数 (Support Functions)

個々のデバイスは、そのデバイスのリンク層に対するインターフェイスの一部として、 プロトコル層がコールできるルーチンの標準セットを提供する。それには、設定ルーチ ンやフレーム送信ルーチン以外にも、標準フレームヘッダ付加や統計情報収集のルーチ ンも含まれる。それらの統計情報は ifconfig コマンドを使って見ることが できる。

ネットワークデバイスの初期化

ネットワークデバイスドライバは、他の Linux デバイスドライバ同様、Linux カーネルに組み込むことができる。組み込まれた個々のネットワークデバイスは、 device データ構造体によって表され、その構造体 は、ポインタのリストである dev_base に よってポイントされたネットワークデバイスのリスト内に置かれる。 ネットワーク層は、デバイス固有の仕事が必要な場合、ネットワークデバイスに関する 多数のサービスルーチンのひとつを呼び出すが、そうしたルーチンのアドレスは device データ構造体内に保持されている。しかし、適切な初期化が済むまでは、 device データ構造体は、初期化もしくは検出ルーチンのアドレスしか保持し ていない。

ネットワークデバイスドライバに関して解決すべき問題はふたつある。まず、 Linux カーネルに組み込まれたネットワークデバイスドライバのすべてが制御すべき デバイスを持っているわけではないことである。もうひとつは、システム上の イーサネットデバイスは、それらの基礎になるデバイスドライバとは無関係に、 /dev/eth0/dev/eth1 等と呼ばれることである。ネットワーク デバイスが存在しないという問題は、簡単に解決できる。個々のネットワークデバイス の初期化ルーチンが呼び出された際に、それらのルーチンは、駆動すべき実際のコン トローラを特定できたかどうかを報告するステータス情報を返す。ドライバがデバイス を見つけられなかった場合、 dev_base によって ポイントされる device 構造体のリスト内の デバイスのエントリは削除される。ドライバがデバイスを検出できた場合、device データ構造体の未設定部分に、そのデバイス情報とネットワークデバイス ドライバ内のサポート関数のアドレスとを書き込む。

もうひとつの問題である、イーサネットデバイスに標準的な /dev/ethN デバイススペシャルファイルを動的に割り当ててしまうという点については、もう少し 手際よく解決される。デバイスリストには 8 個の標準エントリがあり、順に eth0, eth1 等から eth7 まで並んでいる。初期化ルーチンは全部に 共通であり、カーネルに組み込まれたイーサネットドライバを、合致するデバイスが 見つかるまで順番に試していく。ドライバが該当するイーサネットデバイスを見つけた とき、ドライバが所有することになった ethN device データ構造体に情報を書き込む。そして、この時同時に、ネット ワークデバイスドライバは、制御する物理デバイスを初期化し、使用する IRQ や(もし 必要なら) DMA チャンネル等を決定する。ドライバは、制御すべき複数のネットワーク デバイスを検出することがあり、その場合は、複数の /dev/ethN device データ構造体を取得することになる。8 個すべての /dev/ethN devicd が割り 当てられると、それ以上のイーサネットデバイスの検出は行われない。


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