Linuxはセグメンテーションを使用せず、ページングのみを使用しますか?


24

Linuxプログラミングインターフェイスは、プロセスの仮想アドレススペースのレイアウトを表示します。図の各領域はセグメントですか?

ここに画像の説明を入力してください

Linuxカーネルの理解から、

MMUのセグメンテーションユニットがセグメントとセグメント内のオフセットを仮想メモリアドレスにマップし、ページングユニットが仮想メモリアドレスを物理メモリアドレスにマップすることを意味するのは正しいですか?

メモリ管理ユニット(MMU)は、セグメンテーションユニットと呼ばれるハードウェア回路を使用して、論理アドレスを線形アドレスに変換します。その後、ページングユニットと呼ばれる2番目のハードウェア回路が線形アドレスを物理アドレスに変換します(図2-1を参照)。

ここに画像の説明を入力してください

それでは、なぜLinuxはセグメンテーションを使用せず、ページングのみを使用すると言うのですか?

80x86マイクロプロセッサにはセグメンテーションが含まれており、プログラマがアプリケーションを論理的に関連するエンティティ(サブルーチン、グローバルデータエリア、ローカルデータエリアなど)に分割できるようになっています。ただし、 Linuxは非常に限られた方法でセグメンテーションを使用します。実際、セグメント化とページングは​​どちらもプロセスの物理アドレス空間を分離するために使用できるため、やや冗長です。セグメンテーションは各プロセスに異なる線形アドレス空間を割り当てることができますが、ページングは​​同じ線形アドレス空間を異なる物理アドレス空間にマッピングできます。Linuxでは、次の理由により、セグメンテーションよりもページングが優先されます。

•すべてのプロセスが同じセグメントレジスタ値を使用する場合、つまり、同じ一連のリニアアドレスを共有する場合、メモリ管理はより簡単になります。

•Linuxの設計目標の1つは、幅広いアーキテクチャへの移植性です。特に、RISCアーキテクチャでは、セグメンテーションのサポートが制限されています。

Linuxの2.6バージョンは、80x86アーキテクチャで必要な場合にのみセグメンテーションを使用します。


エディションを指定してください。著者名を指定すると役立つ場合があります。少なくとも最初は著名な人物からのものです。しかし、両方のタイトル名は少し一般的であり、最初はあなたが本について話していたことは私には明らかではありませんでした:-)。
sourcejedi

2
Re "セグメンテーションは80x86マイクロプロセッサに含まれています...":それは事実ではありません。16ビットのデータポインターと64 Kbのメモリセグメントを備えた808xプロセッサーの遺産です。セグメントポインターを使用すると、セグメントを切り替えてより多くのメモリをアドレス指定できます。そのアーキテクチャは80x86に引き継がれました(ポインターサイズは33ビットに増加しました)。現在x86_64モデルには、(理論的には48ビットのみが実際に使用されていると思われる)16エクサバイトをアドレス指定できる64ビットポインターがあるため、セグメントは必要ありません。
-jamesqf

2
@jamesqf、まあ、386の32ビット保護モードは、8086にある16バイトスケールポインターとはまったく異なるセグメントをサポートしているため、単なるレガシーではありません。もちろん、それらの有用性については何も言っていません。
イルカチュウ

80186 @jamesqf 8086のように、全く「33ビット」は、同じメモリ・モデルを有していなかった
Jasen

答えに値するものはないので、コメントのみです。セグメントとページはスワッピングのコンテキスト(ページスワッピングとセグメントスワッピングなど)でのみ比較可能であり、そのコンテキストでページスワッピングはセグメントスワッピングを水中から吹き飛ばします。セグメントをスワップイン/アウトする場合、セグメント全体をスワップする必要があります。これは2〜4GBです。これはx86で使用される本物ではありませんでした。ページを使用すると、常に4KB単位で作業できます。メモリへのアクセスに関しては、ページテーブルを介してセグメントとページが関連付けられ、比較はオレンジとオレンジになります。
vhu

回答:


20

x86-64アーキテクチャは、ロングモード(64ビットモード)でセグメンテーションを使用しません。

4つのセグメントレジスタ:CS、SS、DS、およびESは0に強制され、制限は2 ^ 64に強制されます。

https://en.wikipedia.org/wiki/X86_memory_segmentation#Later_developments

OSが使用可能な「線形アドレス」の範囲を制限することはできなくなりました。したがって、メモリ保護にセグメンテーションを使用できません。ページングに完全に依存する必要があります。

