UNIXのソースコードを読む中で、起動時のプロテクトモードへの移行プロセスが気になったので調べたことをまとめました。
技術的に誤りが無いように努めていますが、もし万が一誤りがあればご指摘いただけると助かります。
今回は、Intelのx86CPUをターゲットとしています。
もくじ
リアルモードとプロテクトモード
リアルモード
x86CPUにおけるリアルモードとは、Intel 8086
CPU互換のための動作モードです。
x86の場合は起動時の動作モードとなり、BIOSもリアルモードで動作します。
Intel 8086
CPU互換のため、リアルモードはすべてのレジスタのアドレス長が16bitに制限されます。
メモリアドレスを参照する場合はセグメントレジスタの値を使用して、20bitのアドレス空間にアクセスすることができます。
また、A20 Lineを有効化することで21bitのアドレス空間を使用することができるようになります。
リアルモードでは、後述するプロテクトモードに存在する、ハードウェアベースのメモリ保護や仮想メモリは存在しません。
プロテクトモード
プロテクトモードは、x86CPUにおいてメモリ空間が32bitに拡張され、メモリやI/Oの保護が可能となる動作モードです。
階層的な特権管理(リングプロテクション)とタスク間のメモリ保護が可能です。
参考:Protected Mode - OSDev Wiki
前述した通り、x86CPUにおいてBIOSはリアルモードで動作しているため、プロテクトモード移行後はBIOS割込みを使用できなくなります。
プロテクトモードにおけるメモリ保護の仕組みについては後述します。
プロテクトモードにおけるメモリ保護
プロテクトモードでは、プログラムに対して参照可能なメモリ領域を定義する必要があります。これはカーネルプログラムにおいても同様です。
まず、x86CPUはプログラムやデータをセグメントというメモリ内の連続した領域の単位で扱います。
セグメントの単位は64KBか4GBの2種類があります。
プロテクトモードにおけるメモリ保護では、メモリ領域の開始点とサイズ、そのメモリ領域の読み/書き/実行権限などをセグメントディスクリプタとよび、ディスクリプタテーブルによって管理します。
プロテクトモードでプログラムがメモリを参照する場合、DSレジスタは直接メモリアドレスの値を指しません。
その代わり、DSレジスタはディスクリプタテーブルを参照し、セグメントの情報を取得します。
この仕組みによって、プログラムが自由なメモリ領域を参照するためにはディスクリプタテーブルの書き換えが必須となります。
しかし、ディスクリプタテーブルはプログラムから書き換えられないようになっているため、結果としてプログラムはあらかじめ決められたメモリ領域以外を参照できず、メモリの保護が実現されます。
参考:ASCII.jp:Windowsのメモリー管理をx86の仕組みから読み解く (1/4)
ディスクリプタテーブルについて
ディスクリプタテーブルには、GDT(グローバルディスクリプタテーブル)とLDT(ローカルディスクリプタテーブル)の2種類が存在します。
GDT(グローバルディスクリプタテーブル)
GDTは、通常システムに1つだけ定義されるディスクリプタテーブルです。
GDTでは複数のLDTが管理されます。
x86CPUには、GDTの先頭アドレスを指すためのGDTRと呼ばれるレジスタが存在します。
参考:Global Descriptor Table - Wikipedia
参考:Global Descriptor Table - OSDev Wiki
LDT(ローカルディスクリプタテーブル)
LDTはプログラムごとに作成されるディスクリプタテーブルです。
x86CPUでは、プログラムはディスクリプタテーブルで定義されたアドレス以外の領域にはアクセスできません。
そのため、プログラムごとに競合しないディスクリプタテーブルをOSが管理することによって、各プログラムが互いのメモリ領域を参照することを防ぎ、メモリ保護を実現することができます。
x86CPUには、GDTRと同様に、現在使用しているLDTの先頭アドレスを指定するためのレジスタとしてLDTRが用意されています。
プログラムがセグメントレジスタに値をセットしようとしたとき、CPUはLDTRを参照し、プログラムがアクセスしようとしているアドレスの参照と、アクセス可否の検証を行います。
まとめ
x86CPUにおけるプログラム実行時の流れを整理すると、まずプログラムが実行状態に切り替わるとき、CPUはGDTRから参照したGDTより、実行対象のLDTのインデックスを特定して、LDTRに格納しておきます。
プログラムが特定のセグメントに対して参照を要求した場合、CPUはLDTRからLDTを参照して、参照先のアドレスの取得と参照可否の検証を行います。
この一連の処理によって、プログラムはあらかじめ定義されたメモリ領域以外を参照することができず、メモリ保護が実現されます。