第3章の先頭では、割り込みの仕組みについていろいろと書いてある。x86では、トラップとは呼ばずに、割り込み(interrupt)と呼ぶのが正解のようだ。
割り込みの仕組みによって、プロセスの切り替えを発生させたり、システムコールを発生させてカーネルにサービスを要求したりする。 第3章の先頭では、主に3つの用語を使っている「割り込み」「例外」「システムコール」である。基本的にこれらは同じハードウェアを使って実現するのが王道で、 例えば例外を検出すると割り込みルーチンに飛んだり、システムコールを実行するためにint命令を実行して割り込みを発生させたりなど、基本的に使うハードウェアは同一となる。
ここでは、x86のアーキテクチャを例にとって、どのようにして割り込み処理を実現するのかを説明している。 x86では、権限モードして0-3までの4つが存在するが、オペレーティングシステムによって利用されているのは0と3だけ、ということらしい。 0がカーネルモードで、3がユーザモードだ。
割り込みが発生したときのトラップルーチンは、IDTによって定義されている。 IDTは256エントリあり、int n命令によって指定されるオペランドnを使って、インデックスnを参照する。 詳細は以下、あまり詳しく読んでいないが、例外の設定テーブルが格納されているので、テーブル参照割り込み、という訳か。
Interrupt Descriptor Table - OSDev Wiki
という訳で、int n命令が実行されると、以下のステップを踏む。
- IDTからn番目のディスクリプタをフェッチする。nはint命令のオペランドである。
- %csのCPLフィールドをチェックし、ディスクリプタの保護レベルであるDPL以下であることを確認する。
- ターゲットセグメントセレクタがPL<CPLであった場合にのみ、CPUの内部レジスタである%espと%ssを保存するが、
- %ssと%espをタスクセグメントディスクリプタからロードする。
- %ssをプッシュする。
- %espをプッシュする
- %eflagsをプッシュする
- %csをプッシュする
- %eipをプッシュする
- %eflagsのいくつかのフィールドをクリアする
- %csと%eipにディスクリプタの値をセットする
すげえ命令だな!この1命令で、スタックが以下のように更新される。
ちなみに、int命令によって入ったカーネルモードから戻ってくるためには、iret命令を利用する。これで、ユーザモードとカーネルモードが行き来できるようになる訳だ。