デバイスコントローラ#
デバイス間の差異を隠すために、各デバイスには「デバイスコントローラ」コンポーネントがあります。たとえば、ハードディスクにはハードディスクコントローラ、ディスプレイにはビデオコントローラなどがあります。
コントローラには 3 つのレジスタがあります。それらは「ステータスレジスタ」、「コマンドレジスタ」、および「データレジスタ」です。以下の図を参照してください。
これらのレジスタの役割は次のとおりです:
- 「データレジスタ」:CPU は I/O デバイスに転送するデータを書き込みます。たとえば、印刷する内容が「Hello」である場合、CPU はまず対応する I/O デバイスに H 文字を送信する必要があります。
- 「コマンドレジスタ」:CPU はデバイスに入出力操作を行うためのコマンドを送信します。その後、I/O デバイスに作業を委任し、タスクが完了すると、ステータスレジスタの状態を完了としてマークします。
- 「ステータスレジスタ」:CPU に作業中または作業完了であることを伝えるためのものです。作業中の状態である場合、CPU からデータやコマンドを送信しても効果はありません。前の作業が完了し、ステータスレジスタが完了とマークされるまで、CPU は次の文字やコマンドを送信することはできません。
CPU は、デバイスコントローラのレジスタを読み書きすることでデバイスを制御します。これにより、CPU が直接入出力デバイスを制御するよりも簡単で標準化された方法が提供されます。
さらに、入出力デバイスは「ブロックデバイス」と「キャラクタデバイス」の 2 つの大きなカテゴリに分けることができます。
- 「ブロックデバイス」はデータを固定サイズのブロックに格納し、各ブロックには独自のアドレスがあります。ハードディスクや USB などが一般的なブロックデバイスです。
- 「キャラクタデバイス」は文字単位で文字ストリームを送受信します。キャラクタデバイスはアドレス指定できず、シーク操作もありません。マウスは一般的なキャラクタデバイスです。
ブロックデバイスでは、通常、大量のデータが転送されるため、コントローラには読み書き可能な「データバッファ」が用意されています。
- CPU はデータをコントローラのバッファに書き込む際、バッファのデータが一部たまるまでデバイスに送信しません。
- CPU はコントローラのバッファからデータを読み取る際も、バッファが一部たまるまでメモリにコピーしません。
これにより、デバイスへの頻繁なアクセスを減らすことができます。
では、CPU はデバイスの制御レジスタとデータバッファとどのように通信するのでしょうか?2 つの方法があります:
- 「ポート I/O」:各制御レジスタには I/O ポートが割り当てられ、特殊なアセンブリ命令(in/out など)を使用してこれらのレジスタを操作できます。
- 「メモリマップ I/O」:すべての制御レジスタをメモリ空間にマップし、メモリの読み書きと同様にデータバッファを読み書きできるようにします。
I/O 制御方法#
頻繁なディスクの読み書きに対しては、割り込みの方法は適していません。これにより、CPU が頻繁に中断され、大量の時間を占有する可能性があります。このようなデバイスの問題に対する解決策は、「DMA(Direct Memory Access)」(直接メモリアクセス)機能を使用することです。これにより、CPU の関与なしにデバイスが I/O データをメモリに自動的に配置できます。DMA 機能を実現するには、「DMA コントローラ」ハードウェアのサポートが必要です。
デバイスドライバ#
デバイスコントローラはデバイスの多くの詳細を隠していますが、「デバイスコントローラ」のレジスタ、バッファなどの使用モードは異なります。したがって、「デバイスコントローラ」の差異を隠すために、「デバイスドライバ」が導入されました。
「デバイスコントローラ」はオペレーティングシステムの範囲には含まれず、ハードウェアに属しています。一方、「デバイスドライバ」はオペレーティングシステムの一部であり、オペレーティングシステムのカーネルコードはデバイスドライバのインターフェースをローカルコールコードのように使用できます。デバイスドライバはデバイスコントローラを操作するための命令を発行した後、デバイスコントローラを操作できます。
異なるデバイスコントローラには異なる機能がありますが、「デバイスドライバ」はオペレーティングシステムに統一されたインターフェースを提供します。これにより、異なるデバイスドライバは同じ方法でオペレーティングシステムに統合できます。
前述のように、中断に関する多くのことがあります。デバイスが作業を完了すると、オペレーティングシステムに通知するために中断が送信されます。そのため、オペレーティングシステムは中断を処理する場所が必要であり、それはデバイスドライバです。デバイスドライバはコントローラからの中断要求に迅速に応答し、中断のタイプに応じて適切な「中断処理プログラム」を呼び出します。
一般的なブロックレイヤー#
ブロックデバイスの場合、異なるブロックデバイスの差異による影響を軽減するため、Linux は統一された一般的なブロックレイヤーを使用してさまざまなブロックデバイスを管理しています。
一般的なブロックレイヤーは、ファイルシステムとディスクドライバの間にあるブロックデバイスの抽象化レイヤーです。主な機能は次の 2 つです:
- 1 つ目の機能は、ファイルシステムとアプリケーションプログラムに対して、ブロックデバイスへのアクセスの標準的なインターフェースを提供し、さまざまな異なるディスクデバイスを統一されたブロックデバイスとして抽象化し、これらのデバイスのドライバを管理するためのフレームワークをカーネルレベルで提供します。
- 2 番目の機能は、ファイルシステムとアプリケーションプログラムからの I/O リクエストをキューに入れ、キューを再並べ替え、リクエストをマージするなどの方法で I/O スケジューリングを行います。つまり、I/O スケジューリングは、ディスクの読み書き効率を向上させるためのものです。
Linux メモリは 5 つの I/O スケジューリングアルゴリズムをサポートしています:
- スケジューリングアルゴリズムなし:ファイルシステムとアプリケーションプログラムの I/O に対して何も行いません。このアルゴリズムは、仮想マシン I/O でよく使用されます。この場合、ディスク I/O スケジューリングアルゴリズムは物理マシンシステムによって処理されます。
- 先入れ先出スケジューリングアルゴリズム
- 完全公平スケジューリングアルゴリズム:各プロセスに対して I/O スケジュールキューを維持し、タイムスライスに従って各プロセスの I/O リクエストを均等に分散します。
- 優先度スケジューリング
- デッドラインスケジューリングアルゴリズム:読み取りと書き込みリクエストのために異なる I/O キューを作成し、これによりディスクのスループットが向上し、デッドラインに達するリクエストが優先的に処理されるようになります。これは、データベースなどの I/O 負荷が高いシナリオに適しています。
ストレージシステムの I/O ソフトウェアレイヤー#
Linux ストレージシステムの I/O は、ファイルシステムレイヤー、一般的なブロックレイヤー、デバイスレイヤーの 3 つのレベルに分けることができます。これらのレベルの関係は次の図のようになります。
これらの 3 つのレベルの役割は次のとおりです:
- ファイルシステムレイヤーには、仮想ファイルシステムと他のファイルシステムの具体的な実装が含まれており、アプリケーションプログラムに対して標準的なファイルアクセスインターフェースを提供し、一般的なブロックレイヤーを介してディスクデータを格納および管理します。
- 一般的なブロックレイヤーには、ブロックデバイスの I/O キューと I/O スケジューラが含まれており、ファイルシステムの I/O リクエストをキューに入れ、I/O スケジューラを介して次のデバイスレイヤーに I/O を送信します。
- デバイスレイヤーには、ハードウェアデバイス、デバイスコントローラ、およびドライバプログラムが含まれており、物理デバイスの I/O 操作を担当します。
ファイルシステムインターフェースがあるため、ファイルシステムのコマンドラインを使用してデバイスを操作するだけでなく、アプリケーションプログラムからも read、write 関数を呼び出すことで、デバイスをファイルのように操作できます。したがって、Linux ではデバイスも特殊なファイルにすぎません。
ただし、読み書き操作だけでなく、デバイス固有の機能や属性をチェックする必要があります。そのためには、ioctl インターフェースが必要です。これは、入出力制御インターフェースを表し、特定のデバイスのプロパティを設定および変更するための汎用インターフェースです。
さらに、ストレージシステムの I/O はシステム全体で最も遅い部分です。そのため、Linux は I/O の効率を向上させるためにさまざまなキャッシュメカニズムを提供しています。
- ファイルアクセスの効率を向上させるために、ページキャッシュ、inode キャッシュ、ディレクトリキャッシュなど、さまざまなキャッシュメカニズムが使用されます。これらのキャッシュの目的は、ブロックデバイスへの直接呼び出しを減らすことです。
- ブロックデバイスのアクセス効率を向上させるために、バッファが使用され、ブロックデバイスのデータがバッファにキャッシュされます。
キーボードで文字を入力すると、何が起こるのか?#
CPU 内のメモリインターフェースは、システムバスと直接通信し、システムバスは I/O ブリッジに接続されます。I/O ブリッジのもう一方にはメモリバスが接続され、CPU とメモリの通信を可能にします。もう一方の側には I/O バスが接続され、キーボード、ディスプレイなどの I/O デバイスに接続されます。
ユーザーがキーボード文字を入力すると、「キーボードコントローラ」はスキャンコードデータを生成し、キーボードコントローラのレジスタにバッファリングします。その後、キーボードコントローラはバスを介して CPU に「割り込みリクエスト」を送信します。
CPU は割り込みリクエストを受け取ると、オペレーティングシステムは「割り込まれたプロセスの CPU コンテキストを保存」し、キーボードの「割り込みハンドラ」を呼び出します。
キーボードの割り込みハンドラは、キーボードドライバが初期化されるときに登録されます。キーボードの割り込みハンドラの機能は、キーボードコントローラのレジスタのバッファからスキャンコードを読み取り、スキャンコードに基づいてユーザーがキーボードに入力した文字を見つけます。入力された文字が表示可能な文字である場合、スキャンコードを対応する表示文字の ASCII コードに変換します。たとえば、ユーザーがキーボードに「A」と入力した場合、表示可能な文字であるため、スキャンコードを「A」の ASCII コードに変換します。
表示文字の ASCII コードを取得したら、それを「読み込みバッファキュー」に入れます。その後、結果を表示するために、表示デバイスのドライバは定期的に「読み込みバッファキュー」からデータを読み取り、「書き込みバッファキュー」にデータを 1 つずつ書き込み、最後にこれらのデータを表示デバイスのコントローラのレジスタのデータバッファに書き込みます。これにより、データは画面に表示されます。
結果が表示された後、割り込まれたプロセスのコンテキストを「復元」します。