デバイスドライバ

概要

ワープロソフトや表計算ソフトなどのアプリケーションなどが、モニター、プリンター、ネットワークカードなどのデバイスを利用する際、OSが提供する共通化されたAPI（アプリケーション・プログラミング・インターフェイス）によってデバイスの機能を利用できるようにしておく。そして、抽象化されたAPIとハードウェアとの間の対応を、各ハードウェア用のデバイスドライバが受け持つ。
このような仕組みを採用することで、結果的にハードウェアの差異を吸収することができる。ソフトウェアプログラマは、特定のハードウェアに対応する細々としたソフトウェアを書かずとも、APIにあわせたアプリケーションプログラムを作ることで、作成したソフトウェアから不特定多数のハードウェアを利用することができる。

標準ドライバ

広く共通化が進んだハードウェア（キーボード、マウス、USBなど）では、OS内部に標準ドライバ（ジェネリックドライバ）が含まれている場合が多い。標準ドライバがサポートしないハードウェアに関しては、一般に、そのハードウェアを提供するメーカー（ベンダー）が、デバイスドライバを製品にフロッピーディスクやCD-ROMで添付するか、あるいはインターネット上で配布する。
プリンターやグラフィックスカードなど一部の製品では、ベンダーの提供するドライバと標準ドライバの両方が用意されている場合がある。標準ドライバは最低限の機能を有する安定したドライバ、ベンダー提供のドライバはそのハードウェアのもつ機能を最大限利用できる最適化されたドライバである場合が多い。

APIとの関係

ドライバは、OSの一部として機能する。
ユーザープロセスでのAPI呼び出しをきっかけに、ドライバのコードが呼び出される。しかしドライバのコード自身は、ユーザープロセスではなく、カーネルコードの一部として動作する。
上で言う抽象化されたAPIとは、ほとんどの近代的なOSでは、open, read, write, ioctl, close というAPIに統一化されている。歴史的にいうと、これらのAPIは、記憶装置上のファイルにアクセスするためのAPIであるが、これがデバイスに対してもアクセス可能なように拡張された形で提供されているのが、一般的な作りである。すなわち、デバイスに対して、入出力の準備をするopen処理、デバイスからデータを入力するためのread処理、デバイスにデータを出力するためのwrite処理、デバイスに対して特別な処理を行うためのioctl処理、入出力処理を終えるためのclose処理、などである。
read, writeで実際に何が行われるかは、デバイスごとに異なる。例えば、プリンターに対してwriteを行うと印字されるが、サウンドデバイスに対してwriteを行うと、音が鳴る。マウスに対してreadを行うと、マウスの移動量が読み出せる。デバイスによっては、read, writeの片方にしか意味がない場合も多い。例えば、プリンターに対してreadを行うと、何も行われない場合がほとんどである。read, writeでは何もせずに、実際の入出力をioctlだけで行う、という実装も良く用いられる。

内部構成

デバイスドライバの一般的な内部プログラムの構成は、アプリケーションのAPI呼び出しをきっかけに起動されるディスパッチコードと、ハードウェア割り込みにより起動される割り込み処理コード、の2つからなる。
割り込みに対してはさらに、純粋な割り込みルーチンと、OSのタスクスイッチングのタイミングで呼び出される後処理コードの、2段階に分けて実装する作りになっているケースが多い。これは、ハードウェア割り込みルーチンからは、可能な限り早く復帰して欲しいという要望があるため（そうしないと、他のハードウェア割り込みが入れなくなる）、多少時間がかかっても良い処理は、カーネル内で余裕ができたタイミングまで後回しにして実行しよう、という考えに基づいた構成手法である。（後処理コードは、Windowsでは、DPC (Deferred Procedure Call) 、Linuxでは、softirqあるいはTaskletと呼ばれる部分に相当する。また、過去のLinuxの実装では、Bottom Halfと呼ばれた部分である。）
最近のOSでは、ハードウェア同士で機能が似たものは、まとめてひとつのクラス（デバイスクラス）として扱う仕組みも存在する。この場合のドライバは階層構造になっており、あるデバイスクラスで共通の処理をする上位ドライバ（クラスドライバ）はOS側で供給され、ハードウェアを提供するメーカーでは、下位のドライバを、デバイス個別のミニドライバとして作製する。これにより、ドライバの開発工数を削減できるようになっている。
例えば、シリアルポートではXON/XOFFのようにシリアルポート全般に共通する標準通信プロトコルに対応する必要がある。これはシリアルポートの論理層（クラスドライバ）で管理することになる。しかし、物理層（ミニドライバ）は特定のシリアルポートのチップと通信できなければならない。16550 UART というハードウェアはPL011とは異なる。物理層はそういったチップ固有の差異に対応している。慣例的に、OSからの要求はまず論理層に対して行われる。そして論理層から物理層が呼び出され、OSの要求をハードウェアが理解できる形にして実行する。逆にハードウェア周辺機器がOSに返答しなければならないとき、まず物理層が対応して論理層を呼び出す。
デバイスドライバがクラスごとに共通化されることで、特定のハードウェアが独自に持っている機能が使えなくなる、あるいは使いにくくなるという欠点もある。新規技術開発で出現したハードウェアでは、その機能をどのようにOSが抽象化するか（クラス化するか）が決まるまで、ミニドライバの開発が待たされることもある。この場合は、ハードウェア毎にネイティブなデバイスドライバを、階層化されないドライバ（モノリシック ドライバ）として作成すれば、早期にドライバを提供することができる。
モノリシックドライバでは、ioctlに、そのハードウェア独自の機能を使うための仕掛けを組み入れることも可能であり、これをあやつる専用のアプリケーションを作れば、さらにきめ細かなハードウェア制御を実現することもできる。
デバイスドライバの内部構造は、OSごとに大きく異なる。
Windowsでは、Windows 98以降、様々なバージョンのWindowsごとにドライバを書く手間を省くために、Win32 ドライバモデル (WDM) アーキテクチャが導入された。
Windowsでは、ドライバの最下層にハードウェアを抽象化する層である Hardware Abstraction Layer (HAL) を設けて、プラットフォームによる違いを吸収する仕組みも存在する（386, 486, Pentium, Alpha, SPARC, IA-32, IA-64, EM64Tなどといった、CPUの違い、CPUアーキテクチャの進化を吸収する）。
Linuxでは、デバイスドライバをLinuxカーネルの一部として構築することもできるし、Linuxカーネルとは別のモジュールとして構築することもできる。MakedevにはLinuxでの周辺機器の一覧が含まれており、ttyS（端末）、lp（パラレルポート）、hd（ディスク）、loop（ループデバイス）、sound（mixer、sequencer、dsp、audio を含む）など様々な周辺機器が定義されている。
ロード可能なデバイスドライバは、Windowsでは ".sys"、Linuxでは ".ko" という拡張子のファイルになっている。この形のデバイスドライバは必要なときだけロードできるという利点があり、カーネルメモリの節約につながる。
以上は、ハードウェアに合わせて、ドライバを各種OSに対して用意するという方針である。これとは逆に、PDAなどの開発現場では、ハードウェアの仕様をできるだけ同じにすることでデバイスドライバの開発の手間を省く、という方針が採用されているケースもある。

