x86メモリセグメンテーションとは、 Intel x86コンピュータ命令セットアーキテクチャに特徴的なメモリセグメンテーションの一種を指す用語です。x86アーキテクチャは、初代Intel 8086(1978年)以来メモリセグメンテーションをサポートしてきましたが、 x86メモリセグメンテーションは、単に説明的なレトロニムです。このアーキテクチャにおけるメモリセグメンテーション機構の導入は、初期の80xxプロセッサの遺産を反映しています。80xxプロセッサは当初[ 1 ] 16KB、後に[ 2 ] 64KB(それぞれ16,384バイトまたは65,536バイト)のメモリしかアドレス指定できず、命令とレジスタは後者に最適化されていました。そのため、より大きなアドレスとより多くのメモリを扱うことは、Intel 8086でその機能が後付けされたため、比較的に遅くなりました。メモリセグメンテーションにより、プログラムの互換性とメモリ内での再配置が可能になり、プログラムの動作の重要な部分を64KBセグメントに制限することで、プログラムの実行速度を向上させることができました。
1982年、Intel 80286は仮想メモリとメモリ保護のサポートを追加しました。元のモードはリアルモードと改名され、新しいバージョンはプロテクトモードと名付けられました。[ 3 ] 2003年に導入されたx86-64 アーキテクチャは、64ビットモードでのセグメンテーションのサポートをほぼ廃止しました。
リアルモードとプロテクトモードの両方において、システムは16ビットのセグメントレジスタを使用して実際のメモリアドレスを導出します。リアルモードでは、レジスタCS、DS、SS、ESは、現在使用されているプログラムコードセグメント(CS)、現在のデータセグメント(DS)、現在のスタックセグメント(SS)、およびシステムプログラマによって決定される1つの追加セグメント(ES)を指します。1985年に導入されたIntel 80386では、ハードウェアによって特定の用途が定義されていない2つのセグメントレジスタFSとGSが追加されています。セグメントレジスタの使用方法は、2つのモードで異なります。[ 4 ]
セグメントの選択は通常、実行される関数に応じてプロセッサによってデフォルト設定されます。命令は常にコードセグメントからフェッチされます。スタックへのデータ参照(スタックへのプッシュやポップを含む)はすべてスタックセグメントを使用します。BPレジスタを介して間接的に参照されるデータ参照は通常スタックを参照するため、デフォルトでスタックセグメントが使用されます。エクストラセグメントは文字列操作(例えばMOVSやCMPS)の必須の宛先です。この目的のためだけに、自動的に選択されたセグメントレジスタをオーバーライドすることはできません。その他のすべてのデータ参照はデフォルトでデータセグメントを使用します。データセグメントは文字列操作のデフォルトのソースですが、オーバーライドできます。命令フォーマットでは、必要に応じて選択した命令のデフォルトセグメントをオーバーライドするために使用できるオプションのセグメントプレフィックスバイトが使用できます。 [ 5 ]

