ドライバーの解剖学

繰り返しますが、オペレーティングシステム(およびマイクロコントローラー用のアプリケーション)の開発という従来の分野に戻って、ドライバーを作成します。



この分野でのいくつかの一般的なルールと規範を強調してみます。 いつものように-ファントムの例で。



ドライバーは、コンピューターハードウェアの特定のサブセットとの関係を担当するOSの機能コンポーネントです。



同じUnixの軽い手で、ドライバーはブロック指向とバイト指向に分けられます。 昔の古典的な例は、ディスクドライバー(操作-ディスクのセクターの書き込みと読み取り)とディスプレイドライバー(シンボルの読み取りと書き込み)でした。



もちろん、現代の現実では、すべてがより複雑です。 ドライバーはクラスの典型的なインスタンスオブジェクトであり、これらのクラスがさらにあります。 原則として、彼らは何らかの方法で読み取り/書き込みモデルのプロクラステンベッドでドライバーインターフェイスを絞ろうとしていますが、これは自己欺ceptionです。 ネットワークドライバーには「カードのMACアドレスを読み取る」メソッド(もちろん、 プロパティを介して実装できます )があり、USBドライバーにはUSB固有の操作が多数あります。 グラフィックスドライバーでさらに楽しい-いくつかのbitblt(startx、starty、destx、desty、xsize、ysize、operation)はよくあることです。



一般に、ドライバーのライフサイクルは次のように説明できます。







(一般に、私は昨年、オープンドライバーインターフェイス仕様のドラフトを書きました。 リポジトリドキュメントを参照してください。)



ドライバーを構築するための3つのモデルを知っています。







デバイスベースのポーリングドライバー



このようなドライバーは、大きな悲しみがある場合、または非常に必要な場合にのみ使用されます。 または、2つのドライバーしかないシンプルな統合マイクロコントローラーシステムの場合。 たとえば、ネットワークが割り込みによって動作するシリアルポート<-> TCPインターフェイスコンバーターは、原則として、ポーリングのあるシリアルポートでも動作します。 余分な熱とエネルギーのコストを気にしない場合。



もう1つの理由があります。そのようなドライバーはほとんど破壊されません。 したがって、たとえば、Phantom OSでは、シリアルポートへのカーネルのデバッグ出力はそのように行われます。



ループでは、ポートを受け入れてバイトを受け入れ、バイトを転送する準備ができていることを確認し、演習を終了しました。



#define PORT 0x3F8 int dbg_baud_rate = 115200; void debug_console_putc(int c) { while(! (inb(PORT+5) & 0x20) ) ; if( c == '\n' ) outb(PORT, '\r' ); outb(PORT, c); } void arch_debug_console_init(void) { short divisor = 115200 / dbg_baud_rate; outb( PORT+3, 0x80 ); /* set up to load divisor latch */ outb( PORT, divisor & 0xf ); /* LSB */ outb( PORT+1, divisor >> 8 ); /* MSB */ outb( PORT+3, 3 ); /* 8N1 */ }
      
      







ご覧のように、このようなドライバーは、デバイスの準備ができていることを期待してプロセッサを貪ります。 これは、ドライバー自体の速度が重要でない場合は修正できます。



  while(! (inb(PORT+5) & 0x20) ) yield(); //    ,     
      
      







しかし、もちろん、一般に、これは役に立たない(上記の場合を除いて:)モデルです。



割り込みドライバー