従来の32ビットモードで実行している場合にのみ適用されるx86 CPUの詳細については心配しないでください。Linuxの32ビットモードはそれほど使用されていません。それは「数年間良性の怠慢の状態にある」とさえ考えられるかもしれません。Fedoraの32ビットx86サポート [LWN.net、2017]を参照してください。

(32ビットLinuxでもセグメンテーションは使用されないことがあります。しかし、私を信頼する必要はありません。無視してください:-)。


それはちょっと言い過ぎです。レガシーの元の8086セグメント(CS / DS / ES / SS)のベース/制限はロングモードで0 / -1に固定されますが、FSとGSには依然として任意のセグメントベースがあります。また、CSにロードされたセグメント記述子は、CPUが32ビットモードで実行されるか64ビットモードで実行されるかを決定します。また、x86-64 Linuxのユーザースペースは、スレッドローカルストレージにFSを使用します(mov eax, [fs:rdi + 16])。カーネルはGS(後swapgs)を使用して、syscallエントリポイントで現在のプロセスのカーネルスタックを見つけます。ただし、はい、セグメンテーションはメインOSメモリ管理/メモリ保護メカニズムの一部として使用されません。
ピーター

これは基本的に、「Linuxの2.6バージョンは80x86アーキテクチャで必要な場合にのみセグメンテーションを使用する」という質問の引用です。しかし、2番目の段落は基本的に間違っています。Linuxは、32ビットモードと64ビットモードでセグメンテーションを基本的に同じように使用します。つまり、通常の命令で暗黙的に使用されるクラシックセグメント(CS / DS / ES / SS)の場合は、base = 0 / limit = 2 ^ 32または2 ^ 64です。32ビットLinuxコードでは、「余分な」心配はありません。HW機能はありますが、使用されていません。
ピーター

@PeterCordesあなたは基本的に間違った答えを解釈している:-)。だから私はそれを編集して、私の議論を曖昧にしないようにしました。
sourcejedi

良い改善、今では誤解を招かない。私はあなたの本当のポイントに完全に同意します。つまり、x86セグメンテーションはosdevシステム管理とTLSにの​​み使用されるため、x86のセグメンテーションを無視できることです。最終的に学習したい場合は、フラットメモリモデルを使用してx86 asmを既に理解していると理解しやすくなります。
ピーター

8

x86にはセグメントがあるため、それらを使用しないことはできません。ただし、cs(コードセグメント)とds(データセグメント)の両方のベースアドレスはゼロに設定されているため、セグメンテーションは実際には使用されません。例外はスレッドローカルデータです。通常は使用されないセグメントレジスタの1つがスレッドローカルデータを指します。しかし、それは主に、このタスクのために汎用レジスターの1つを予約することを避けるためです。

Linuxはx86でセグメンテーションを使用しないとは言っていません。それは不可能だからです。すでに1つの部分を強調しましたが、Linuxは非常に限られた方法でセグメンテーションを使用しています。2番目の部分は、80x86アーキテクチャで必要な場合にのみLinuxがセグメンテーションを使用することです

既に理由を引用しましたが、ページングは​​より簡単で移植性があります。


7

図の各領域はセグメントですか?

いや

セグメンテーションシステム(x86の32ビット保護モード)は、個別のコード、データ、およびスタックセグメントをサポートするように設計されていますが、実際には、すべてのセグメントが同じメモリ領域に設定されます。つまり、それらは0で始まり、メモリの終わりで終了します(*)。これにより、論理アドレスと線形アドレスが等しくなります。

これは「フラット」メモリモデルと呼ばれ、個別のセグメントがあり、その中にポインタがあるモデルよりもいくらか単純です。特に、セグメントセレクターはオフセットポインターに加えて含まれている必要があるため、セグメントモデルには長いポインターが必要です。(16ビットセグメントセレクター+合計48ビットポインターの32ビットオフセット。単なる32ビットフラットポインターに対して。)

64ビットロングモードは、フラットメモリモデル以外のセグメンテーションも実際にはサポートしていません。

286で16ビット保護モードでプログラムする場合、アドレス空間は24ビットですが、ポインターは16ビットしかないため、セグメントがさらに必要になります。