リアルモードまたはV86モードでは、セグメントの基本サイズは65,536バイトで、個々のバイトは16ビットのオフセットを使用してアドレス指定されます。
セグメントレジスタ内の 16 ビットセグメントセレクタは、セグメントアドレスと呼ばれる 20 ビットの線形アドレスの最上位 16 ビットとして解釈され、残りの 4 ビットの最下位ビットはすべてゼロです。セグメントアドレスは常に命令内の 16 ビットオフセットに加算され、線形アドレスが生成されます。これは、このモードでは物理アドレスと同じです。たとえば、セグメントアドレス 06EFh:9234h (ここで接尾辞「h」は 16 進数を意味します)のセグメントセレクタは 06EFh で、これはセグメントアドレス 06EF0h を表し、これにオフセットが加算されて、線形アドレス 06EF0h + 9234h = 10124h が生成されます。
(ここでは、線形アドレス、セグメントアドレス、セグメントフィールド、オフセットフィールドの先頭のゼロを分かりやすく表示していますが、通常は省略されます。)
セグメントアドレスとオフセットの加算方法により、1つの線形アドレスを最大 2¹² = 4096 通りの異なるセグメント:オフセットのペアにマッピングできます。たとえば、線形アドレス 08124h は、セグメントアドレス 06EFh:1234h、0812h:0004h、0000h:8124h などを持つことができます。これは、一意のアドレス指定方式に慣れているプログラマにとっては混乱を招く可能性がありますが、複数のネストされたデータ構造をアドレス指定する場合など、利点として利用することもできます。
リアルモードのセグメントは技術的には常に64KBの長さですが、実際には、プログラムで実際に使用されるすべてのセグメントが64KBの長さとして扱われるのではなく、どのセグメントも64KBより長くすることはできないという効果しかありません。つまり、より短いセグメントを扱うことは可能です。使用可能なサイズは16バイト刻みで、16バイトから65,536バイトまでです。リアルモードには保護や権限の制限がないため、セグメントの境界内に収まるように調整するのは完全にプログラムの責任です。これは、セグメントがプログラムによって64KBより短い場合も、64KB全体として扱われる場合も同様ですが、セグメントセレクタを任意に設定してセグメントアドレスを変更できるため、どのプログラムでもセグメントを変更するだけで任意のメモリにアクセスできるという点も当てはまります。したがって、リアルモードはさまざまなセグメント長を許可し、セグメントを必要に応じて重複させたり重複させなかったりできると考えることができますが、これらのいずれもCPUによって厳密に強制されることはありません。
PC/XT世代CPUの実効アドレス空間は20ビットであるため、アドレス指定可能なメモリは2²⁰ バイト、つまり1,048,576 バイト(1MB )に制限されます。これは、アドレスピンが正確に20本だったIntel 8086(および、その後の密接に関連する8088)のハードウェア設計から直接派生したものです。(どちらも40ピンDIPパッケージに収められていました。アドレス線が20本しかないにもかかわらず、アドレスバスとデータバスは多重化され、限られたピン数内にすべてのアドレス線とデータ線を収めていました。)
各セグメントは、線形(フラット)アドレス空間の先頭から16 バイトの倍数(パラグラフ と呼ばれる)の位置から始まります。つまり、16バイト間隔です。すべてのセグメントは技術的には64KB の長さであるため、セグメント間でオーバーラップが発生する理由、および線形メモリアドレス空間内の任意の位置に多数のセグメント:オフセットのペアでアクセスできる理由が説明できます。線形アドレス空間におけるセグメントの開始位置は、セグメント×16で計算できます。このようなアドレス変換は、CPUのセグメンテーションユニットによって実行されます。
最後のセグメント FFFFh (65535) は、 20 ビット アドレス空間の末尾から 16 バイト前の線形アドレス FFFF0h (1048560) から始まるため、最大 65,536 バイトのオフセットで、8086 または 8088 CPU の 20 ビット アドレス空間の末尾から最大 65,520 (65536−16) バイト先までアクセスできます。さらに、次に高い 4,094 個の 64K セグメントも 1MB のしきい値を超えますが、その差はますます小さくなっています。8086 および 8088 CPU では、これらのアドレス アクセスはアドレス空間の先頭にラップアラウンドされるため、65535:16 はアドレス 0 にアクセスし、たとえば 65533:1000 は 線形アドレス空間のアドレス 952 にアクセスします。 8088および8086向けに作成された一部のプログラムが、この独特なラップアラウンド機能を前提としていたため、後の世代のCPU、特にリニアアドレス空間が20ビットを超えて拡張されたIntel 286以降において、ゲートA20の互換性の問題が発生した。
16ビット実モードでは、単一のデータ構造に対して複数のメモリセグメントを使用できるようにする(64Kセグメント1つで利用可能なメモリよりも多くのメモリにアクセスするため)ことは非常に複雑ですが、最小限のツール(メモリ使用量が少ない方が良い)を除いて、必要悪とみなされていました。問題の根本は、メモリ範囲全体をフラットアドレッシングするのに適したアドレス演算命令が存在しないことです。複数の命令を適用することでフラットアドレッシングは可能ですが、プログラムの実行速度が低下します。
メモリモデルの概念は、セグメントレジスタの設定に基づいています。例えば、タイニーモデルではCS=DS=ES=SSとなり、プログラムのコード、データ、スタックはすべて単一の64KB セグメント内に収まります。スモールメモリモデルではDS=SSとなり、データとスタックは同じセグメント内に存在し、CSは最大64KBの別のコードセグメントを指します 。

