割り込み、トラップ、例外。。。結論はイベントーOSイベント(1)

イベント

この章では、オペレーティングシステムで本当に重要な概念であるイベントについて学びましょう。

また、オペレーティングシステムを勉強した人にとっても、もう一度混乱しがちな概念を再確認することを目標とします。

今回のチャプターを読んだ後、OSに関する以下の知識を得ることができるでしょう。

  1. プロセスの正体を知るための基本的な知識を得ることができる。
  2. コンピュータでキーボードとマウスがどのように動作するのかが分かる。
  3. オペレーティングシステムを理解するために重要である割り込み(Interrupt)、トラップ(Trap)、例外(Exception)が何であるかを知ることができる。

割り込み? トラップ? 例外?

タイトルからも分かるように、割り込み、トラップ、例外などの正体はイベントという単語で整理することができます。

では、イベントというのはどこに使われ、なぜ必要なのだろうか?

それを理解するためにもう一度チューリングマシンに戻りましょう。
Pasted image 20231213204547.png|center|700
上の写真は初期のコンピュータであるエニアックの様子である。

過去のコンピュータと現在のコンピュータは多くの点で違いが存在するだろうが、私たちの目に見える最も大きな違いは、まさにキーボードマウスが見えないことですね。

では、昔エンジニアたちはキーボードとマウスが環境でどのようにコンピュータを使用したのだろうか?

過去のコンピュータは一つの仕事

実際、過去のコンピュータは一つの仕事だけを担当していた。

人々が手で一つ一つコードを作成し、それを入力として入れると、期待した出力値が出力してくれる機械であった。
image-20231015220526810.png|center round|150
[一針一針手で編まれたコードとマーガレット・ヘミルトン]。

もしロケットの軌道を計算したり、ロケットを月に着陸させるためのコードを入れると、どのように姿勢を調整し、軌道を修正すべきかについての値が出力されるのだ。
Pasted image 20231213211006.png|center round|200
つまり、コードと入力値だけあれば答えを出力として得ることができたので、キーボードとマウスが必要なかったのだ。

コンピュータが軌道計算だけのための機械だったら何の問題もなかったが、人々はこの素晴らしいものを一つの計算のための道具ではなく、もう少し多様な仕事ができるようにしたかった。

現在のコンピュータ

ここで様々な仕事とは何だろうか?

重要なのは、コンピュータが計算をするという本質は変わらないが、現代の私たちはその計算結果を通してエクセル(統計)を計算したり、グラフィック計算の成果物であるゲームをしたりしている。

現在と過去の違いは、私たちはゲームをしながらウェブサーフィンをし、エクセルをしながらパワーポイントを作り、ディスコードで通話をしているということだ。

過去のエンジニアは様々な仕事、つまりゲームと通話と学校の宿題を同時にすることを夢見て、その成果が現在のコンピュータなのだ。

Pasted image 20231213212400.png|center round|400

どうやって?

では、このコンピュータはどうやって=複数の仕事=ができるのだろうか?

いったい私たちはどうやってゲームと講義を同時に聞きながら友達と通話をすることができるのだろうか?

下にスクロールする前に、次のようなヒントをもとに考えてみましょう。 一体、エンジニアたちはどうやってこの問題を解決したのでしょうか?

  1. 様々なことをしている現代のコンピュータも実は一つの計算をしている(1つのコアを使う場合)。
  2. コンピュータが計算をするという本質は変わっていない。
  3. CPUは人が体感できるよりずっと~~ずっと速い。

正解は

Pasted image 20231214181452.png|center|200
つまり、上記のような流れで進めているのです。

大体予想できる内容で、’コンピュータは複数の仕事をしているように見せる’という言葉を聞いたことがある人もいるかもしれない。

では、もう少し深い内容に入りましょう。

これまでの内容をまとめると、プログラムが交互に短時間ずつ実行されると言いました。

それでは

ここで少し用語整理をしながらOS理論について整理すると、実際に動作しているプログラムをプロセスと呼び、CPUで動作するプログラムの切り替え過程をコンテキストスイッチングと定義する。

それでは再び内容に戻って、動くプログラム(プロセス)の切り替え(コンテキストスイッチング)はどのようにそして何を基準に行われるのでしょうか?

コンテキストスイッチングの本質は後ほどプロセスの本質で扱うことにして、私たちは切り替え(コンテキストスイッチング)が行われる方法に集中しましょう。

答えは意外に簡単である。

それは、=交換してくれ=という=要求=を発生させることだ。

????と思うかもしれないが、結論は以下の通りだ。

このようなリクエストをイベントと呼び、リクエストを解決するための行動をイベントに対するハンドリングと言います。

イベントとイベントハンドラ

今までイベントとイベントハンドラについて説明しました。