このようなドライバーの一般的な構造は次のようになります。



 struct device_state dev; dev_write( buf ) { dev.buf = buf; if( !dev.started ) dev_start(); cond_wait( &dev.ready ); } dev_interrupt() { dev_output(); } dev_start() { dev.started = 1; dev_enable_interrups( &dev_interrupt ); dev_output(); } dev_output() { if( buffer_empty() || (!dev.started) ) { dev.started = 0; dev_disable_interrupts(); cond_signal( &dev.ready ); // done return; } // send to device next byte from buffer out( DEV_REGISTER, *dev.buf++ ); }
      
      







実際、このようなドライバーは、それ自体に対して擬似スレッドを生成します。これは、デバイスからの割り込みの受信時にのみ有効な制御フローです。



ドライバは、別の書き込み要求を受信するとすぐに、割り込みをオンにし、デバイスへのデータの最初のバイトの送信を手動で開始します。 次に、着信スレッドがスリープ状態になり、転送の終了を待ちます。 または、非同期の作業が必要な場合に戻ります。 これで、ドライバーはデバイスからの割り込みを待ちます。 デバイスが受信したバイトを「ライブ」すると、ドライバーは割り込みを生成します。その間に、ドライバーは次のバイトを送信(および次の割り込みを待機)するか、操作を終了して割り込みをオフにし、dev_write()内で待機しているスレッドを「解放」します。



忘れられているもの



最新のドライバーモデルに移る前に、前のストーリーで(意図的に)見逃したことをリストします。



エラー処理


擬似コードは、I / Oの成功をチェックしません。 実際のデバイスに障害が発生したり、メディアの障害が報告される場合があります。 LANポートからケーブルを取り外しましたが、ディスクで不良ブロックが発生しました。 ドライバーは検出して処理する必要があります。



タイムアウト


デバイスが破損し、割り込みでリクエストに応答しないか、レディビットがセットされない場合があります。 そのような場合、ドライバーはタイマーイベントを要求して、それを「“迷」から解除する必要があります。



死のリクエスト


私たちを取り巻くOSがこれを許可している場合、I / O要求が入ったドライバーに入ったスレッドが単純に強制終了できるという事実に備える必要があります。 これは、ドライバーに致命的な結果をもたらさないはずです。



同期する


簡単にするため、同期プリミティブとしてcondに言及します。 実際のドライバーでは、これは不可能です-condは同期ポイントで囲むミューテックスを必要とし、割り込みでは、どのミューテックスは不可能です! 最新のモデルである独自のスレッドを持つドライバーでは、ユーザースレッドとドライバースレッドを同期する手段としてcondを使用できます。 ただし、割り込みとの同期はスピンロックとセマフォのみであり、セマフォの実装は、割り込みからセマフォをアクティブ化(開く)準備ができている必要があります。 (Phantomでは、そうです)



スレッドベースのドライバー



I / Oを実行する独自のスレッドを持つという点で、前のものとは異なります。



 dev_thread() { while(dev.active) { while(!dev_buffer_empty()) cond_wait(&dev.have_data); while( /* device busy */ ) cond_wait(&dev.interrupt); dev_enable_interrupts(); // send to device next byte from buffer out( DEV_REGISTER, *dev.buf++ ); } } dev_interrupt() { dev_disable_interrupts(); cond_signal(&dev.interrupt); }
      
      







このようなドライバーの利点は、割り込みハンドラーよりもスレッドからはるかに多くの余裕があることです。メモリの割り当て、ページテーブルの管理、および一般的にカーネル関数の呼び出しができます。 中断から、長い、特にブロッキング操作を行う余裕はありません。



3番目の中間モデルがあり、ドライバには独自のスレッドはありませんが、I / O要求スレッドから同じことを実行することに注意してください。 しかし、最初に、彼らが彼女を殺すことができる段落を見てください、第二にそれは田舎者です:)、そして第三に、彼女(スレッド)は常にこれを望んでいません。 他の人は非同期サービスを希望します。



ブロックI / O、仕分け、フェンス



通常、ディスクドライバーは入力にリクエストのキューを持ちます。OSはI / Oのリクエストをバッチで生成し、ドライバーレベルのリクエストはすべて非同期です。 同時に、優れたドライバーには、リクエストを処理するための独自の戦略があり、さらに、サービスは受信した順序ではありません。



実際、要求が到着した順に通常のディスクデバイスでリクエストを実行すると、ドライブヘッドはディスク上でランダムに移動する必要があり、I / Oが遅くなります。



通常、ドライバーは要求番号をブロック番号でソートし、ディスクヘッドが外部トラックから内部トラックへ、またはその逆に順次移動するようにそれらを処理します。 それは非常に役立ちます。



ただし、リクエストのすべてのペアを交換できるわけではありません。 ファイルシステム(またはその類似物)がディスク上のデータの整合性を保証する必要があると判断した場合、特定のグループの要求が完了することを確認したいと考えています。 これを行うには、特別なI / O要求が要求キューに挿入され、それ自体への要求とそれ以降の要求を混合することを禁止します。



また、ブロックNの書き込み要求と同じブロックの読み取り要求を交換することはお勧めできません。 ただし、これは合意事項です。



All Articles