80286の保護モードでは、プロセッサのアドレス空間が 2 24バイト (16 メガバイト) に拡張されますが、セグメント レジスタの値からセグメント アドレスを計算するために使用されるシフト値を調整することによるものではありません。代わりに、各 16 ビット セグメント レジスタには、オフセットが加算される 24 ビットのベース アドレスを含むセグメント ディスクリプタ テーブルへのインデックスが格納されます。古いソフトウェアをサポートするために、プロセッサは「リアル モード」で起動します。このモードでは、8086 のセグメント アドレス指定モデルを使用します。ただし、わずかな違いがあります。結果として得られる物理アドレスは 20ビットに切り捨てられなくなったため、リアル モードポインタ (8086 ポインタではない) は、100000 16から 10FFEF 16までのアドレスを参照できるようになりました。この約 64 キロバイトのメモリ領域は、高メモリ領域(HMA) として知られており、DOSの後のバージョンでは、DOS の一部を従来のメモリから HMA に移動することで、使用可能な「従来型」メモリ (つまり最初のMB内) を増やすことができました。HMA を追加すると、アドレス空間の合計は約 1.06 MB になります。80286 はリアル モード アドレスを 20 ビットに切り捨てませんが、80286 を搭載したシステムは、プロセッサ外部のハードウェアを使用して、21 番目のアドレス ライン、 A20 ラインをゲートオフすることで、これを実現できます。IBM PC AT は、このためのハードウェアを提供しました (オリジナルのIBM PCおよびPC/XTモデルのソフトウェアとの下位互換性のため)。そのため、その後のすべての「ATクラス」 PC クローンも同様でした。
286 保護モードは、8086/88 マシンを使用している多くのユーザーを排除することになるため、ほとんど使用されませんでした。さらに、リアルモードと同様に、メモリを 64k セグメントに分割する必要がありました。この制限は、64k を超えるサイズのメモリ ポインタの使用を許可する 32 ビット CPU では回避できますが、セグメント制限フィールドは 24 ビット長しかないため、作成できるセグメントの最大サイズは 16MB です (ページングを使用してより多くのメモリを割り当てることはできますが、個々のセグメントは 16MB を超えることはできません)。この方法は、Windows 3.x アプリケーションでフラットなメモリ空間を作成するためによく使用されていましたが、OS 自体はまだ 16 ビットであったため、32 ビット命令で API 呼び出しを行うことができませんでした。そのため、API 呼び出しを実行するすべてのコードを 64k セグメントに配置する必要がありました。
286 保護モードが起動されると、通常はハードウェア リセットを実行する以外に終了することはできませんでした。上昇傾向にあるIBM PC/AT規格に準拠したマシンは、標準化されたキーボード コントローラを介して CPU のリセットを偽装することができましたが、これは非常に動作が遅かったのです。Windows 3.x は、CPU の割り込み処理メカニズムで意図的に三重フォールトを発生させることで、これらの問題を回避しました。これにより、IBM AT 互換ハードウェアが CPU をほぼ瞬時にリセットし、リアル モードに戻ることができました。[ 6 ]
論理アドレスは、16ビットのセグメントセレクタ(13+1ビットのアドレスを提供)と16ビットのオフセットで構成されます。セグメントセレクタは、セグメントレジスタのいずれかに格納されている必要があります。このセレクタは、2ビットの要求特権レベル(RPL)、1ビットのテーブルインジケータ(TI)、および13ビットのインデックスで構成されます。
指定された論理アドレスのアドレス変換を試みるとき、プロセッサは、TI=0 の場合はグローバル記述子テーブルから、TI=1 の場合はローカル記述子テーブルから、64 ビットのセグメント記述子構造を読み取ります。次に、特権チェックを実行します。
ここで、CPLは現在の特権レベル(CSレジスタの下位2 ビット)、RPLはセグメントセレクタから要求された特権レベル、DPLはセグメントの記述子特権レベル(記述子に格納)です。すべての特権レベルは0~3の整数で、数値が小さいほど特権レベルが高くなります。
不等式が偽の場合、プロセッサは汎用保護(GP)フォルトを生成します。そうでない場合は、アドレス変換が続行されます。次に、プロセッサは16ビットオフセットを取得し、セグメント記述子で指定されたセグメント制限と比較します。オフセットが大きい場合は、GPフォルトが生成されます。そうでない場合は、プロセッサは記述子で指定された24ビットセグメントベースをオフセットに加算し、線形物理アドレスを作成します。
セグメント記述子はセグメントレジスタの隠し部分にキャッシュされるため、特権チェックはセグメントレジスタがロードされるときにのみ行われます。 [ 4 ]
Intel 80386以降では、プロテクトモードは80286プロテクトモードのセグメンテーション機構を維持していますが、セグメンテーションユニットと物理バス間のアドレス変換の第2層としてページングユニットが追加されています。また、重要な点として、アドレスオフセットは32ビット(16ビットではなく)となり、各セグメント記述子のセグメントベースも32ビット(24ビットではなく)となっています。セグメンテーションユニットの一般的な動作は、それ以外は変更されていません。ページングユニットは有効または無効にできます。無効の場合、動作は80286と同じです。ページングユニットが有効の場合、セグメント内のアドレスは、80286のように物理アドレスではなく、仮想アドレスになります。つまり、ページングユニットが有効になっている場合、セグメントの開始アドレス、オフセット、およびセグメンテーションユニットが2つを加算して導出した最後の32ビットアドレスはすべて仮想(または論理)アドレスになります。セグメンテーションユニットがこれらの32ビット仮想アドレスを生成および検証すると、有効化されたページングユニットが最終的にこれらの仮想アドレスを物理アドレスに変換します。386プロセッサでは物理アドレスは32ビットですが、物理アドレス拡張をサポートする新しいプロセッサではより大きなアドレスを使用できます。
前述のように、80386では、元の4つのセグメントレジスタ(CS、DS、ES、SS)に加えて、FSとGSという2つの新しい汎用データセグメントレジスタが導入されました。
386 CPUは、CR0制御レジスタのビットをクリアすることでリアルモードに戻すことができますが、これはセキュリティと堅牢性を確保するための特権操作です。比較として、286は、トリプルフォールトや外部ハードウェアの使用など、プロセッサのリセットを強制しない限りリアルモードに戻すことはできませんでした。
x86-64アーキテクチャは、ロングモード(64ビットモード)ではセグメンテーションを使用しません。セグメントレジスタのうち、CS、SS、DS、ESの4つはベースアドレスが0に固定され、2 64に制限されます。セグメントレジスタFSとGSは、ベースアドレスをゼロ以外に設定できます。これにより、オペレーティングシステムはこれらのセグメントを特別な目的で使用できます。従来のモードで使用されるグローバル記述子テーブル機構とは異なり、これらのセグメントのベースアドレスはモデル固有のレジスタに格納されます。x86-64アーキテクチャはさらに、カーネルモードとユーザーモードのベースアドレスを交換できる特別なSWAPGS命令を提供します。
例えば、 x86-64 版のMicrosoft Windowsでは、GS セグメントを使用してスレッド環境ブロック (Thread Environment Block) を指し示します。これは各スレッドの小さなデータ構造で、例外処理、スレッドローカル変数、その他のスレッドごとの状態に関する情報が含まれています。同様に、Linux カーネルもGSセグメントを使用して CPU ごとのデータを格納します。
GS/FS は、gccのスレッドローカルストレージやカナリアベースのスタックプロテクターでも使用されています。
論理アドレスは、 x86アセンブリ言語で明示的に指定できます(例:AT&T構文)。
movl$42,%fs:(%eax); Equivalent to M[fs:eax]<-42) inRTLまたは、Intel構文では:
mov dword [ fs : eax ], 42しかし、セグメントレジスタは通常、暗黙的に使用されます。
x86-32プロセッサではセグメンテーションを無効にすることはできません(64ビットモードでも同様ですが、ここでは説明しません)。そのため、多くの32ビットオペレーティングシステムは、すべてのセグメントのベースを0に設定することでフラットメモリモデルをシミュレートし、プログラムに対してセグメンテーションの影響を最小限に抑えています。例えば、Linuxカーネルでは、汎用セグメントは4つしか設定されていません。
ベースアドレスはすべての場合において0に設定され、制限値は4GiBであるため、セグメンテーションユニットは、プログラムがページング ユニットに到達する前に発行するアドレスには影響を与えません。(もちろん、これは80386以降のプロセッサに限った話であり、それ以前のx86プロセッサにはページングユニットがありません。)
現在のLinuxもGSを使用してスレッドローカルストレージを指し示します。
セグメントは、コードセグメント、データセグメント、システムセグメントのいずれかとして定義できます。セグメントを読み取り専用、読み書き可能、実行可能などに設定するための追加のパーミッションビットも存在します。
プロテクトモードでは、コードはCS(コードセグメントセレクタ)を除くすべてのセグメントレジスタを常に変更できます。これは、プロセッサの現在の特権レベル(CPL)がCSレジスタの下位2ビットに格納されているためです。プロセッサの特権レベルを上げる(およびCSを再ロードする)唯一の方法は、lcall(ファーコール)命令とint(割り込み)命令を使用することです。同様に、特権レベルを下げる(およびCSを再ロードする)唯一の方法は、lret(ファーリターン)命令とiret (割り込みリターン)命令を使用することです。リアルモードでは、コードはファージャンプを行う(または8086または8088で文書化されていない命令を使用する)ことによってCSレジスタを変更することもできます。 [ 7 ] もちろん、リアルモードでは特権レベルはありません。すべてのプログラムは、すべてのメモリとすべてのCPU命令に絶対的に無制限にアクセスできます。 POP CS
セグメンテーションに関する詳細については、AMDまたはIntelのウェブサイトで無料で入手できるIA-32マニュアルを参照してください。
POP CS、命令ポインタから計算される実効アドレスを即座に変更して次の命令をフェッチするため、非常に慎重に使用しなければならず、有用性は限られています。一般的には、ファージャンプの方がはるかに有用です。 の存在はPOP CSおそらく偶然であり、8086 および 8088 の 4 つのセグメントレジスタに対する PUSH および POP 命令オペコードのパターンに従っています。