1.入門


  1. デバイスドライバに関する一般的な考慮事項

    1. デバイスドライバとは?

      デバイスドライバとは、OSの起動時にメモリ上に読み込まれて常駐し、 周辺装置からのハード的な割り込みによって駆動される、OSに付属する 一種のサブルーチン群です。IBMのPCのマニュアルでは 「装置駆動ルーチン」などと呼ばれています(した?)。

    2. デバイスの種類

      デバイスには、以下に示すように、主に2つのタイプがあります。

      • ブロック・デバイス

        ある決められた単位(ブロック)でデータの入出力を行うデバイスです。 「ファイルシステムを構成できるデバイス」と考えてください。 つまり mount(8) できるもの、ですね。

      • キャラクタ・デバイス

        1バイトずつデータの入出力を行うデバイスです。今回の「SCSI ターゲット・ドライバ」はこのモデルで作りました。このドライバは、 アプリケーションがSCSIのコマンドを直接解析して動作するという、 「全く汎用性のない代物」です。

    3. デバイス・ドライバの構造

      デバイスドライバは、以下に示す3つの部分から構成されます。

      • 初期化ルーチン

        OSの起動時に、上位(OS)から1度だけコールされる部分です。 ハードウェアの存在チェックを行った後、自分自身をデバイスドライバ としてOSに認識させます。

        当該ドライバの場合、この初期化関数だけをグローバル関数とし、 カーネルの他の関数(/usr/src/linux/drivers/char/mem.c)から 呼ぶように mem.c を修正しました。それ以外の内部関数/変数は すべて static としました。この理由は、カーネルは膨大な オブジェクト・モジュールをリンクして作られるため、関数を グローバルにしてしまうと名前の衝突が起こり、リンクで失敗する ことが考えられるので、これを防ぐためです。

      • トップハーフルーチン

        上位から(間接的に)コールされる部分です。アプリケーションが open(2) や read(2) のシステムコールを発行すると、OSの デバイスドライバ・カーネル・インターフェース(DKI)機能を 通じてOSから当該関数がコールされます。OSからの周辺装置との 入出力要求を実行し、その結果をOSに返します。図示すると、

        ユーザ・アプリケーション


        システム・コール・ライブラリ(libc)


        カーネルのシステムコールの受け口


        トップハーフルーチン

        となります。

      • ボトムハーフルーチン

        ハードウェア割り込みによって駆動される部分です。

    4. 上位APとのインターフェース

      上位アプリケーション(以下、APといいます)は、システムコールという OSとのインターフェースを使ってファイル単位の入出力要求を行います。 上位APからはデバイスドライバの存在は見えません。唯一、 /dev/xxx というデバイスを仮想化したファイルが見えるだけです。上位APは、 open(2) でこのデバイス・スペシャル・ファイルをオープンします。

      また、ドライバの種類によっては、デバイスドライバ・インターフェース (DDI)という受け口を設けて、上位APからの直接的な指令を受けられる ものもあります。このような指令は、通常のREAD/WRITE以外の、 ドライバ内部の状態通知・設定変更を指示するものが多いです。私が作った ドライバも、ioctl(2) というDDIを利用してAPとのやり取りを行いました。

    5. コーディングに関する制限事項

      デバイスドライバ内部では、OSが提供する read(2) などのシステムコール や printf(3) などのライブラリ群(実体は libc.* にある *らしい* です) がいっさい使えません。また、上位APがまだ起動していない(=存在しない) 状態もありうるなど、一般的なサブルーチン・ライブラリとはかなり異なった 環境を持ちます。

  2. UNIXでのアプリケーション開発に関する一般的な考慮事項

    UNIXシステムはマルチタスク環境であり、自分が作成したAP以外に 多くのAPまたはデーモン(システムサービスプログラム)が動作しています。 イベント待ちを行う場合、DOSアプリケーションのようにビジーウェイト (イベントが発生するまでその場でループすること)を行うと、他のプロセス に制御が移りにくくなり、システム全体のパフォーマンスが落ちます。 このため、イベントがなかったら、スリープなどにより明示的に他のプロセス に制御を譲ってやる必要があります。このほかに、他のプロセスに制御が移る (自プロセスがスリープ状態になる)機会は次の場合があります。

    • システムコールを発行した場合。

    • 何らかのハードウェア割り込みがかかった場合(の一部)。

    • 今回のタイムスライスを使い切った場合。

  3. UNIXでのデバイスドライバ開発に関する一般的な考慮事項

    1. トップハーフルーチンにおけるイベント待ちの方法

      あるイベント(入出力の完了、状態の遷移など)を待つ場合、極力 ビジーウェイトしないで「スリープ」するようにします。ただし、 タスク・スイッチング時間より早くイベントが発生することが わかっている場合はその限りではありません。イベント割り込み (ボトムハーフ)ルーチンでは、上位に対して結果を返したい状態 になったら、それまで「スリープ」していたプロセスを 「ウェイク・アップ」(起こす)します。この時その関数が負の値を 返すと、(システムコールで止まっていた)アプリケーションへ戻り値 -1 が返り、関数の戻り値の絶対値が errno にセットされます。

    2. イベントが発生しなかった場合の処理

      スリープ状態で期待するイベントが発生しなかった場合、そのプロセス は起こしてくれるものがいなくなって完全に止まってしまい、リブート 以外に解除する方法はなくなります。これを防ぐために、OSのタイマー 割り込みを利用します。タイマー割り込みがかかった場合、(その原因に よって異常と判断したら)異常終了処理を行い、プロセスを「ウェイク ・アップ」します。また、期待する割り込みがかかった場合、忘れずに タイマーを解除します。

    3. ボトムハーフルーチンにおけるイベント待ち

      ボトムハーフルーチン(割り込み駆動関数群)においては、その コンテキスト(制御の流れ=その時点でドライバがどのプロセスの 一部となっているか)は不定なので、どのプロセスが実行中に割り込まれた かがわかりません。このため、ボトムハーフルーチンにおいては、 スリープを発行してはなりません。スリープは「プロセスに対して」 行うものです。