I/Oシステム

恐竜本13章です。10章からファイルシステムの仕組みやディスクの動作やそれにまつわる問題に関して述べられてきましたがこの章ではその他のI/Oシステムの物理層よりの話にフォーカスしています。なお、この章で恐竜本を読み進めるのは一旦おしまいにしてパタヘネに移るか、理解度チェックとして演習をやっていくかしようかと思います。システム保護の部は情報系のカリキュラムを見てもやっている大学とやっていない大学がありますし、東大はやっていないみたいですし、今後の自分の開発にもすぐ役立ちはしなさそうですからね。

I/Oハードウェア

マシンとデバイスはポートという接点でつながっている。色々なポートはバスという共通の信号線で接続されており、マシンとのコミュニケーションをとる。また、デバイスがいくつも信号線を介して直列につながっている時、daisy chainとよぶ。daisy chainはバスとして動作する。バスには色々あり、PCI bus(プロセッサとメモリを高速なデバイスに接続)、expansion bus(キーボードやシリアルポートなど遅いデバイスに接続)、SCSI bus(ディスクに接続)、PCI-X、PCI Express、HyperTransportなどがある。

コントローラーはポート・バスやデバイスを操作することができる電子機器のことを指す。コントローラーはコンピュータ上のチップとして実装される。ただし、SCSI bus controllerに関しては分離された回路ボード(ホストアダプタ)として実装されている。

プロセッサとコントローラの間の通信は専用のI/O命令を使うか、memory-mapped I/Oを使う。後者においては、コントローラのレジスタがプロセッサがアクセスするアドレス空間にマップされ、そこに対して読み書きすることでコントローラと通信できる。レジスタには、data-in register、data-out register、status register、control registerが存在する。data-in registerはホストが入力を得るために使用し、data-out registerは出力を書き込むために使用する。status registerはコマンドの完了状態やエラー状態を示し、control registerはデバイスにコマンドを開始させたり、デバイスのモードを変更したりするのに用いられる。

デバイスが作業を終えたか否かの確認を行う方法としては、busy-waiting (polling)がある。これは、例えば、ホストがcommandレジスタのbusyビットをホストがループで読み出し続け、状態を確認するような手法である。これを行っている間は有益な作業ができない。待ち時間が長時間になるほど無駄が深刻化するので、代わりの方法として、デバイス側がCPUに割り込みを通知する方法がある。

CPUはCPUにつながっている割り込みリクエストラインを、命令を1個実行するたびにチェックする。チェックの結果割り込みが発生していたことがわかれば、作業内容を対比して割り込みハンドラを実行する。概念的にはシンプルだが、割り込みを禁止したいような処理だったり、どのデバイスが割り込みを起こしたのか効率的に調べる方法が必要だったり、割り込みにも優先順位をつけたかったりと、考慮すべき事項は多い。こういった考慮事項への対応は現代ではCPUと割り込みコントローラハードウェアに実装されている。

CPUにはマスク不可能な割り込みとマスカブルな割り込みが用意されている。後者は重要な作業をしている間は無効化することができる。

割り込みでは、割り込みベクタにおけるどの割り込みハンドラを実行すべきか、というアドレス値を引数としてとれる。ただし、割り込みベクタのサイズよりつながっているデバイス数が普通は多いので、アドレス値に対応する割り込みハンドラはリストとして持っておく必要があり、割り込みを発生させたデバイスのハンドラが実行されるまで頭から全部実行される。

割り込みには優先順位があり、高い優先度の割り込みが起きたら低い優先度の割り込みを止めたり後回しにしたりできる。

また、割り込みはゼロ除算などの例外機構を実装するのに使われたりする。

システムコールの実装にも割り込みが使われている。ライブラリのルーチンが呼び出された時、引数をチェックして、システムコール用のデータ構造を作成し、ソフトウェア割り込みと呼ばれる特殊な命令を実行する。

長い時間データの転送が行われるデバイスでは、ステータスビットをCPUに監視させて1バイトずつコントローラレジスタに値を書き込むようにする(programmed I/O)のは無駄が多い。そのため、コントローラが直接メモリにデータを書き込み、CPUを煩わせないようにする技術が利用される。これをdirect-memory-access (DMA)という。DMAコントローラは普通のPCの一部であり、bus-mastering I/O boardは通常高速なDMAハードウェアを持っている。DMAでのハンドシェイクはDMA-requestとDMA-acknowledgeという2本のワイヤを使って行われる。DMAが実行されているとき、memory busが専有されるが、一時的にCPUがメモリにアクセスできなくなる。この現象をcycle stealingという。これが起きてもなおDMAを行ったほうが全体のパフォーマンスは良くなる。また、DMAは物理アドレスを使って行われるが、direct virtual memory access (DVMA)では仮想アドレスを理解してデータの送信が行われる。

アプリケーションI/Oインターフェース