開発

デバイスドライバ開発には、そのプラットフォームでのハードウェアとソフトウェアについて詳細まで理解している必要がある。ドライバは高い特権を与えられた環境で動作するので、間違った動作をすると破壊的な結果を招く。対照的に現代のオペレーティングシステムでのユーザーレベルのソフトウェアは、システムの他の部分に影響せずに停止することができる。ユーザーモードで動作するデバイスドライバであっても、バグがあればシステムをクラッシュさせることがある。そういった要因から、問題の診断も困難で危険なものとなる。
したがってドライバを書く仕事は、ハードウェア開発企業のソフトウェア技術者の仕事となることが多い。これは、部外者よりもハードウェア開発元の方がそのデザインに関する情報をより多く得られるためである。さらに言えば、デバイスドライバを提供することで製品を最適な方法で使えることを保証するという意味もある。一般に論理層（クラスドライバ）はOSベンダーが書き、物理層（ミニドライバ）は周辺機器ベンダーが書く。しかし最近では FLOSS OS で使用するためにベンダー以外の者がデバイスドライバを書くことも増えている。その場合、ハードウェア製造業者がその周辺機器のインターフェイス仕様について情報を提供することが重要となる。リバースエンジニアリングでそういった情報を解明することもできるが、対応ソフトウェアが全くない状態ではそれも難しくなる。
マイクロソフトは品質の低いデバイスドライバによってシステムの安定性が損なわれるのを防ぐため、ドライバ開発の新たなフレームワーク Windows Driver Foundation (WDF) を開発した。WDFには User-Mode Driver Framework (UMDF) と Kernel-Mode Driver Framework (KMDF) がある。UMDFはユーザーモードで動作するドライバ開発用のフレームワークで、UMDFを使ったユーザーモードのドライバにバグがあったとしても、システム安定性に影響を与えない。KMDFはカーネルモードで動作するデバイスドライバ開発を扱うが、I/O操作のキャンセル、パワーマネジメント、プラグ・アンド・プレイのサポートなど問題を起こしやすい機能の標準的実装を提供している。
アップルはmacOS用のドライバ開発のオープンソース・フレームワーク I/O Kit を提供している。

カーネルモードとユーザーモード

Windowsでは、デバイスドライバはカーネルモード（x86 CPU のリング0）またはユーザーモード（x86 CPU のリング3）で動作する。ドライバをユーザーモードで動作させる最大の利点は安定性の向上であり、ユーザーモードのデバイスドライバは品質が悪くてもカーネルメモリを上書きしてシステムをクラッシュさせるということがない。一方、カーネルモードからユーザーモードに移行させると性能が低下するので、性能が要求されるデバイスドライバはユーザーモードに移行できない。
ユーザーモードのモジュールはシステムコールを使わないとカーネル空間にアクセスできない。

仮想デバイスドライバ

仮想デバイスドライバはハードウェア周辺機器をエミュレートするもので、特に仮想化環境で使われる。例えば、Windowsの動作しているコンピュータ上でMS-DOSプログラムを実行する場合や、Xenなどの上で動作するゲストOSの場合である。ゲストOSがハードウェアと直接やりとりできるようにするのではなく、仮想デバイスドライバがハードウェアをエミュレートすることで、VM内で動作するゲストOS（とその中のデバイスドライバ）が実際のハードウェアにアクセスしているかのような幻影を生じさせる。ゲストOSがハードウェアにアクセスしようとしたとき、ホストOS内の仮想デバイスドライバがそれに対応して呼び出される。仮想デバイスドライバはまた、VM内に割り込みなどのプロセッサレベルのイベントを擬似的に発生させることができる。
仮想化環境以外でも仮想デバイスが使われることがある。例えば、Virtual Private Networkでは仮想ネットワークカードが使われ、iSCSIでは仮想ディスクデバイスが使われる。仮想デバイスドライバの好例としてDaemon Toolsなどがある。