起動するまでの長い道のり プロテクト・モード移行編(1) セグメンテーション薀蓄の巻

 プロテクト・モードへの移行は起動処理の山場の一つだ。リアル・モードからプロテクト・モードに移って初めて32ビットのコードが動き、64KBの壁を破って4GBまでの全メモリにアクセスできるようになる。
 x86系CPUでプロテクト・モードに移行するには、最低限セグメンテーション割り込みディスクリプタテーブルを初期化する必要がある。だが、割り込みを禁止した状態ならばとりあえずセグメンテーションさえ設定すれば大丈夫だ。
 今回は、プロテクト・モードにおけるセグメンテーションについて解説しようと思う。

プロテクト・モードにおけるセグメンテーション

 プロテクト・モードのセグメンテーションは、リアル・モードのそれと何が違うか。本当に違う。まったく違う。別物なんじゃないかと思うくらい違う。実際に別物だ。
 リアル・モード時にはぶっちゃけ64KBの壁をどうにか超えるためのオフセットに過ぎなかった。ある意味後ろ向きな仕組みだった。だがプロテクト・モード時には、もっと高機能で前向きな意味のある仕組みとなっている。
 プロテクト・モードのセグメンテーションには以下のような特徴がある。

最大サイズが4GB

 リアル・モード時にはセグメント・リミットが64KBまでしかなかったが、プロテクト・モード時には最大で4GBまで設定できる。

アクセス制御が行える

 セグメント毎に読み取り・書き込み・実行などの権限を設定できる。さらに、ユーザ・システム・カーネルといったように権限のレベルも設定できる。カーネルだけがアクセスできるデータ・セグメントや、ユーザ・カーネル両方から参照されるコード・セグメントといった設定が行えるようになっている。
 こういった保護が行えるからこそプロテクト・モードと呼ばれているのだ(多分)。

セグメント・ディスクリプタを必要とする

 上記のような複雑な設定があるため、レジスタに値を設定するだけでは情報が足りない。そこで、ディスクリプタ・テーブルというものをメモリ上に用意し、セグメント・ディスクリプタという構造体をそこに設定してやらないといけない。セグメント・レジスタには、そのテーブルのインデックスを設定する。これが面倒くさい。


 こういった色々と利点のあるセグメンテーションだが、実はこれから作るOSではあまり活用しない。セグメンテーションってx86以外ではあんまり実装されていない機能らしい……。これを使いまくってしまうと、例えばARMとかPowerPCとかAlphaとかH8とかAVRとかPIC(無理だ)に移植する時に困るだろう。そこでさらっと使うくらいにしておく。Linuxの設計の真似だという事実は秘密だ。

プロテクト・モードへの移行の仕方

 で、実際にセグメンテーションをOnにしてプロテクト・モードに移行するには以下の手順が必要になる。

  1. ディスクリプタ・テーブルをメモリ上に用意する。
  2. ディスクリプタ・テーブルの位置を示す構造体をメモリ上に用意する。
  3. ディスクリプタ・テーブルの位置をそれ専用の命令で指定する。
  4. 制御レジスタCR0の0ビット目を1にする。
  5. ジャンプ命令でパイプラインをリセットする。
  6. コード・セグメントをジャンプ命令で設定する。
  7. データ・セグメントを設定する。

 さらさら書いてしまったが新しい用語が幾つか出てきた。
 制御レジスタというのは、名前の通りCPUの動作状態を制御するためのフラグを納めたレジスタだ。CR0〜CR4までの5種類ある。プロテクト・モードかどうかはCR0を設定することで制御する。
 パイプラインとは命令の処理の仕方のことだ。実は、CPUはコードを一つ一つ順に実行しているわけではない。実行中のコードの先を見て、予め命令を実行しておいたりする。当然、分岐が起きてジャンプしたりするとその予め実行された結果は捨てることになる。だがもし分岐が起きなければ、実行結果がそのまま使えて高速に処理できる。
 で、ここではリアル・モードからプロテクト・モードに移行したので、予め実行してしまった結果は無意味だし良く分からないものになっている可能性が高い。そこで空のジャンプ命令を使って実行結果を捨てさせる必要があるのだ。

セグメント・ディスクリプタの構造

 先述の通り、セグメンテーションをOnにするにはディスクリプタ・テーブルをメモリ上に用意する必要がある。ディスクリプタ・テーブルは単純にいえばセグメント・ディスクリプタの配列だ。じゃあそのセグメント・ディスクリプタって何よ、ということをここで説明する。
 セグメント・ディスクリプタは、8バイトの構造体だと思えば良い。ただ、x86の下位互換性とか度重なる拡張のためにやや複雑な構造をしている。以下にその内容を示す。

