見出し画像

80286のプロテクトモード - 仮想アドレスから実アドレスに変換する仕組み

大型機などでは一般的だった仮想メモリやタスク管理などの機能をプロテクトモードという形でマイコンに導入した80286でしたが、当時はそんな複雑な機能は見向きもされず、どちらかというと8086では1Mまでしか使えなかったメモリをもっと多く使えるようにするために無理やり使うというケースの方が多かったと思います。

さて、今では当たり前となった仮想メモリやタスク管理ですが、普通の人がプログラムを書くうえで知る必要も無いことが多く、たとえアセンブラが書ける人でも馴染みがない人のほうが多いでしょう。いずれもOSなどが使う裏方さんのお仕事なので、仮想メモリは想像ができるかもしれませんが、タスク管理なんて動かなくなったプログラムを止めるときに出てくるかな?という程度だとは思います。

タスク管理の進化

80286では、どのようにこれらの機能が動いているのかを追ってみようと思います。リセットがかかった時点では8086と互換性のあるリアルモードで動作を開始します。最初に初期設定を行わないとならないので、割り込みも禁止された状態で知る人は知っているCSはFFFFH、IPは8080時代と同じく0000H、つまりFFFF0Hのアドレスに格納されている命令から実行を開始します。このモードでは先頭の1Mまでのメモリしかアクセスできません。このままリアルモードで動作するのであれば、割り込みが発生した場合に呼び出されるアドレスの入った00000H付近のセットアップを行ってから割り込みを許可すれば普通のプログラムの実行ができるようになります。

プロテクトモードを使うのであれば、さらにいくつもの準備が必要になります。最初にタスク自身の管理や割り込みが発生した時に行う処理を行うOSなどのコードが、このモードで動作するようにセットアップしなければなりません。プロテクトモードではセグメント・レジスタは、その値自身でアドレスを指定するのではなく、その値をインデックスとしてテーブルを参照して実アドレスを得るセレクタという機能に変わります。ですから、まずテーブルを作っておかなければなりません。

286のプロテクトモード - 仮想アドレス

まずセレクタから実アドレスに変換するためのディスプリクタ・テーブルを用意します。テーブルはシステム全体で使われるセグメントに対するグローバルディスプリクタテーブル(GDT)とタスクごとに切り替えて使うローカルディスプリクタテーブル(LDT)があります。セレクタ(セグメントレジスタ)が、これらのテーブルへのインデックスとして機能します。ひとつのテーブルは8バイトあるので、下位3ビットはフラグとして使われマスクされてテーブルの先頭からのオフセットとして使われます(ディスクリプタテーブルは最大で64K)。

ディスクリプタ・テーブルの構造

そして、このテーブルの先頭をGDTRに格納します。

GDTR

これでセレクタ(セグメントレジスタ)の値からテーブルを引いて実アドレスを計算できるようになるわけです。

セレクタの下位3ビットはフラグとして使われるので、
これをマスクしてオフセットとして使われる

この時、TIが0の場合にGDTが使われ、1の時はタスクごとに用意するLDTが使われます。RPLは特権モードでセレクタごとに特権レベルを設定します。

仮想アドレスから実アドレスを取り出す仕組み

各々のセレクタの指すディスクリプタは、セレクタに値を設定する事にプログラムからは見えないレジスタにキャッシュされて、都度メモリにアクセスすること無く実アドレスに変換できます。

そしてタスクごとに用意したアドレス空間を使う時(セレクタのTIが1の時)、該当するGDTRのディスクリプタ・テーブルのベースアドレスからタスクごとの変換テーブル(LDT)の実アドレスを求め、セレクタの値に対応するベースアドレスを探します。

タスクごとのテーブルを引く手順

もちろんGDTにもLDTにも最大の大きさ(リミット)が設定されているので、テーブルのサイズを超えたセレクタの値を使えば例外が発生します。そしてもちろんLDTRにもディスクリプタはキャッシュされています。

何だか仮想アドレスから実アドレスを求めるのに随分と手間をかけているようにも見えますが、これでOSが直接アクセスするメモリとタスクごとに使うメモリを分離し、タスクを切り替えた時にも参照するテーブルを取り替えるだけで独立したメモリ空間を確保できているわけです。そしてタスクごとに必要に応じてメモリ空間を実メモリに割り当てたり解放したすることで、限りある実メモリを効率よく使う仕組みが作られているのですね。

80286はひとつのセレクタの値で扱えるメモリ空間は最大でも64Kなので、複数のセレクタを上手に使い分ける必要があります。ひとつのデータで64Kを超えるデータを簡単に扱うことができません。そのためメモリモデルという概念が導入され、コードやデータのポインタをセレクタを含まない16ビットで処理するのか、セレクタを含んだ32ビットで処理するのかを選ぶ必要があり、nearポインタであるとかfarポインタ(そしてhugeポインタ)を使い分けるのにとても苦労した覚えがあります。

次は、いよいよタスクの管理の仕組みを調べます。






この記事が気に入ったらサポートをしてみませんか?