ぐだぐだ低レベルプログラミング(241)x86(16/32bit)、セグメントデスクリプタ

Joseph Halfmoon

前回、286タスクステートセグメントなる新たなメモリ上のオブジェクト登場。しかしタスク・スイッチには「タスク・ゲート」などさらに新たなものどもがついてくるのでした。いったい何かどれだけあるの?今回は、セグメント・デスクリプタなるものを列挙してみたいと思います。286だけでなく386以降の32ビットもチョイ含むっと。

※「ぐだぐだ低レベル プログラミング」投稿順indexはこちら

※実機動作確認(といってもエミュレータなんだけれども)には以下を使用させていただいております。

    •  Windows 11 PC (i5-1235U)
    •  Ubuntu 24.04 LTS on WSL2
    •  QEMU 8.2.2
    •  FreeDOS 1.3
GDT、LDT、IDT

さて、前回までに2つ、デスクリプタ・テーブルというものがメモリ上にあり、286以降のCPUは、プロテクトモードに遷移するとそれを参照するようになると説明しました。実はもう一つテーブルがあります。全貌が以下に

    • IDT(インタラプト・デスクリプタ・テーブル)
    • GDT(グローバル・デスクリプタ・テーブル)
    • LDT(ローカル・デスクリプタ・テーブル)

新登場のIDTは、ぶっちゃけリアルモードの割り込みベクタテーブルに相当するものです。リアルモードでは、割り込みや例外が発生したときに、メモリの最下位に置かれている割り込みベクタテーブルを参照し、そこにセグメントアドレスとオフセットが書かれているので、その指し示す番地に制御を移すのでした。

一方、プロテクトモードでは、IDTというデスクリプタテーブルに何種類もある「割り込みゲート」だの「トラップ・ゲート」などいうゲート様が鎮座しており、ハードウエア割り込み、ソフトウエア割り込み、ソフトウエアにより発生する例外は、みなIDTでの検査を受けてから、さらに先で示される実際の命令アドレスに制御を移すという機構になってます。

今回、まず、3つのデスクリプタ・テーブルの中に置かれているデスクリプタにはどんなものがあるのか、まず見ていきたいと思います。

デスクリプタのレイアウト

16ビットの80286でも、32ビット化された80386以降のプロセッサでも1個のデスクリプタはメモリ上で8バイトの領域を占めます。以前述べたとおり、GDTやLDTであれば最大8K個のデスクリプタを格納できます。新登場のIDTは、割り込みや例外の番号が0から255なので、合計256個分デス。

8バイトの領域のうち、

    • 下の6バイトは286でも386以降でも共通
    • 上の2バイトは286では予約(0フィル)、386以降で使用

ということになってます。つまり16ビットの286では上の2バイトは使っていないのですが、必ずオール0でなければならないということになってます。ここにミソ(「デフォルト」ビット)があり、286のセグメンテーションと386以降の32ビットモード下でのセグメンテーションを共存させるようになってます。

まずは、メモリ上、プログラムやデータを置くためのメモリ領域であるメモリ・セグメントを記述するデスクリプタを見ていきます。MemorySegmentDescriptor

一番下のLIMIT15..0とかかれている部分がメモリ番地上+0の位置です。横幅はワード(x86では16ビット)です。薄い下の3ワード(6バイト)が286、386以降共通部分、上の濃い1ワードが386以降で拡張された部分です。

下から見ていくと、16ビット幅のLIMITフィールドがあります。これがセグメントのサイズを示してます。16ビットなので最大64Kバイト長までのセグメントを定義できるようになってます。

つづく3バイトばBASEフィールドです。24ビット幅なので最大16Mバイトまでのメモリアドレスを指すことができるようになってます。16Mバイト=80286の物理メモリ空間デス。