バイト位置 ビット位置 内容
0 セグメント・リミット下位
1
2 ベース・アドレス下位
3
4 ベース・アドレス上位1
5 0 アクセス
1〜3 タイプ
4 ディスクリプタ・タイプ
5〜6 特権レベル
7 存在フラグ
6 0〜3 セグメント・リミット上位
4 使用可能ビット
5 常に0
6 オペレーション・サイズ
7 ラニュラリティ
7 ベース・アドレス上位2

 これはメンドくさい。ややこしい。後でD言語が使えるようになったら構造体でラップしよう。ただ今はとにかくこれを直接設定してやらないといけない。
 各フィールドについて大雑把に説明する。

セグメント・リミット

 これはセグメントの長さを示すフィールドだ。数えてみれば分かるけど、全部で20ビットしかない。これでどうやって最大4GB指定するのかというと、実はこの値は4KB単位になっている(できる)のだ。グラニュラリティ(日本語に訳せば粒度)に1を設定すると、リミットが4KB単位になる。4KB×2^20=2^32=4GBとなる。グラニュラリティが0だとバイト単位になってしまうので注意。

ベース・アドレス

 これは名前の通りセグメントの開始アドレスを指定する。歴史的な事情でバラバラになっているけど頑張ってどうにかすること。

アクセス

 セグメントがアクセスされた場合、CPUによって1に設定される。0にクリアしておくことで、その時点から次回確認時までにアクセスが行われたかどうか確かめられる。仮想メモリの実装などに使われる。私は多分使わない(汗)。

タイプ

 ディスクリプタのタイプをフラグで設定する。セグメントがデータ・コードのどちらかと、読み取り・書き込みの有効無効を設定できる。フラグは下表のように設定する。

3 2 1 タイプ 意味
0 0 0 データ 読み取り
0 0 1 データ 読み書き
0 1 0 データ 読み取り、エキスパンドダウン
0 1 1 データ 読み書き
1 0 0 コード 実行
1 0 1 コード 実行、読み取り
1 1 0 コード 実行、コンフォーミング
1 1 1 コード 実行、読み取り、コンフォーミング

 エキスパンドダウンとは、セグメントの伸長が下に向かって行われるという意味だ。これは主にスタック・セグメントに使われる。セグメント・ベースがセグメントの終端(始点ではなく)を指すようになり、セグメントのリミットが大きくされた場合は始点がより下に移動する。
 コンフォーミングとは、特権レベルが低いコードから呼び出しが行える事を示す。カーネルに属するコードでもユーザのプログラムから実行できるようにした場合は、コンフォーミングのセグメントに置く。

ディスクリプタ・タイプ

 ディスクリプタがシステム・ディスクリプタかどうかを示す。0の場合はシステム・ディスクリプタだということになる。ここでは1を設定する。
 システム・ディスクリプタとは、セグメント・ディスクリプタではない色々な種類のディスクリプタの総称だ。よって今は無視。

特権レベル

 コード・セグメントの場合、コードの特権レベルの高さを示す。データ・セグメントの場合、アクセス時に要求される特権レベルの高さを示す。
 レベルは0〜3の値で指定する。0が最も高く、3が最も低い。
 特権レベルの低いコードが高いデータ・セグメントにアクセスしたり、コード・セグメントにジャンプしようとした場合、例外が発生する。(いわゆる「落ちる」)

存在フラグ

 セグメントが実際に存在するかどうかを示す。これを0にした状態でセグメントがアクセスされると例外が発生する。OSがその例外に応じてセグメントを用意したりとか色々行えるらしい。普通は1にしておく。

使用可能ビット

 OSが勝手に使っていいビット。でも使わない。使いたくなったらどうぞ。

オペレーション・サイズ

 16ビットコード用(0)か、32ビットコード用(1)かを示す。ここでは当然1に設定する。もし16ビットコードのプログラムを動かす必要があった場合などに使える。例えば古いDOSプログラムとか……。

ラニュラリティ

 リミットのところで説明してしまったが、セグメント・リミットの粒度を示す。0の場合はバイト単位のリミット値・1の場合は4KB単位のリミット値となる。ここでは1に設定する。

次回予告

 次回はセグメントの設計、ディスクリプタ・テーブルの用意と設定方法を解説する。これが終われば普通のC言語のコードとかが動くようになるぜ!
 が、本OSではさらにD言語を動かすまでの長い道のりがあるのだった……。

参考資料

詳解 Linuxカーネル 第3版

詳解 Linuxカーネル 第3版

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

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