毎回長い記事を書いてしまうバスクリンです。 今回は、コンピュータの歴史から少しだけ外れて、OSとハードウェアの依存性について話をしてみようと思います。
ドッグイヤーの幕開け
前回までの話で、コンピュータの進化にはハードウェアの進化が大きく影響していると話をしました。ハードウェアの進化はとても速く、1990年ごろからは「ドッグイヤー」と言われるほどIT業界の進化は急速に加速しました。OSはハードウェアを操作するアプリケーションですが、ハードウェアの種類や性能が全く異なるものをどう制御していくようになったのでしょうか?
OSが登場した当初は、デバイスドライバという仕組みでハードウェアを直接制御していました。OSメーカーが一般的なハードウェアの動作をエミュレートした「標準ドライバ」と、メーカー独自の機能を盛り込んだ「独自のドライバ」が混在していました。
標準ドライバはOSを作成した時点で一般的なハードウェアのドライバ
独自のドライバはメーカーがOSを理解して独自に作成したドライバ
どちらも一長一短がありました。
標準ドライバは、新しく登場するハードウェアについていけませんでした。 独自ドライバは、OSの詳細まで理解しなければ作成できませんでした。 アプリケーション開発者は、そのハードウェア(ドライバ)に合ったプログラムを作成しなければなりませんでした。
当時はドライバ起因の依存性による不具合がかなり多かったのです。OSをインストールする時にドライバがハードウェアを10回に1度くらいしか認識せず、徹夜でOSのインストールをしたこともありました。
この問題を解決するために登場したのが「カーネル」と「HAL」です。
カーネルの大事な仕事
カーネルという言葉を聞いたことがある方は多いと思います。でも、その仕組みを詳細に知っている人は少ないと思います。ざっくりと言うと、アプリケーションとハードウェアの架け橋をするための仕組みで、プロセスやスレッド、仮想メモリー、デバイスドライバなどを管理するOSのコアの部分になります。
アプリケーションはこのカーネルが提供するAPIやフレームワークに対してプログラムを作ればよく、ハードウェアを直接意識することはほとんどありません。
下図はWindows 7の主要な層(レイヤー)を簡潔に表した図です。
カーネルの下に「HAL」があり、その下にハードウェアがいるイメージです。この構成は、Windows NTの時代に確立され、現在もこの構造が使われています。基本的な部分は20年くらい前とあまり変わっていないので、ここをしっかりと勉強すれば色々な点で「強いエンジニア」になれます。
Linixや他のOSにも似たような仕組みはありますが、OSによって異なるので興味のある方は調べてみてください。
依存性を和らげるHAL
HALは「Hardware Abstraction Layer」と言い、日本語にすると「ハードウエア抽象化レイヤー」と言います。OS(カーネル)は、アプリケーションからの命令(ファイル操作や印刷要求など)を、このHALに対して処理します。ハードウェアを直接操作することはありません。
また、現在の「ドライバ」は、このHALからの命令に対して同期を取りながらハードウェアを動作させるように作られています。OSが何か、そのうえで動いているアプリケーションが何かなど、一切考える必要はありません。独自の機能を追加したい場合は、HALに用意されている拡張機能の仕組みさえ理解していれば、独自の機能を追加できます。
「依存性」があると、双方がお互いの動作を熟知していないと動作ができません。
また、どちらかの動きが変更されてしまうと、動作しなくなったりしてしまいます。
この「依存性」という問題を解決する手法が「中間レイヤー」という概念です。 中間レイヤーを用意することで、お互いを詳細に知らなくても製品を作ることができるようになりました。
依存性を解消するレイヤー
HALのような依存性を緩和させるレイヤーを定義しているモデルで有名なモデルがあります。「OSI参照モデル」です。みなさんも一度は学校や調べものをしていて見たことがあると思います。
これは現在のインターネットなどの技術の根底にあるモデルであり、このモデルがなければ今日のインターネットの発展はなかったと言っても過言ではないモデルです。
これは、各層(レイヤー)の役割と上下のレイヤーに対して提供するインターフェースや動作を定義したものであり、強制力はありません。ただし、このモデルに合わない(要件を満たしていない)ハードウェアやプロトコル(ソフトウェア)は、市場からも消えてなくなる傾向にあります。言わば、インターネット技術におけるデファクタスタンダードの位置付けです。
この他にも、動画や音楽再生は「コーデック」のレイヤーと、フレームをバッファリングするレイヤー、アプリケーションとしてユーザに表示するレイヤーに分かれて設計・開発されています。コーデック開発者は、再生ソフトがAdobeのQuiqTimeであろうが、DVDプレーヤーに同封されているオマケのDVD再生ソフトであろうが、Youtubeのストリーミングであろうが、そんなことは気にせずに開発しています。
中間レイヤーの仕様が明確になっていれば、それ以外の開発に集中できるので、効率も良いのです。
プログラムと依存性
ここまでの話を聞いてプログラムをしている方は気が付いたと思いますが、プログラムの依存性を解消する方法として「インターフェース」や「抽象クラス」があります。依存性のある「クラス」や「レイヤー」にそれぞれが理解できるインターフェースを用意しておけば、めんどくさい処理はあまり考えなくてよいって言う「アレ」です。
よく使う依存性の解決策として「デザインパターン」があります。
デザインパターンを勉強している人は、クラス図とかを見て理解していくと思いますが、まずこの「依存性」という部分に着目してみると理解が早いかもしれません。どことどこが「依存」していて、それを解消するためにどこを「抽象化」しているか。
こんな感じでクラス図を見てみてください。依存性の解消方法がわかってくれば、システム設計やクラス設計のスキルが大きくアップします。
ユニットテストで使われる「スタブ」や「モック」の考え方も「依存性」を解消する手法です。リフレクションなどを使ったDI(dependency injection:依存性の注入)という考え方も、好きな時に必要な「依存性」を動的に組み込む手法です。
SEやPGはこの「依存性」をいかに簡潔に分離・設計できるかが重要になってきます。
また、よくある依存性をパターン化して「モデル」や「フレームワーク」をイメージできるようになれば面白いアイデアが生まれるかもしれません。
まとめ
いかがでしたでしょうか?今回はハードウェアやプログラムの依存性を解消するために、インターフェースや中間レイヤーを使う手法を説明してみました。
この業界で仕事をしていると、「現場のプログラムが依存性高くてテストしにくい!」とか、「ここの関係性をみんながわかるようにもっと簡潔に記述できないだろうか?」と感じる人も多いと思います。 そんな時に今回の「依存性」の話を思い出してもらえれば嬉しいです。