有限のハードウェア資源を仮想インスタンスが共有することにより、
ほぼ無限のハードウェアがあるもののように見せるパターン
Virtual Instance Pattern (
有限の対象デバイスがあたかもたくさん存在するようにみせる 仮想的なデバイスを、仮想インスタンスと呼びます。 通常、対象デバイスの一部(単数・複数)をクラス化して、仮想化します。 たとえば、音声出力が1チャンネルしかないときに、 仮想音声出力を2つ作り、 それぞれのタスクがそれぞれの仮想音声出力に出力し、 ソフトウェアで2つのチャンネルを1つに合成したとします。 その仮想音声出力が仮想インスタンスです。
仮想インスタンスは、記憶装置がある限り無限に作ることができます。
仮想インスタンスを合成した出力を得るための処理に
かかる時間が現実的でなかったりすることもありますが、
理論的には無限に作ることができます。
仮想インスタンスが少ないときは、有限のデバイスにすべて 割り当てることができますが、デバイスの数(最大値)を 超えたとき、デバイスを共有する必要が出てきます。
対象デバイス割り当ての基本的は方針は、次のとおりです。
ワークを共有するということは、そこに排他制御が必要になります。
仮想インスタンスは、ソフトウェアであるとは限りません。 たとえば、3チャンネルを持った音声出力装置を VIP にした場合 (チャンネルを仮想インスタンスにして無限のチャンネルを 扱うことができるようにした場合)、 いくつかのチャンネルはデバイスに割り当てられ、 残りのチャンネルはソフトウェアのみになります。
仮想インスタンスと対象デバイスには、常に多対1の関係があり、 ツリー構造をとります。デバイスが複数ある場合、 ユーザ見えには1段のツリーですが、 内部的には2段のツリーになります。
物理ドライバは、物理チャンネルに割り当てられているときに使われ、 物理チャンネルの数だけあらかじめ生成しておいて破壊はしません。 仮想ドライバは、ワークの物理チャンネルに割り当てられているときに 使われ、必要になるたびに生成と破壊をします。 上の図で、ソフト処理、ハード処理の部分は、あとで説明する 「同期」の章を参照してください。 ワーク以外の物理チャンネルにつながっている(データ領域)は、 まれに省略されることがあります(ライトバック同期のとき)。 物理チャンネルは、別のデバイスになっていることもあります。
[ 仮想インスタンス= Vip_Instance クラス、 対象デバイス= Vip_Device クラス、 デバイス・プール= Vip_DevPool クラス ]
アプリケーションは、「各種仮想インスタンス」を生成して操作するだけです。
仮想インスタンス・ライブラリが提供するものと、 各種ライブラリが提供するものと、ドライバが提供するものに分かれます。
仮想インスタンス・マネージャは、クラスは仮想インスタンス・ライブラリが
提供しますが、インスタンスは、各種ライブラリが
静的変数として提供します。
仮想インスタンス・マネージャは、仮想インスタンスのクラスの数だけ、
デバイス・プールを所有します。また、ハード・デバイス・ラッパを
デバイスの数だけ所有します。
仮想インスタンスがどのデバイスに割り当てるかの設定は、 インスタンス生成時、インスタンス削除時、優先順位変更時に 行う必要があります。
仮想インスタンス生成時は、フリー・デバイス・プールから 空いているデバイスを検索して割り当てます。 もし、空いているデバイスが無いときは、 優先順位の低い仮想インスタンスをワークに割り当て直し、 それによって空いたデバイスに割り当てます。 これは、後で生成したものを優先的にデバイスに割り当てて、 最近使われたものは近いうちに再び使われるという キャッシュの法則によるものです。
仮想インスタンスを生成(初期化)するときは、次のようにします。
Vip_Instance* Vip_Instance_new( Vip_DevPool_Pac devInf ) { Vip_Instance* this; Vip_DeviceI dev; Vip_DevPool* pool; fast_malloc( &this, Vip_Instance ); /* デバイス全体を初期化して、デバイス・プールを返す */ if ( bFirst ) { pool = Vip_DevPool_new_Vip_DevPool( devInf ); Vip_Instance_setDevice( this, dev, pool ); } /* デバイス・プールから、対象デバイスを取得する */ dev = Vip_DevPool_alloc( pool ); /* 仮想インスタンスと対象デバイスとデバイス・プールをリンクする */ Vip_Device_setInstance( dev.obj, this ); /* 初期値の設定(デフォルトのときは、それを表す定数を設定する) */ this->adr = (int)NULL; this->width = -1; this->height = -1; this->bpp = -1; Vip_DeviceI_init( dev, this, &m->devices ); return this; } |
対象デバイスが初期化済みで、仮想インスタンスを生成するときは、 次のようにします。
Vip_Instance* Vip_Instance_new( Vip_Manager* m ) { Vip_Instance* this; Vip_Device* dev; /* デバイス・プールから取得する */ fast_malloc( &this, &m->instances, Vip_Instance ); dev = Vip_DevPool_alloc( &m->devices ); /* 初期値の設定(デフォルトのときは、それを表す定数を設定する) */ this->m = m; this->adr = (int)NULL; this->width = -1; this->height = -1; this->bpp = -1; /* 仮想インスタンスとデバイスとセレクタをリンクする */ Vip_Instance_setDevice( this, dev, dev->selector ); Vip_Device_setInstance( dev, this ); Vip_DeviceI_init( dev, this, &m->devices ); Vip_Instance_sync( this, &m->devices ); return this; } |
仮想インスタンス破壊時は、物理チャンネルを開放します。 もし、ワークに割り当てられていた仮想チャンネルがあれば、 それを開放した物理チャンネルに割り当てます。
優先順位変更時は、それぞれのルールに従って 物理チャンネルを割り当てなおします。 割り当てなおすことにより、デバイスが変わることがあります。
仮想インスタンスと対象デバイスの状態が一致するように 対象デバイスの設定を行うことを同期と呼びます。
ソフトウェアとハードウェアの間で選択同期をするときは、
属性値(対象デバイスのレジスタ)のすべてを
コピーする必要があります。
そのため、あとで説明する「同期判定」を行わなければ
処理効率が悪くなります。
ソフトウェア同士で選択同期を取るときは、すべての属性のコピーではなく、
独自の構造体へのリンクで済ますことができるので、
処理効率が悪くなるとは限りません。
同期は、Vip_Instance_sync 関数を呼び出すことで実行します。
合成同期、または変換のある選択同期を行うときは、 ワークのデバイスを使うこともあります。
合成同期がハードウェア処理で行われているとき、 トリガが自動的に行われていることがあります。 ソフトウェアと組み合わせた合成同期にした場合、 明示的にトリガする(関数を呼び出す)か、 処理ごとに合成同期を行うか、 タイマ割り込みで定期的に合成同期を行います。 明示的にトリガする場合、し忘れることがあるので、 同期範囲のステータスを表示する機能をつけると良いでしょう。
仮想インスタンスは、複数のインスタンスを使えるようにする 責務があるので、排他制御のクリティカル・セクションを 仮想インスタンスの操作関数の内部に作ります。
同期方法は、ライトバック、ライトスルー、ライトフラッシュが
ありますが、通常、ライトフラッシュを使用します。
なぜなら、それほど速度が遅くならないわりに、
ドライバが作成しやすいためです。
|
仮想インスタンスのインプリメント・クラスは 次のように作成します。
/************************************************************************** * <<< [Ins13] Instance 13 >>> ***************************************************************************/ struct _Ins13 { Vip_Instance inherit_Vip_Instance; /* device = Dev13 など */ int attrA; int attrB; }; void Ins13_init( Dev13*, int attrA, int attrB ); /* サブクラス独自のもの */ |
Vip_Instance クラスから継承します。
仮想インスタンス独特の属性をメンバ変数に持ちます。
各種ライブラリは、Vip_Instance_sync 関数を呼び出して、
デバイスと同期を取ります。この関数は、必要な部分だけハードに設定
するので、一瞬で抜けることもあります。
/************************************************************************** * <<< [Dev13] Device 13 >>> ***************************************************************************/ struct _Dev13 { Vip_Device inherit_Vip_Device; /* vip = Ins13 */ }; void Dev13_init( Dev13* ); /* デバイスの初期化関数 */ void Dev13_sync( Dev13* ); /* 同期を実行する関数 */ /* Vip_DeviceI インターフェイス(Inf)の取得関係 */ Vip_DeviceI_Sc* Dev13_getSc_Vip_DeviceI_Sc(void); Vip_DeviceI Dev13_inf_Vip_DeviceI( Dev13* ); Dev13* Dev13_by_Vip_DeviceI( Vip_Device ); |
Vip_Device クラスから継承します。
Vip_Device クラスは、仮想インスタンスへのポインタを持っていますが、
そのインプリメント・クラス(上の例では Ins13)をコメントとして明示します。
上記の Dev13_init 関数は、 この関数が呼ばれる前に設定された仮想インスタンスを Vip_Device_getInstance マクロで取得し、 デフォルト定数が設定されている仮想インスタンスの属性に、 デフォルト値を設定します。 そして、Dev13_sync 関数を呼び出してハードウェアの初期化を行います。
上記の Dev13_sync 関数は、Vip_Instance_sync 関数から呼び出される
Vip_Device_sync 関数のオーバーライド関数です。
設定が必要かどうかをこの関数が呼び出される前に判断しているので、
この関数は、Vip_Device_getInstance, Vip_Device_getSelector を使って
取得した設定値を、実際にデバイスに設定するだけで済みます。
ソフトウェア・デバイスの場合、処理を行うときに device->vi->attr で参照できるので、 Vip_Device_sync 関数のオーバーライド関数では、 device->vi にリンクのみすれば十分です。 しかし、ソフトウェア・デバイスも状態を持つことになります。
コールバック関数は次のようになります。
void DevPool_alloc(); 未使用の物理チャンネルの取得
void DevPool_allocWork(); ワーク用の物理チャンネルの取得
void DevPool_free(); 物理チャンネルの開放
デバイス・プールの初期化は、ドライバの初期化の時に行います。 デバイス・プールには、未使用状態の物理デバイス(?)
使用されていない物理チャンネルは、 デバイス・プールに登録しておきます。 デバイス・プールは、通常ダブルリストで実装されます。(?)