ブロックデバイスインターフェースはディスク・ドライブやブロック志向のデバイスの側面を捉えている。デバイスはread()とwrite()を理解し、ランダムアクセスデバイスならseek()も理解すると期待される。データベースアプリケーションなどではreadやwriteよりも配列のようにデバイスにアクセスすることを求める事があり、そのようなアクセス形態はraw I/Oと呼ばれる。

キャラクターストリームインターフェースはキーボードのようなデバイスを表す。get()やput()といった操作をサポートする。また、行レベルでのアクセスのため、ライブラリは文字のバッファリングや編集サービスを実装することもできる。このアクセススタイルはキーボード、マウス、モデム、プリンター、オーディオボードなどに適している。

ネットワークデバイスはディスクI/Oとは大きく異なるので、read-write-seekの形式はそぐわない。UNIXやWindowsではsocketインターフェースをサポートしている。socketは電気機器のソケットにはなにをはめ込んでも良いということのアナロジーとして、ローカルのソケットをアプリケーションが作成して、リモートのアプリケーションがソケットに差し込まれるのを待ち、データの送受信を行うというインターフェースを持っている。OSはselect()という関数を提供する。selectはどのソケットが受信したパケットを保持しているか、どのソケットが送信パケットを受け入れられるかを教えてくれる。これによりポーリングが必要なくなる。

時計やタイマーといったデバイスも存在する。これらは一定時間後に割り込みを発生させる。スケジューラはこのデバイスをスケジューリングに使用している。

I/OにはブロッキングI/OとノンブロッキングI/Oとがある。ノンブロッキングI/Oを実現するにはスレッドを作ってブロッキングI/Oを行うか、非同期のシステムコールを実行するかの方法が存在する。

カーネルI/Oサブシステム

カーネルはI/Oのために様々なサービスを提供する。I/Oスケジューリングではシステムのパフォーマンスを向上させるためにディスクの動かし方を決定したりする。また、非同期I/Oをサポートする場合はデバイス状態テーブルを保持しておき、待っているリクエストをキューイングする必要がある。また、バッファリング、キャッシング、スプーリングなどのデータの一時保管も行う。デバイスのI/Oに失敗した場合はデバイスが提供するエラー情報を解読したりする。SCSIではsense key、additional sense code、additinoal sense-code qualifierといった情報がOSに提供される。

I/Oリクエストのハードウェア命令への変換

  1. アプリケーションがread()システムコールを、以前に開いたファイルディスクリプタに対して実行
  2. カーネルのシステムコールコードが入力をチェック、バッファーにデータが存在するならそれを返す。無いならプロセスをデバイスのwait queueに並ばせる。キューの先頭に来たら、I/Oサブシステムがデバイスドライバにリクエストを送信。
  3. デバイスドライバがデータの受信に使うバッファを確保。また、I/Oのスケジューリングを行う。その後デバイス制御レジスタに値を書き込む。
  4. デバイスコントローラがハードウェアを動かしてデータの転送を行う
  5. データの転送が終わったらDMAコントローラが割り込みを発生させる
  6. 割り込みベクタに登録されたハンドラのうち適切なものがデータを保存しデバイスドライバに通知。通常実行に戻る
  7. デバイスドライバが通知を受け取ったらリクエストのステータスを決定し、I/Oサブシステムにリクエスト完了を通知
  8. カーネルが受け取ったデータをプロセスのアドレス空間に移し、プロセスをready queueに戻す

STREAMS

UNIXではアプリケーションがドライバーコードのパイプラインを動的に組み上げるSTREAMSという仕組みがある。STREAMSではstream head、driver endという端点があり、間をstream modulesというモジュールが埋めている。各モジュール間はキューでつながっており、キューが溢れないようにフローコントロールが行われる。ユーザープロセスはwrite()もしくはputmsg()システムコールでデータを書き込む。getmsg()を使うとキューからデータを読むことができる。このシステムはデバイスドライバを作る際に再利用性が高まることである。

恐竜本を読み終えて

全章ではありませんが、恐竜本で読みたい箇所は読み終えることができました。GWから読んできて3ヶ月ほどかかりましたが無事三日坊主にならず読むことができて良かったです。事前の予想ではOSの勉強は理論的に難しい話はあまり出てくることはなく、覚えることが多いんだろうなと思っていました。実際その通りで、OSの勉強はOSがどんな役割を持っているか、それぞれの役割実現に際してどんな設計問題が出現するか、それぞれに対する対応方針の洗い出し、それぞれのメリデメ、といったことを理解することがすべてでした。プロセスのスケジューリングや非同期処理における同期の問題など、アプリケーション開発者としても直面しそうな設計問題も多く、それに対する一通りの解をOSの学習を通して知ることができ有益でした。まだそらで説明できるレベルではありませんが、今後都度都度読み返して説明できるレベルに持っていきたいです。

冒頭でも書きましたが、次は演習問題かパタヘネをやっていこうと思います。多分今年はパタヘネまで読み終えれば御の字という感じですね。大学生は多分もっとハイペースでやっていくんだと思いますが、仕事がある社会人はこのペースが限界です。

コメント

タイトルとURLをコピーしました