その後のPとかTYPEとかを一旦おいておいて、上にあるG、D、0、AVLという4ビットを見てください。ここは32ビットモードで定義された部分です。ここで、D(デフォルト)ビットが1の場合は、以下のように上記のLIMITとBASEが「継ぎ足され」るようになってます。

まずBASEは、最上位のバイトにもう8ビットあるので、これを継ぎ足して合計32ビット、つまり4Gバイトの空間を指し示すことが可能となります。これは32ビットモードの「線形アドレス空間」の最大値です(まだこの先にページングがあるので物理空間ではない)

次にLIMITは、4ビット分継ぎ足しできるので、合計20ビットとなります。素のバイト単位では1Mバイト・サイズが最大となります。しかし、お隣にGビット(グラニュアリティ)あり、このビットにも1を立ててやることで、その20ビットはバイト単位ではなく4Kバイト・ページ単位であることになるのです。勿論4Kバイトは386以降が備えるページングの基本単位です。これにより20+12(4Kページ)で、最大4Gバイトのサイズまで1個のセグメントをデカくすることができます。およよ。

なお、AVLはOSなどが利用してかまわないビットらしいです。

さて、286、386に共通なP、DPL、DT、TYPEというフィールドを見ていきます。まずメモリ・セグメントにおいてはDTは1です。DTを0にすると後でみる他のセグメントの意味になります。Pはプレゼントで、セグメントの在、不在を示すもの、DPLはメンドクセー、プリビレッジレベルというものです。該当のデスクリプタの特権ね。そして最後のTYPE部分はこんな感じ。MemorySegmentType

 

下にある1ビットAは、該当セグメントが実際にプロセッサにロードされて、アクセス中だったら1、そうでなかったら0となるアクセスビットです。プロセッサはイチイチこんなところまでハードで上げ下げしてます。他のタイプは上の図でだいたい分かるでしょう。?なのは、上方伸長、下方伸長、コンフォーミングですかね。上方伸長は、ベースアドレスを下位の基点としてメモリアドレスの上方に伸びる「フツー」のセグメントです。一方、下方伸長は、ベースアドレスを上位の基点としてメモリアドレスの下方の伸びる「スタックなどに使いやすい」セグメント(決まりはないが)です。コンフォーミングは、高い特権のコードセグメントを低い特権の他のセグメントから呼び出ししやすくするための便宜的に特権を操作するための仕組みです。OSのサービスなどを、特権の低いアプリから呼び出せるようにしたいときに使うみたい。

ここまではメモリブロックを管理するためのメモリ・セグメント・デスクリプタでした。以下は、前回登場のTSS(タスク・ステート・セグメント)やLDT自体を管理するためのシステム・セグメント・デスクリプタです。SystemSegmentDescriptor

上記を見ると分かるとおり、DTビットが0であることを除けば、メモリ・セグメント・デスクリプタと似ています。

ただし、TYPEについてはゲート・デスクリプタと共通で列挙した方が良いので、先にゲート・デスクリプタのレイアウトを掲げます。GateDescriptor

ゲート・デスクリプタが支配する制御転送についてはまた次回とするつもりですが、簡単にいうと「なんちゃらゲート」共は、

他のデスクリプタを参照している間接的なデスクリプタ

です。「なんちゃらゲート」単体では完結せず、そこでナンヤカンヤ検査をしたり準備をした上で、既に登場している他のセグメント・デスクリプタに後はお任せ、するスタイルです。

システム・セグメント・デスクリプタとゲート・デスクリプタに共通するTYPEフィールドを列挙したものが以下に。System_Gate_DescriptorType

前回登場のTSSもここに登場してます。286と386(386以降の32ビットモード)の2種類、そしてビジーとビジーでないの両ステータスあります。コール・ゲート、割り込みゲート、トラップゲートなどというゲートどもあり、これらも286と386の2種類。

今回はこの辺でおなか一杯、消化不良っと。また今度ね。

ぐだぐだ低レベルプログラミング(240)x86(16bit)、286タスクステートセグメント へ戻る

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です