これまでの内容をよく理解していれば、キーボードとマウスを’押す’ことがイベントに該当することが予想できます。

また、発生したイベントをコンピュータがそのイベントを処理するルーチン(イベントハンドラ)を呼び出すことが予想できます。

そして、ハンドラの結果として、コンピュータは私たちの入力を受け取っていることが予想できます。

このように、プロセスの切り替え(コンテキストの切り替え)も、何らかのイベントが発生し、そのイベントを処理するハンドラを呼び出すことによって行われることがわかります。

それでは、16bit CPUを例にその詳細を説明します。

Pasted image 20231216134833.png|center|250
[出典 - https://en.wikipedia.org/wiki/Intel_8086 ]

16bitコンピュータの代表格である8086の場合、INTR(Interrupt request)とNMI(Non-maskable Interrupt)というピンが存在し、ここに信号によってイベントの発生を確認します。

また、イベントが発生すると、イベントの種類によってイベントを処理するイベントハンドラを呼び出すことになりますが、このイベントハンドラの正体は実は関数です。

Pasted image 20231216141316.png
[嘘が少し入っている図です-8086にはIDTRが存在しないことに注意].

上の図が先ほどの説明の全体的な流れです。

IRQ 1がキーボードに対するイベントだと仮定してみましょう。

イベントハンドラはOS(カーネル)の初期段階で次のような流れで動作します。

1. CPUはIDTRレジスタにイベントに対するハンドラのアドレスを保管しているテーブルのアドレスを持つ。OS(カーネル)は開始段階でIDTRの値をIDT Tableのアドレスに設定する。
2. OS(カーネル)のコードにキーボードに対するイベントを処理する(キーボードに対する入力が来たら、キーボードと通信を進める)関数を作成する。
3. 最後にこの関数をIRQ 1のハンドラとして登録します。

この時、ハンドラは関数に対するアドレスを持ち、IDTはC言語で関数のポインタに対するリストを持つ関数ポインタ配列と同じと見ることができます。

これで、キーボードを押すことでイベントが発生すると、CPUは作業を停止し、生成されたイベントを解決するためにキーボード入力に対応するイベントハンドラを呼び出します。

イベントハンドラを呼び出すことは関数を呼び出すことであり、関数の進行を通じてキーボードと通信を行った後、元の仕事に戻ります。

付録

参照 - プロセスの切り替え

ここで、これから説明するプロセスのスケジューリングについて説明するため、上記のプロセスの切り替えについて少しだけ説明しておきましょう。

プロセスの切り替えは通常、タイマーを基準に実行されます。

最新のCPUは内部的にアラームを持ち、定期的に鳴るように設定することが可能です。

例えば、アラームを1秒ごとに鳴るように設定したとしたら、もし私たちが何らかのプロセスが実行された時間を記録しておけば、次のようなロジックを考えることができます。

1秒ごとにアラームが鳴る時、イベントハンドラとしてプロセスの実行時間を確認する関数を登録しておきましょう。

アラームが鳴った時、記録しておいたプロセッサの実行時間が長すぎる場合、他のプロセスを実行させることができます!

とても抽象的な説明ですが、スケジューラは通常このように動作します。

割り込み、トラップ、例外 - イベントの種類

ここまでイベントとは何か、そしてその詳細な例を見てきました。

それでは、単にイベントという言葉を使うべきなのに、なぜ割り込み、トラップ、例外という様々な用語を使うのか、ちょっと見てみましょう。

上記ではキーボードを例に挙げましたが、他にも様々なイベントが存在します。

今まで述べたように、OSはリソースの管理が重要なキーワードであり、プロセスの管理もその一つです。

このとき、プロセスでエラーが発生したり、プロセス自身が終了したことを知らせるためにプロセスはイベントを発生させることができます。

また、プロセスがOSに何かを要求する場合もあります(代表的な例として、これから学ぶシステムコールがあります)。

このような目的を達成するために、CPUには INT n という割り込みを発生させる命令が存在する。

ここで、キーボードとプロセスが発生させるイベントをそれぞれハードウェアによるイベントとソフトウェアによるイベントとして、hardware interrupt, software interruptと区別して呼ぶ。

そして、トラップと例外(Exception)のような用語は、実は正確な基準で区別されている用語ではない(実際、複数のCPU会社(Intel, ARM, AMD)の開発者マニュアルで呼ばれている用語を引用しているに近い)。

しかし、一般的にトラップを software interrupt で、数を0で割ったり、存在しない命令を入力するなどの例外的な状況に対するイベントを例外(Exception)と言います。

用語まとめ

上記で説明したイベントに関する用語以外にも、この章で扱ったいくつかの同じ概念を他の用語を使って呼びます。

これから聞くことがあるかもしれないので、整理しておきましょう。

IVT(Interrupt Vector Table)

通常、割り込みに対するハンドラを登録するテーブルを IVT(Interrupt Vector Table) と呼びます。

上で私たちはこのテーブルの正体が実は関数ポインタのリストであることを見ました。

この関数ポインタを通常、OS理論では Interrupt Vector と定義します。

そして、そのテーブルを Interrupt Vector Table と呼びます。

ISR(Interrupt Service Routine)

別の用語としてISR(Interrup Service Routine)があります。

ISR は、上記で説明したキーボードのイベントを処理するハンドラと似たような概念です。

イベント発生時にハンドラを呼び出してその結果を返す過程をInterruptに対するServiceを行うと言い、全体の流れ(ルーチン)をInterrupt Service Routineと言います。

IRQ(Interrupt Request)

最後の用語は上で使ったIRQです。

IRQは文字通りInterruptのリクエストを意味します。

上で見たように、IRQには番号が存在し、各番号に登録されたハンドラを呼び出す際にIRQを発生させることを’Interruptに対するRequestを要求した’、または’n番目のIRQを生成した’と言います。

8086をもう少し詳しく見てみましょう。

8086を見る時、割り込み発生のためにINTR(Interrupt request)とNMI(Non-maskable Interrupt)という2つのピンが存在すると言いましたが、私たちはINTRだけ見てみました。

もちろん、INTRだけでなく、NMIも同じ割り込みと関係しています。

ここでは、イベントの種類と管理について詳しく説明します。

詳しくは[[iAPX_286_Programmers_Reference.pdf]]を見てみましょう。

実は、割り込みの種類はもう少し細かく分類されま。

分類の基準は通常次のように決まっています。

  1. 割り込みの主体(ソフトウェア、ハードウェア)
  2. Mask可能かどうか(INTR, NMI)
  3. 優先順位(Priority)

1つ目の主体は既に説明したので、2つ目から説明します。

Maskとは、割り込みの発生の有無を決めることです。

もしキーボードのイベントを処理中に毎回別のイベントが発生し、キーボードの入力が無視される場合を考えてみよう(考えただけでもイライラしますね)。

このような場合に備えて、CPUには割り込みの発生を無視するように設定する命令(CLI(Clear Interrupt Flag))や、特定の割り込みに対するハンドラの呼び出しを無視するようにするなどの設定が可能である。

こうした割り込みの発生を制御することをマスキングといい、マスキングされた割り込みは無視される。

しかし、特定の割り込みはマスキングをすることができない(つまり無視することができない)そのような割り込みを Non-maskble Interrupt という。

例えば、自動車に入るオペレーティングシステムを考えてみると、自動車のハンドルをInputとすると、ハンドリングに対する入力を無視することは、単にキーボードに対する入力を無視するよりも危険であることが分かるだろう。

最後に、割り込みは優先順位によって分けられる。

もし同時に複数のイベントが発生した場合、優先度の高い割り込みが動作するようにするなどの機能のための優先順位が存在します。

自動車の例のように、ハンドリングに対する入力を優先度の高いイベントとして考えることができます。

もし割り込みが同時に発生したら?

もし複数の割り込みが同時に発生した場合、CPUはそれをどのように処理するのでしょうか?

この問題に対する答えは、実は 実装によって異なります

例えば、同時に発生したイベントを保存し、順番に処理したり、優先度の高いものを先に処理したり、残りのイベントを無視したりするなどの実装があります(つまり、オペレーティングシステムを理解するためには、結局はハードウェアと親しくなる必要があります...)。

私たちは上で説明した8086を基準に説明します。

8086は実は様々なデバイスの割り込み処理のために追加のチップであるIntel 8259を装着して使います。

では、Intel 8259の実装はどうなっているのでしょうか?

Intel 8259には二つのレジスタを使用してサービスの進行を表します。

Interrupt Request Register (IRR), In-Service Register (ISR)

The IRR is used to store all the interrupt levels which are requesting service; and the ISR is used to store all the interrupt levels which are being serviced.

Intel - 8259A PROGRAMMABLE INTERRUPT CONTROLLER

つまり、各レジスタにデバイスの割り込み要求とサービス完了を表示しておき、複数の要求がある場合、優先順位などを基準に先に割り込み処理を行います。

もっと詳しい内容は以下を参考しましょう。

様々な用途

割り込みはもう少し様々な場面で使われます。

それぞれの用途を調べてみると、オペレーティングシステムの様々な機能についてもう少し詳しく知ることができます。

  1. DMA: Direct Memory Access機能
  2. デバッガ: Single-step Execution機能
  3. IPC: Inter Process Communication プロセス間通信機能

特に、デバッグのために使われるSingle-step Executionを調べてみることをお勧めします。