(* 32ビットLinuxがカーネル/ユーザー空間の分離をどのように処理するか思い出せないことに注意してください。セグメンテーションは、ユーザー空間セグメント制限を設定することにより、カーネル空間を含まないようにします。ページングは​​、ページごとの保護レベル。)

それでは、なぜLinuxはセグメンテーションを使用せず、ページングのみを使用すると言うのですか?

x86にはまだセグメントがあり、それらを無効にすることはできません。それらは可能な限り使用されません。32ビット保護モードでは、フラットモデル用にセグメントを設定する必要があり、64ビットモードでもセグメントは存在し続けます。


ええと、32ビットカーネルは、ユーザー空間が2Gまたは3Gを超えてアクセスできないようにCS / DS / ES / SSにセグメント制限を設定することで、ページテーブルを変更するよりも安価にMeltdownを軽減できると思います。(Meltdown vulnは、ページテーブルエントリのカーネル/ユーザービットの回避策であり、ユーザー空間がカーネルのみにマップされているページから読み取ることを許可します)。VDSOページは4Gの上部にマッピングされる場合がありますが wrfsbase、保護された/互換モードではロングモードのみであるため、32ビットカーネルのユーザー空間ではFSベースを高く設定できません。
ピーター

64ビットカーネルでは、32ビットユーザー空間が64ビットコードセグメントに飛ぶ可能性があります。そのため、純粋な32ビットカーネルでのみ、Meltdown保護のセグメント制限に依存できません。(たとえば、スレッドスタックの低メモリが不足するなど、物理RAMが多いマシンでは大きな欠点があります。)とにかく、Linuxはページングでカーネルメモリを保護し、通常のユーザースペースにbase / limit = 0 / -1を残しますセグメント(スレッドローカルストレージに使用されるFS / GSではありません)。
ピーター

ハードウェアページテーブル(PAE)でNXビットがサポートされる前は、一部の初期のセキュリティパッチはセグメンテーションを使用して、ユーザー空間コードの実行不可能なスタックを作成していました。例:linux.com/news/exec-shield-new-linux-security-feature(Ingo Molnarの投稿では、「Solar Designerの優れた「非execスタックパッチ」」に言及しています。)
Peter Cordes

3

Linux x86 / 32は、すべてのセグメントを同じ線形アドレスと制限に初期化するという意味でセグメンテーションを使用しません。x86アーキテクチャでは、プログラムにセグメントが必要です。コードはコードセグメントからのみ実行でき、スタックはスタックセグメントにのみ配置でき、データはいずれかのデータセグメントでのみ操作できます。Linuxは、すべてのセグメントを同じ方法で設定することでこのメカニズムをバイパスします(とにかく本で言及されていない例外を除く)。したがって、同じ論理アドレスがどのセグメントでも有効です。実際、これはセグメントをまったく持たないことに相当します。


2

図の各領域はセグメントですか?

