起動するまでの長い道のり IPL編(6) 続CPU解説の巻

 今回はセグメンテーションと割り込みテーブルについて。でも多分あんまり詳しくやらない。できないから(笑)。

 しかしいつになったらD言語が出てくるんだろう……プロテクトモードに行くようになったら出てくるはずなので乞うご期待!

セグメンテーションとは

 以前にちょっとだけ説明したが、最近のx86系CPUにはセグメンテーションという機能がある。これはメモリを複数のセグメントに切り分ける仕組みだ。あるセグメントはデータセグメント、別のセグメントはプログラムコード用のセグメント(コードセグメント)といったように用途によってメモリ領域を区切ることができる。

 セグメントには用途によって以下のような種類がある。

名称 対応するセグメントレジスタ 用途
コードセグメント CS プログラムコードが置かれる。
データセグメント DS プログラムで使うデータが置かれる。
スタックセグメント SS スタック領域として使われる。

 セグメントでメモリを用途別に区切ることで、プログラムのエラーを捕捉しやすくなったり安全性が上がったりする。mov命令は通常データセグメント、jmp命令はコードセグメント、push命令はスタックセグメントといったように命令によって参照するセグメントが決まっている。また、セグメントによって読み書きの許可・禁止を設定できたりもする。

 たとえば普通プログラムコードは実行時に書き換えない。だがもしプログラムコードを書き換えるようなバグを埋め込んでしまった場合、CPUがそこを実行してしまった時に致命的なことが起きる可能性がある。バグが起きた時の状況によって現象が変わったり、そもそも既存のソースを見てもその現象を起こすコードが見当たらなかったりと、色々大変だ。

 もしメモリがコードセグメントとデータセグメントに分かれていれば、メモリ転送命令などは通常データセグメントを参照するのでプログラム領域が変更される可能性が低くなる。また、コードセグメントが書き込み禁止になっていれば、コードを変更しようとした瞬間にエラーが通知されてプログラムを中断させたりできる。

 x86のセグメンテーションには、リアルモード(16ビット)時のセグメンテーションとプロテクトモード(32ビット)時のセグメンテーションの2種類がある。今はリアルモードだけを対象に簡単に説明する。ただ、OS開発で重要になるのは恐らくプロテクトモード時のセグメンテーションだろう。こちらは後の回で詳しく説明する。

リアルモードでのセグメンテーション

 リアルモードでのセグメンテーションは極めて簡単=低機能だ。以前に解説したとおり、セグメントレジスタの値と指定されたオフセット・アドレスが組み合わされて、以下の計算式で変換されるだけだ。

アクセスされるアドレス = セグメントレジスタ値 × 16 + オフセット・アドレス

 ちなみに、セグメントレジスタの値を16倍したアドレス(セグメントの開始アドレス)のことをベース・アドレスという。また、ベース・アドレスとオフセット・アドレスを足したアドレスをリニア・アドレスという。さらに、実際のメモリと対応するアドレスは実アドレスと呼ばれる。実アドレスとリニア・アドレスは何が違うのかというと、今はまだ違わない。だが、プロテクト・モードに移ってページングという仕組みが動くと違ってくるようになる。

リアルモードでの割り込み

 入出力やint命令で割り込みが発生すると、CPUは実行中のプログラムを中断して割り込みの処理を行う。メモリ上に割り込み番号と割り込みハンドラの対応表(割り込みハンドラテーブル)があり、それを元に割り込みハンドラを呼び出して処理する。

 割り込みハンドラテーブルは、アドレス0x0000〜0x03ffの範囲にある。位置は普通固定されている。テーブルの項目(エントリ)には、セグメントとオフセットアドレスが書かれていて、割り込み発生時にはそのアドレスへジャンプする。

 割り込み番号とエントリの対応付けは以下のように行われる。

エントリのアドレス = 割り込み番号 × 4

 割り込みハンドラテーブルはマシン起動時に初期化されていて、BIOSファンクションやその他の例外処理のコードを指すようになっている。リアルモード中は多分変更しないほうが良い。

 プロテクトモード時には割り込みハンドラテーブルは移動でき、また普通移動される。プロテクトモードではBIOSファンクションが使用できないので、エントリは全て独自の割り込み処理に書き換えることになる。

参考資料

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版

はじめて読む486―32ビットコンピュータをやさしく語る

はじめて読む486―32ビットコンピュータをやさしく語る