この記事は、制御構文 からの続編です。
この記事は、絵でわかる プログラムとは何か(7)~フロー~ の一記事です。
この記事のポイント
- プロセッサがそれまでの処理を中断して、最優先で行う処理のことを割り込み(interrupt)と言う。
- 割り込みには、ハードウェア割り込みとソフトウェア割り込みがある。
- 割り込みは、プロセッサが割り込みベクタから割り込みハンドラを特定し実行する流れ。
- 外部のハードウェアと電気信号をやり取りするプログラムをドライバと言う。
- 割り込みの詳細情報(メッセージ)を順番通りに保存するメモリ領域をメッセージキューと言う。
- ドライバや他プロセスからのメッセージを取得し、利用することをイベントと言う。
- 一連のプログラムのフローに沿って、決まったタイミングでされる処理を同期処理と言う。
- 他のプログラムをブロック(block)しない処理を非同期処理と言う。
- メッセージキューを常時監視する無限ループプログラムをイベントループと言う。
- イベントハンドラーへ、イベントオブジェクトをディスパッチするプログラムをディスパッチャと言う。
- 本来の処理に必要な前提条件が欠けて、根本的に行き詰まる実行時エラーを例外(exception)と言う。
- try 文を使って例外に対処させる処理のことを例外処理と言う。
- 関数のコール元へ遡る例外通知が繰り返されることを、例外チェーンと言う。
割り込み(interrupt)
割り込みとは
プログラムは、実行を開始してから終了するまで、ノンストップ&最短時間で進むとは限りません。
入出力で待たされることもあるでしょうし、マルチタスクOSでの仮想記憶管理や、他プロセスとのコンテキストスイッチに、プロセッサ(CPU)をとられることもあるでしょう。
プロセッサがそれまでの処理を中断して、最優先で行う処理のことを割り込み(interrupt)と言います。
割り込みフロー
割り込みでは、その発生から完了まで、次の4段階のステップをプロセッサが実行します。
- 割り込みの発生 ・・・プロセッサに割り込みベクタが通知され、現在実行中のコンテキストを一時退避する。
- 割り込みベクタ検索 ・・・割り込みベクタテーブルを検索し、割り込みハンドラを参照する。
- 割り込みハンドラ参照 ・・・割り込みハンドラに書かれた処理を実行する。
- 割り込み前への回復 ・・・一時退避させていた処理を再開する。(割り込みの完了)
割り込みベクタ
割り込みベクタは割り込み番号とも言い、割り込みの詳細を特定するキーとなる番号です。
割り込みベクタテーブル
割り込みベクタテーブルは、OS起動時からメモリへロードされている、割り込みベクタと割り込みハンドラ(への参照)の対応表です。
同時に、割り込みベクタテーブルがロードされたメモリアドレスも、プロセッサの割り込み専用レジスタに入れられ、割り込み発生時、すぐにプロセッサが割り込みベクタテーブルを参照できるようにします。
ハンドラ
ハンドラ(handler)とは、対応処理プログラムのことを指します。ハンドラには、割り込みハンドラの他にも、後で説明するイベントハンドラや例外ハンドラなどがあります。
プロセッサ最大手のインテルでは、以下の用語が使われますが、ほぼ同じものです。
- 割り込みベクタテーブル ・・・ IDT: Interrupt Descriptor Table
- 割り込みハンドラ ・・・ ISR: Interrupt Service Routine
ハードウェア割り込みとソフトウェア割り込み
プロセッサが受ける割り込みは、プロセッサ外部からのハードウェア割り込みと、プロセッサ内部からのソフトウェア割り込みに分けられます。
それぞれ、扱われ方がだいぶ異なるので、以降で詳しく説明します。
ハードウェア割り込み
割り込み要求
プロセッサには、外部のハードウェアからの割り込み要求(IRQ: Interrupt ReQuest)を受け付けるための専用端子が付いています。その専用端子をIRQ端子と言います。
外部のハードウェアが割り込み要求を出すのは、以下のような場合です。
- 入出力の開始
- 入出力の完了
- 入出力エラー(例外の一種)
なお、ハードウェア割り込みには、割り込みの優先レベルを下げる設定(マスク)が可能な割り込みもあります。逆に優先レベルを下げられない割り込みをNMI(Non-Maskable Interrupt)とも言います。通常、NMIはIRQ端子と別にNMI端子があって区別されています。
このように複雑化した割り込みを処理するため、CPUではなく割り込み専用のプロセッサをCPUの近くに置く場合があり、それを割り込みコントローラーと言います。
割り込みハンドラ
ハードウェア割り込みの割り込みハンドラには、通常、割り込み要求元ハードウェアのドライバに対して、割り込みの詳細情報をメッセージキューへ送るよう、指示が書かれます。
割り込みの詳細情報には、発生時刻、発生場所、内容(参照)などが入ります。
ドライバからメッセージキューへ送られる、この詳細情報のことをメッセージと言います。
なお、メッセージという用語は本来、プロセス間通信に使われる一技術を指す言葉で、割り込み以外でも使われます。
ドライバ
ドライバ(driver)は、外部のハードウェアと電気信号をやり取りするプログラムで、ハードウェア毎に異なる電気信号をやり取りするため、通常はハードウェアの製造元が、製品ごとに提供するものです。
ハードウェアは、デバイス(device = 装置)とも表現されるため、ドライバのことを丁寧にデバイスドライバと言う場合もあります。
なお、ドライバは、OSから呼び出されるプログラムですが、アプリから見ると、OSの一部に見えます。
メッセージキュー
キューとは、入力した順番が維持され、その順番通りにだけ、出力を受け付ける、メモリ上のデータ構造です。
よってメッセージキューは、メッセージを順番通りに保存するキューになります。
なお、メッセージキューには、ドライバ以外のプロセスからのメッセージも保存されます。
イベント
外部のハードウェアとの入出力(ユーザー操作や通信なども含む)を、アプリで利用する場合、その詳細情報をイベントオブジェクトとして取得します。
イベントオブジェクトを引数として直接取得する関数を、イベントハンドラと言います。
イベントハンドラが、イベントオブジェクトを受け取って、実行されることをイベントと言います。
イベントは通常、アプリに同期せず、いつ発生するかわからないものなので、そのタイミングが重要になります。
同期と非同期
同期(synchronization)とは、「期を同じくする」という意味で、一連のプログラムのフローに沿って、決まったタイミングで処理される(他の処理との順番が前後しない)ことを指します。
同期処理では、プログラムで決められた、処理の順番を変えられないので、前の処理が終わらなければ、後の処理はずっと待たされます。このような待ちのことをブロッキング(blocking)と言います。
逆に、他のプログラムの処理を、ブロック(block)しない(待たせない)ような処理をのことを、非同期処理と言います。
イベントには、ハードウェアとの入出力だけでなく、非同期な他プロセスからのメッセージ送信なども含めて、全てのメッセージ受付が対象となります。
イベントループ
アプリでイベントを取得するためには、常にイベントループと言われる無限ループプログラムを実行させておきます。
イベントループは通常、OSや言語処理系で用意されています。
イベントディスパッチャ
アプリには、特定のイベントに対応するイベントハンドラーを、イベントディスパッチャへ登録する処理を、プログラミングしておきます。
ディスパッチ(dispatch)とは、「急いで送る」という意味です。
ディスパッチをするプログラムをディスパッチャ(dispatcher)と言います。
よってイベントオブジェクトを、イベントハンドラへディスパッチするプログラムを、イベントディスパッチャと言います。
イベントディスパッチャは、イベントループに含まれていたり、OSが担当していたりする場合もあります。
イベント登録処理をアプリにプログラミングしておくことで、アプリを実行(run)した時に、イベントハンドラがイベントディスパッチャへ登録されます。
イベントのフロー
イベントループとイベントディスパッチャは、以下のように動作します。
- 監視
イベントループは、メッセージキューにメッセージが入っていないかの判定を無限ループします。
メッセージが入っていなければ、メッセージキューは通常、イベントループの動作をブロックします。 - 発火
メッセージキューにメッセージが入っていれば、イベントループが 3. 通知 へと作動します。
このイベントの発端のことを発火(fire)と言います。 - 通知
イベントループは、メッセージを基にしてイベントオブジェクトを作ります。
そしてイベントループは無限ループなので、また 1. 監視 へ戻ります。
イベントディスパッチャは、作ったイベントオブジェクトを、そのイベントに対応するイベントハンドラへ、ディスパッチします。
イベントディスパッチでは、ディスパッチャのプロセスからアプリのプロセスへ、プロセス間通信を使って、イベントオブジェクトを送ります。
ソフトウェア割り込み
ソフトウェア割り込みは、割り込み専用オペコード、または例外(exception)の発生により、発動します。
割り込み専用オペコード
通常、CPUには、割り込み専用のオペコード(INT/BOUND ニーモニック など)が用意されています。
割り込み専用オペコードのオペランドには、割り込みベクタが入れられます。
割り込み専用オペコードは、CPUがユーザーモードあっても、実行が可能です。
割り込みが発生すると、プロセッサは、割り込みハンドラを実行します。
割り込みハンドラに、カーネルモードでのみ動くオペコードをプログラミングしておくと、CPUはカーネルモードに切り替わります。
このように割り込み専用オペコードを使えば、割り込みハンドラで、ユーザーモードからカーネルモードへ切り替えができるため、割り込み専用オペコードは、主にシステムコールに利用されてきました。
ただし、最近のシステムコールとしては、切り替えの処理に時間がかかる単純な割り込み専用オペコードではなく、より高速なシステムコール専用オペコード(SYSCALL/SYSENTER ニーモニック など)を使う方が、一般的になっています。
ユーザーモード、カーネルモード、システムコール については、マルチタスク の記事をご覧ください。
例外(exception)
プログラミングで言う「例外」とは、実行時エラー(run-time error)の一種で、本来の処理に必要な前提条件が欠けて、根本的に行き詰まることを指します。
一般的な「エラー」という言葉には、構文エラー、論理エラー、入力エラーなども含まれます。
構文エラーと論理エラーはバグなので、実行前にデバッグが可能です。
論理エラーは、論理的に正しくない結果が出力されることを指します。
構文的に正しいと、デバッガには引っかかりませんが、動作テストをすることで、論理エラーもデバッグ可能です。
また、実行時でも、入力エラーは、ユーザー(データ入力者)が入力をし直せば、すぐ対処できるエラーです。
入力エラーは、プログラムとしては正しく動作していても、正しくないユーザー入力によって起こる問題です。
そして例外は、最後の最後まで、避けられなかったエラーであり、時に致命的エラー(fatal error)となり得ます。
例外の代表例
代表的な例外としては、以下のようなものがあります。
- 参照先が見つからない
- 機器の故障、未ロード、権限不足、・・・
- 入力が定義の範囲外
- 割る数に0指定、小数に対して割り算の余り演算、・・・
例外の3レベル
例外には通常、3段階のレベル分けがされています。
- トラップ
- 通知/続行
- フォルト
- 修正/再試行(一部例外は再開不可/OSが強制終了)
- アボート
- 重大エラー/再開不可
例外処理
例外が、「最後の最後まで、避けられなかったエラー」であると言っても、プログラマーが例外発生源を、そのまま放置しておくわけにはいかない場合もあります。
仮に例外が発生した場合でも、代替処理(本来の処理の代わり)を行うことで、プログラム実行を続行させようとするのが、try 文です。(ただし、アボートの場合は、代替処理不可。)
try 文を使って、例外に対処させる処理のことを、例外処理(例外ハンドラ)と言います。
try 文に伴うその他のキーワードとして、以下のようなものが挙げられます。(言語によって異なります。)
- catch
- 例外内容に応じた代替処理を記述します。
- throw
- 意図的に例外を発生させます。
- finally
- 例外処理を通っても通らなくても行う処理を記述します。
例外処理なのに、なぜ try なのかわからない、と思う方は、こんな状況をイメージしてみると良いかもしれません。
処理玉が、一本橋を左から右へ渡ろうと(Try)していると、例外玉が投げられます(Throw)。そのままだと、処理玉は橋から落とされてしまいます(例外発生)が、あらかじめ例外玉を Catch する仕掛け(例外ハンドラ)を入れておけば、そのまま橋を渡り続けられます。そして、例外の有無に関わらず、最後にたどり着く処理が Finally です。
例外チェーン(exception chaining)
例外の発生は、処理実行の行き詰まりを意味します。
したがって、いち早くその状況を実行元に知らせないと、全ての後続処理が止まり続けることになります。
そこでCPUは、ソフトウェア割り込みで、まず例外ハンドラから、手短に例外通知だけ発行し、割り込みを終了します。
例外は、コールした先の関数の中でも発生し得ます。その場合、コール先の関数内に、その例外に対応する例外処理が入っていなければ、関数のコール元に、例外がエスカレート(escalate)されます。
コール元も、実は他からコールされた関数だったという場合は、さらにそのコール元へと続きます。
このような例外エスカレーションの繰り返しを、例外チェーンと言います。例外チェーンのエスカレーションは、例外処理に行き当たるまで、あるいは、最初のコール元にたどり着くまで続きます。
例外処理がどこにも入っておらず、最初のコール元までたどり着いた場合は、そこでプログラム全体が停止します。
この記事のまとめ
プロセッサがそれまでの処理を中断して、最優先で行う処理のことを割り込み(interrupt)と言います。
割り込みは、プロセッサが割り込みベクタから割り込みハンドラを特定し実行する流れです。
割り込みには、ハードウェア割り込みとソフトウェア割り込みがあります。
外部のハードウェアと電気信号をやり取りするプログラムをドライバと言います。
割り込みの詳細情報(メッセージ)を順番通りに保存するメモリ領域をメッセージキューと言います。
ドライバや他プロセスからのメッセージを取得し、利用することをイベントと言います。
一連のプログラムのフローに沿って、決まったタイミングでされる処理を同期処理と言い、他のプログラムをブロック(block)しない処理を非同期処理と言います。
メッセージキューを常時監視する無限ループプログラムをイベントループと言います。
イベントハンドラーへ、イベントオブジェクトをディスパッチするプログラムをディスパッチャと言います。
本来の処理に必要な前提条件が欠けて、根本的に行き詰まる実行時エラーを例外(exception)と言います。
try 文を使って例外に対処させる処理のことを例外処理と言います。
関数のコール元へ遡る例外通知が繰り返されることを、例外チェーンと言います。
イベントやシステムコールなどの割り込みは、非同期処理でした。この他にも、プログラミングでは、意図的に非同期処理を利用するケースがたくさんあります。
次は、非同期処理 に進みましょう。
この記事は、絵でわかる プログラムとは何か(7)~フロー~ の一記事です。
コンピューターのしくみ全体を理解したい場合は、以下の2コースがお勧めです。
日本全国 オンラインレッスン にも対応しています。
知りたいことだけ単発で聞きたい場合は、 オンラインサポート をご利用ください。