これらは、「セグメント」という言葉のほぼまったく異なる2つの使用法です。

  • x86セグメンテーション/セグメントレジスタ:最新のx86 OSは、32ビットモードですべてのセグメントが同じbase = 0およびlimit = maxであるフラットメモリモデルを使用します。 。(FSまたはGSを除き、64ビットモードでもスレッドローカルストレージに使用されます。)
  • リンカー/プログラムローダーセクション/セグメント。(ELFファイル形式のセクションとセグメントの違いは何ですか

用法は、共通の起源を持っている:あなたがいる場合された(特にページ仮想メモリなし)セグメント化されたメモリ・モデルを使用して、あなたはデータを持っている可能性があり、BSSアドレスはDSセグメントベース、SSベースにスタック比べ、およびへのコードの相対に対して相対的CSベースアドレス。

そのため、セグメントベースに関連する16ビットまたは32ビットのオフセットを変更せずに、複数の異なるプログラムを異なる線形アドレスにロードしたり、起動後に移動することさえできます。

ただし、ポインタがどのセグメントに関連するかを知る必要があるため、「遠いポインタ」などがあります。(実際の16ビットx86プログラムは、データとしてコードにアクセスする必要がないことが多いので、どこかで64kコードセグメントを使用でき、DS = SSで別の64kブロックを使用できます。または、すべてのセグメントベースが等しい小さなコードモデル)。


x86セグメンテーションとページングの相互作用

32/64ビットモードでのアドレスマッピングは次のとおりです。

  1. segment:offset(オフセットを保持するレジスタによって暗示されるか、命令プレフィックスでオーバーライドされるセグメントベース)
  2. 32ビットまたは64ビットの線形仮想アドレス= base + offset。(Linuxが使用するフラットメモリモデルでは、ポインター/オフセット=線形アドレスも。FSまたはGSに関連するTLSにアクセスする場合を除きます。)
  3. ページテーブル(TLBによってキャッシュ)は、32(レガシーモード)、36(レガシーPAE)、または52ビット(x86-64)の物理アドレスにリニアにマッピングします。(/programming/46509152/why-in-64bit-the-virtual-address-are-4-bits-short-48bit-long-compared-with-the)。

    この手順はオプションです。制御レジスタのビットを設定して、起動時にページングを有効にする必要があります。ページングがない場合、線形アドレスは物理アドレスです。

すべてがマップされるフラット(線形)アドレス空間にはオフセット自体と同じビット数しかないため、セグメンテーションでは単一プロセス(またはスレッド)32または64ビットを超える仮想アドレス空間を使用できないことに注意してください。(16ビットx86の場合はそうではありませんでした。セグメンテーションは、実際にはほとんどが16ビットのレジスタとオフセットで64kを超えるメモリを使用するのに役立ちました。)


CPUは、セグメントベースを含むGDT(またはLDT)からロードされたセグメント記述子をキャッシュします。ポインタを間接参照すると、そのレジスタがどのレジスタにあるかに応じて、デフォルトでセグメントとしてDSまたはSSが使用されます。レジスタ値(ポインター)は、セグメントベースからのオフセットとして扱われます。

セグメントのベースは通常ゼロであるため、CPUはこれを特別な場合に行います。あなたがいる場合や、別の観点から、行う非ゼロセグメントベースを持っているベースアドレスを追加するバイパスの「特別」(ノーマル)の場合は適用されないため、負荷が余分な待ち時間を有します。


Linuxがx86セグメントレジスタを設定する方法:

CS / DS / ES / SSのベースと制限は、32および64ビットモードですべて0 / -1です。これは、すべてのポインターが同じアドレス空間を指すため、フラットメモリモデルと呼ばれます。

(AMD CPUアーキテクトは、メインストリームOSがそれを使用していないため、64ビットモードのフラットメモリモデルを強制することでセグメンテーションを廃止しました。ただし、PAEまたはx86- 64ページの表形式。)

  • TLS(スレッドローカルストレージ):FSおよびGSは、ロングモードでbase = 0に固定されていません。(これらは386で新しく追加されたもので、repESを使用する-string命令でさえも、暗黙的に使用されていません。)x86-64 Linuxは、各スレッドのFSベースアドレスをTLSブロックのアドレスに設定します。

    たとえばmov eax, [fs: 16]、32ビット値を16バイトからこのスレッドのTLSブロックにロードします。

  • CSセグメント記述子は、CPUのモードを選択します(16/32/64ビット保護モード/ロングモード)。Linuxは、すべての64ビットユーザー空間プロセスに単一のGDTエントリを使用し、すべての32ビットユーザー空間プロセスに別のGDTエントリを使用します。(CPUが正しく機能するためには、DS / ESも有効なエントリに設定する必要があり、SSも同様です)。また、特権レベル(カーネル(リング0)とユーザー(リング3))を選択するため、64ビットのユーザー空間に戻った場合でも、カーネルは、通常の使用の代わりに、iretまたはsysretジャンプまたはリット命令。

  • x86-64では、syscallエントリポイントはswapgsGSをユーザー空間のGSからカーネルのGSにフリップするために使用し、このスレッドを使用してこのスレッドのカーネルスタックを見つけます。(スレッドローカルストレージの特殊なケース)。syscall命令は、カーネルスタック点までスタックポインタを変更しません。カーネルがエントリポイント1に到達しても、まだユーザースタックを指し示しています。

  • DS / ES / SSは、CPUが保護モード/ロングモードで動作するために有効なセグメント記述子に設定する必要がありますが、これらのディスクリプタのベース/制限はロングモードでは無視されます。

そのため、基本的にx86セグメンテーションはTLSに使用され、ハードウェアが必要とする必須のx86 osdevに使用されます。


脚注1:おもしろい歴史:AMD64シリコンがリリースされる数年前からカーネル開発者とAMDアーキテクトの間でメッセージのメーリングリストアーカイブがあり、設計が微調整されてsyscall使用可能になりました。詳細については、この回答のリンクを参照してください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.