私はオペレーティングシステムとx86アーキテクチャを研究しており、セグメンテーションとページングについて読んでいる間、当然のことながら、最近のOSがメモリ管理をどのように処理するのか興味がありました。私がLinuxと他のほとんどのオペレーティングシステムを見つけたことから、基本的にページングを支持してセグメンテーションを回避します。私が見つけたこの理由のいくつかは、シンプルさと移植性でした。
セグメンテーション(x86またはそれ以外)にはどのような実用的な用途がありますか?それを使用して堅牢なオペレーティングシステムが見られるか、ページングベースのシステムが引き続き好まれますか?.
これは負荷の高い問題であることはわかっていますが、新しく開発されたオペレーティングシステムでセグメンテーションがどのように処理されるのか知りたいです。ページングを支持することはそれほど意味がなく、より「セグメント化された」アプローチを誰も検討しないでしょうか?もしそうなら、なぜですか?
そして、私が「回避」セグメンテーションと言うとき、私はLinuxがそれを必要な範囲でのみ使用することを意味しています。ユーザーおよびカーネルのコード/データセグメント用に4つのセグメントのみ。 Intelのドキュメントを読んでいると、セグメンテーションはより堅牢なソリューションを念頭に置いて設計されていると感じました。その後、私は何度もx86がどれほど複雑になる可能性があるかと言われました。
LinuxについてのTorvaldの最初の「発表」にリンクされた後、私はこの興味深い逸話を見つけました。彼はこれを数回投稿した後、
簡単に言えば、移植は不可能だと思います。ほとんどがCですが、ほとんどの人は私がCと書いたものを呼ばないでしょう。これは、386について教えてくれるプロジェクトでもあったため、私が見つけた386の考えられるすべての機能を使用しています。 、ページング(まだディスクではない)とセグメンテーションの両方。それは本当に386に依存するセグメンテーションです(すべてのタスクにはコードとデータ用の64Mbセグメントがあります-4Gbで最大64タスク。64Mb/タスク以上を必要とする人-タフなCookie)。
私自身がx86を使った実験でこの質問をするようになったと思います。 LinusにはStackOverflowがなかったので、実際に試すために実装しました。
セグメンテーションを使用すると、たとえば、動的に割り当てられた各オブジェクト(malloc)を独自のメモリセグメントに配置できます。ハードウェアはセグメントの制限を自動的にチェックし、セキュリティバグ(バッファオーバーラン)のクラス全体が排除されます。
また、すべてのセグメントオフセットはゼロから始まるため、すべてのコンパイル済みコードは自動的に位置に依存しなくなります。別のDLLを呼び出すと、(呼び出された関数に応じて)一定のオフセットで遠方の呼び出しに要約されます。これにより、リンカーとローダーが大幅に簡略化されます。
4つの保護リングを使用すると、よりきめの細かいアクセス制御(ページングでは、ユーザーとスーパーバイザの2つの保護レベルしかない)とより堅牢なOSカーネルを考案することができます。たとえば、リング0のみがハードウェアに完全にアクセスできます。コアOSカーネルとデバイスドライバーをリング0と1に分離することにより、関連するアクセスチェックのほとんどがHWによって実行される、より堅牢で非常に高速なマイクロカーネルOSを作成できます。 (デバイスドライバーは、TSSのI/Oアクセスビットマップを介してハードウェアにアクセスできます。)
ただし、x86は少し制限されています。 「フリー」データセグメントレジスタは4つしかありません。それらをリロードすることはかなりコストがかかり、同時に8192セグメントのみにアクセスすることが可能です。 (アクセス可能なオブジェクトの数を最大化するために、GDTがシステム記述子とLDT記述子のみを保持すると仮定します。)
現在、64ビットモードのセグメンテーションは「レガシー」と呼ばれ、ハードウェア制限チェックは限られた状況でのみ行われます。私見、大きな間違い。実際、私はIntelを非難せず、ほとんどの場合開発者を非難します。開発者の大多数は、セグメンテーションが「複雑すぎる」と考え、フラットなアドレス空間を求めていました。また、セグメンテーションを有効に活用するための想像力に欠けていたOSライターのせいです。 (AFAIK、OS/2は、セグメンテーション機能をフルに活用した唯一のオペレーティングシステムでした。)
簡単に言えば、セグメンテーションはハックであり、メモリをアドレス指定する機能が制限されたプロセッサがこれらの制限を超えるために使用されます。
8086の場合、チップ上に20本のアドレスラインがあり、物理的に1Mbのメモリにアクセスできました。ただし、内部アーキテクチャは、おそらく8080との一貫性を保持したいため、16ビットアドレッシングに基づいていました。したがって、命令セットには、16ビットインデックスと組み合わせて1Mbのメモリ全体のアドレッシングを可能にするセグメントレジスタが含まれていました。 。 80286は、セグメントベースの保護とより多くのメモリのアドレス指定をサポートするために、このモデルを真のMMUで拡張しました(iirc、16Mb)。
PDP-11の場合、プロセッサの新しいモデルでは、16ビットアドレススペースの制限をサポートするために、命令スペースとデータスペースにセグメント化されていました。
セグメンテーションの問題は単純です。プログラムはアーキテクチャの制限を明示的に回避する必要があります。 8086の場合、これは、アクセスできるメモリの最大連続ブロックが64kであることを意味しました。それ以上にアクセスする必要がある場合は、セグメントレジスタを変更する必要があります。つまり、Cプログラマにとっては、Cコンパイラに生成するポインタの種類を指示する必要がありました。
32ビットの内部アーキテクチャと24ビットの物理アドレス空間を備えたMC68kをプログラミングする方がはるかに簡単でした。
80x86の場合、「なし」、セグメンテーションのみ、ページングのみ、およびセグメンテーションとページングの両方の4つのオプションがあります。
「何もない」(セグメンテーションもページングもない)の場合、プロセスをそれ自体から簡単に保護する方法、プロセスを相互に保護する簡単な方法、物理アドレス空間の断片化などを処理する方法、位置を回避する方法がなくなる独立したコードなど。これらすべての問題にもかかわらず、(理論的には)状況によっては役立つ場合があります(たとえば、1つのアプリケーションのみを実行する組み込みデバイス、またはJITを使用してすべてを仮想化するものなど)。
セグメンテーションのみ。 「それ自体からプロセスを保護する」問題はほぼ解決しますが、プロセスが8192を超えるセグメントを使用する場合(プロセスごとに1つのLDTを想定)、それを使用可能にするためには多くの回避策が必要です。 「プロセスを互いに保護する」問題をほぼ解決します。ただし、同じ特権レベルで実行されているソフトウェアの異なる部分は、互いのセグメントをロード/使用できます(これを回避する方法-コントロール転送中のGDTエントリの変更および/またはLDTの使用)。また、「位置に依存しないコード」の問題もほとんど解決します(「セグメントに依存するコード」の問題が発生する可能性がありますが、それほど重要ではありません)。 「物理アドレス空間の断片化」の問題に対しては何もしません。
ページングのみ。 「それ自体からプロセスを保護する」問題はあまり解決されません(ただし、正直に言うと、これは本当に安全でない言語で書かれたコードのデバッグ/テストの問題であり、とにかくvalgrindのようなはるかに強力なツールがあります)。 「相互にプロセスを保護する」問題を完全に解決し、「位置独立コード」問題を完全に解決し、「物理アドレス空間の断片化」問題を完全に解決します。追加のボーナスとして、ページングなしでは実用的ではないいくつかの非常に強力なテクニックが開かれます。 「コピーオンライト」、メモリマップファイル、効率的なスワップスペース処理などを含みます。
ここで、セグメンテーションとページングの両方を使用すると、両方の利点が得られると思います。理論的には可能ですが、セグメンテーションから得られる唯一の利点(ページングではうまくいかない)は、「プロセスをそれ自体から保護する」問題の解決策であり、誰も本当に気にしません。実際に得られるのは、両方の複雑さとオーバーヘッドであり、ほとんどメリットがありません。
これが、80x86向けに設計されたほとんどすべてのOSがメモリ管理にセグメンテーションを使用しない理由です(CPUごとやタスクごとのストレージなどに使用しますが、これは、これらのより有用な汎用レジスターを消費しないようにするための便宜のためだけです)物)。
もちろん、CPUメーカーは馬鹿げているわけではありません。彼らは、誰も使用していないことを知っているものを最適化するために時間とお金を費やすことはありません(ほとんどすべての人が代わりに使用するものを最適化します)。このため、CPUメーカーはセグメンテーションを最適化せず、セグメンテーションを予想以上に遅くし、OS開発者はそれをさらに回避したいと考えています。ほとんどの場合、下位互換性のためにセグメンテーションを維持するだけです(これは重要です)。
最終的に、AMDはロングモードを設計しました。心配する必要がある古い/既存の64ビットコードはなかったため、(64ビットコードの場合)AMDは可能な限り多くのセグメンテーションを取り除きました。これにより、OS開発者は、セグメンテーションを回避し続けるためのもう1つの理由(セグメンテーション用に設計されたコードを64ビットに移植する簡単な方法はありません)を与えました。
この質問が投稿されて以来、セグメント化されたメモリアーキテクチャの起源とそれらが提供できる真のパワーについて誰も言及していないことに、私はむしろ驚かされます。
セグメント化されたページ化された仮想メモリシステム(対称型マルチプロセッシングと階層型ファイルシステムと共に)の設計と使用を取り巻くすべての機能を発明または有用な形式に改良した元のシステムは Multics (および Multicians サイトも)。セグメント化されたメモリにより、Multicsはeverythingが(仮想)メモリ内にあるというビューをユーザーに提供し、すべて直接形式(つまり、メモリ内で直接アドレス指定可能)。ファイルシステムは、メモリ内のすべてのセグメントへの単なるマップになります。体系化された方法で(Multicsのように)適切に使用すると、セグメント化されたメモリは、セカンダリストレージの管理、データの共有、プロセス間通信などの多くの負担からユーザーを解放します。他の回答では、セグメント化されたメモリの使用はより困難であるとの手触りの主張がいくつかありますが、これは単に真実ではなく、Multicsは数十年前に大成功を収めました。
Intelは80286のセグメント化メモリのホブリングバージョンを作成しました。これは非常に強力ですが、その制限により、本当に役立つものに使用することができませんでした。 80386はこれらの制限を改善しましたが、当時の市場勢力は、これらの改善を真に利用できるシステムの成功をほとんど妨げていました。過去数年の間に、過去の教訓を無視することを学んだ人が多すぎたようです。
Intelはまた、 iAPX 432 と呼ばれるより優れたスーパーマイクロの構築を早い段階で試みましたが、当時は他のものをはるかに上回っていました。また、オブジェクト指向を強く指向するセグメント化メモリアーキテクチャとその他の機能を備えていました。プログラミング。ただし、元の実装は遅すぎたため、それを修正するためのさらなる試みは行われませんでした。
Multicsがセグメンテーションとページングをどのように使用したかの詳細な説明は、Paul Greenの論文 Multics Virtual Memory-Tutorial and Reflections にあります。
セグメンテーションは、16ビットプロセッサで最大1MBのメモリをアドレス指定できるようにするためのハック/回避策でした。通常は64Kのメモリしかアクセスできませんでした。
32ビットプロセッサが登場すると、フラットメモリモデルで最大4 GBのメモリをアドレス指定できるようになり、セグメンテーションの必要がなくなりました-プロテクトモードでのGDT /ページングのセレクターとしてセグメントレジスタが再利用されました(ただし、プロテクトモード16ビット)。
また、フラットメモリモードはコンパイラにとってはるかに便利です。 Cで16ビットのセグメント化されたプログラム を記述できますが、少し面倒です。フラットメモリモデルでは、すべてが簡単になります。
一部のアーキテクチャ(ARMなど)は、メモリセグメントをまったくサポートしていません。 Linuxがセグメントに依存するソースだったとしたら、それらのアーキテクチャに移植するのは簡単ではありませんでした。
全体像を見ると、メモリセグメントの障害は、Cとポインタ演算の人気が高まっていることに関係しています。 C開発は、フラットメモリを備えたアーキテクチャではより実用的です。フラットメモリが必要な場合は、メモリページングを選択します。
80年代の初め頃、インテルは組織として、エイダやその他の高水準プログラミング言語の将来の人気を予測していました。これは基本的に、ひどいAPX432や286のメモリセグメンテーションなど、より壮観な障害のいくつかが原因です。 386で、彼らはフラットメモリプログラマーに降伏しました。ページングとTLBが追加され、セグメントが4GBにサイズ変更可能になりました。そして、AMDは基本的にx86_64でセグメントを削除し、base regをdont-care/implied-0にしました(fsを除いて?
そうは言っても、メモリセグメントの利点は明白です。TLBを再配置することなくアドレス空間を切り替えることができます。多分いつか誰かがセグメンテーションをサポートするパフォーマンス競争力のあるCPUを作るでしょう、私たちはそれのためにセグメンテーション指向のOSをプログラムでき、プログラマーはAda/Pascal/D/Rust/another-langage-not-required-flatを作ることができます-それのためのメモリプログラム。
セグメンテーションは、アプリケーション開発者にとって大きな負担です。これが、セグメンテーションを廃止するために大きなプッシュが生まれた場所です。
興味深いことに、インテルがこれらの古いモードのすべてのレガシーサポートを取り除いた場合、i86がどれほど優れているかをよく疑問に思います。ここでより良いことは、より低い電力とおそらくより速い操作を意味します。
インテルが16ビットのセグメントで牛乳を酸っぱくして、開発者の一種の反乱を引き起こしたと私は思うかもしれません。しかし、最新のアプリを見ると、64kのアドレス空間は特に何もないことに直面します。競争はi86のアドレス空間の問題に効果的に対抗できるので、結局彼らは何かをしなければなりませんでした。
セグメンテーションにより、ページの変換とスワッピングが遅くなります
これらの理由により、セグメンテーションはx86-64では大幅に削除されました。
それらの主な違いは次のとおりです。
構成可能なセグメント幅を持つ方が賢く見えるかもしれませんが、プロセスのメモリサイズを増やすと、断片化は避けられません。
| | process 1 | | process 2 | |
----------- -----------
0 max
プロセス1が成長するにつれて、最終的には次のようになります。
| | process 1 || process 2 | |
------------------ -------------
0 max
分割が避けられないまで:
| | process 1 part 1 || process 2 | | process 1 part 2 | |
------------------ ----------- ------------------
0 max
この時点で:
ただし、固定サイズのページの場合:
固定サイズのメモリチャンクは管理が簡単で、現在のOS設計を支配しています。
参照: https://stackoverflow.com/questions/18431261/how-does-x86-paging-work