1. ホーム
  2. x86

[解決済み] x86のページングはどのように機能するのですか?

2022-12-02 04:54:49

質問

この質問は、このテーマに関する良い無料情報の空白を埋めることを意図しています。

私は、良い答えは一つの大きなSOの答えに収まるか、少なくともいくつかの答えに収まると信じています。

主な目標は、完全な初心者が自分でマニュアルを読んで、ページングに関連する基本的なOSの概念を理解できるように、十分な情報を提供することです。

推奨されるガイドライン

  • 回答は初心者に優しいものであるべきです。
    • 具体的な、しかし場合によっては簡略化された例が非常に重要である。
    • 示された概念の応用は歓迎される
  • 有用なリソースを引用することは良いことです。
  • OS がページング機能をどのように使用するかについての小さな脱線は歓迎されます。
  • PAE と PSE の説明も歓迎します。
  • x86_64 への小さな脱線は大歓迎です。

関連する質問と、それらが重複していないと私が考える理由。

どのように解決するのですか?

この回答のバージョンで、TOC とより多くのコンテンツがあります。 .

私は報告されたエラーを修正します。もし、大きな修正や足りない部分の追加をしたいのであれば、自分自身の回答でそれを行い、正当な評価を得てください。マイナーな編集は直接マージすることができます。

サンプルコード

最小限の例です。 https://github.com/cirosantilli/x86-bare-metal-examples/blob/5c672f73884a487414b3e21bd9e579c67cd77621/paging.S

プログラミングの他のすべてのものと同様に、これを本当に理解する唯一の方法は、最小限の例で遊ぶことです。

これが "hard"なテーマなのは、自分で小さなOSを作る必要があるため、最小限の例が大きくなってしまうからです。

Intelのマニュアル

例を頭に入れないと理解できませんが、なるべく早くマニュアルに慣れるようにしましょう。

Intelはページングについて インテルマニュアル第3巻 システム・プログラミング・ガイド - 325384-056US September 2015 Chapter 4 "Paging"で説明されています。

特に興味深いのは、図4-4 "Formats of CR3 and Paging-Structure Entries with 32-Bit Paging"で、主要なデータ構造を示しています。

MMU

ページングは メモリ管理ユニット (MMU) という CPU の一部で行われます。他の多くと同様に(例えば x87コプロセッサ , APIC ) のように、これは初期には別のチップで、後にCPUに統合されました。しかし、この用語はまだ使用されています。

一般的な事実

論理アドレスは、quot;regular" ユーザー ランド コードで使用されるメモリ アドレスです (たとえば、quot;regular" の内容は rsimov eax, [rsi] ).

まずセグメンテーションでリニアアドレスに変換され、次にページングでリニアアドレスが物理アドレスに変換されます。

(logical) ------------------> (linear) ------------> (physical)
             segmentation                 paging

ほとんどの場合、物理アドレスは実際の RAM ハードウェア メモリ セルをインデックスするものと考えることができますが、これは以下の理由で 100% 真実ではありません。

ページングはプロテクトモードでのみ使用可能です。プロテクトモードでのページングの使用は任意です。ページングがオンになるのは PG のビットが cr0 レジスタが設定されます。

ページングとセグメンテーションの比較

ページングとセグメンテーションの大きな違いの1つは、以下の通りです。

  • ページングでは、RAM をページと呼ばれる同じ大きさのチャンクに分割します。
  • セグメンテーションは、メモリを任意の大きさのチャンクに分割します。

これはページングの主な利点で、同じサイズのチャンクは物事をより管理しやすくするからです。

ページングは非常に一般的になり、x86-64 では、新しいソフトウェアの主な動作モードである 64 ビット モードでは、IA32 をエミュレートする互換モードでのみ存在するセグメント化のサポートが廃止されました。

アプリケーション

ページングは、現代のOSでプロセスの仮想アドレス空間を実装するために使用されます。仮想アドレスを使用すると、OSは、その方法で単一のRAM上の2つ以上の同時プロセスに適合することができます。

  • 両方のプログラムは、他のプログラムについて何も知らなくてよい
  • 両プログラムのメモリは必要に応じて拡大・縮小できる
  • プログラム間の切り替えが非常に速い
  • あるプログラムが他のプロセスのメモリにアクセスすることはありません。

ページングは歴史的にはセグメンテーションの後に登場し、Linux などのモダン OS における仮想メモリの実装では、可変長のセグメントではなく、ページの固定サイズのメモリ チャンクの管理が容易であるため、大部分がこれに取って代わりました。

ハードウェアの実装

保護モードでのセグメンテーション (セグメント レジスタの変更が GDT または LDT からのロードをトリガーする) のように、ページング ハードウェアはその仕事をするためにメモリ内のデータ構造 (ページ テーブル、ページ ディレクトリ、その他) を使用します。

これらのデータ構造の形式は固定です。 ハードウェアによって しかし、RAM 上のデータ構造を正しくセットアップして管理するのは OS であり、ハードウェアにその場所を伝えるのは ( cr3 ).

他のいくつかのアーキテクチャでは、ページングをほぼ完全にソフトウェアの手に委ね、TLB が見つからない場合は OS が提供する関数を実行してページテーブルを走査し、新しいマッピングを TLB に挿入します。 これは、ページ テーブルの形式を OS が選択できるようにするものです。 x86 のように、ハードウェアがページ ウォークと他の命令のアウトオブオーダー実行をオーバーラップさせることはあり得ません。 .

例: 単純化されたシングル レベルのページング スキーム

これは、ページングが 簡略化された x86 アーキテクチャの簡略化されたバージョンで で、仮想メモリ空間を実装するためにページングがどのように動作するかの例です。

ページ テーブル

OSは次のようなページテーブルを与えることができる。

OSからプロセス1に与えられたページテーブル。

RAM location        physical address   present
-----------------   -----------------  --------
PT1 + 0       * L   0x00001            1
PT1 + 1       * L   0x00000            1
PT1 + 2       * L   0x00003            1
PT1 + 3       * L                      0
...                                    ...
PT1 + 0xFFFFF * L   0x00005            1

OSからプロセス2に与えられたページテーブル。

RAM location       physical address   present
-----------------  -----------------  --------
PT2 + 0       * L  0x0000A            1
PT2 + 1       * L  0x0000B            1
PT2 + 2       * L                     0
PT2 + 3       * L  0x00003            1
...                ...                ...
PT2 + 0xFFFFF * L  0x00004            1

どこで

  • PT1 そして PT2 : RAM上のテーブル1、2の初期位置。

    サンプル値です。 0x00000000 , 0x12345678 など。

    それらの値を決定するのはOSです。

  • L ページテーブルのエントリーの長さです。

  • present : は、ページがメモリ上に存在することを示します。

ページテーブルはRAM上に配置されます。それらは例えば次のように配置されます。

--------------> 0xFFFFFFFF


--------------> PT1 + 0xFFFFF * L
Page Table 1
--------------> PT1


--------------> PT2 + 0xFFFFF * L
Page Table 2
--------------> PT2

--------------> 0x0

両方のページテーブルのRAM上の初期位置は任意であり、OSによって制御されます。それらが重ならないようにするのは OS の責任です!

各プロセスはページテーブルに直接触れることはできませんが、OS に対してページテーブルを変更させる要求、例えばより大きなスタックやヒープセグメントを要求することは可能です。

1 ページは 4KB (12 ビット) の塊で、アドレスは 32 ビットなので、各ページを識別するのに必要なのは 20 ビット (20 + 12 = 32、したがって 16 進法では 5 文字) だけです。この値はハードウェアによって固定されています。

ページ テーブルのエントリ

ページテーブルとは...ページテーブルエントリーのテーブルです!

テーブル・エントリの正確なフォーマットは決まっています ハードウェアによって .

この単純化された例では、ページテーブルの項目は2つのフィールドだけを含んでいます。

bits   function
-----  -----------------------------------------
20     physical address of the start of the page
1      present flag

ということで、この例では、ハードウェア設計者は L = 21 .

実際のページテーブルのエントリのほとんどは、他のフィールドを持っています。

メモリはビットではなくバイトでアドレス指定できるため、21 ビットで整列するのは非現実的です。したがって、このケースで 21 ビットしか必要ない場合でも、ハードウェア設計者はおそらく L = 32 を選択してアクセスを高速化し、残りのビットは後で使用するために確保するだけでしょう。実際の L の実際の値は 32 ビットです。

単一レベルスキームでのアドレス変換

OSによってページテーブルが設定されると、リニアアドレスと物理アドレスの間のアドレス変換が行われる ハードウェアによる .

OS はプロセス 1 をアクティブにしたい場合、プロセス 1 のための cr3PT1 というように、プロセス1のテーブルの先頭を指定します。

プロセス1がリニアアドレスにアクセスしたい場合 0x00000001 にアクセスしたい場合、ページング ハードウェア 回路は、OS のために自動的に次のことを行います。

  • リニアアドレスを 2 つの部分に分割します。

    | page (20 bits) | offset (12 bits) |
    
    

    ということで、この場合、私たちは

    • ページ = 0x00000
    • オフセット = 0x001
  • ページ表 1 を見てください。 cr3 がそれを指しているからです。

  • ルックエントリー 0x00000 というのは、これがページ部分だからです。

    ハードウェアは、このエントリーがRAMアドレス PT1 + 0 * L = PT1 .

  • が存在するため、アクセスは有効です。

  • ページテーブルによって、ページ番号の場所 0x00000 にあります。 0x00001 * 4K = 0x00001000 .

  • を使用して、最終的な物理アドレスを見つけるには、オフセットを追加するだけです。

      00001 000
    + 00000 001
      -----------
      00001 001
    
    

    なぜなら 00001 はテーブルで調べられたページの物理アドレスであり 001 はオフセットです。

    その名前が示すように、オフセットは常にページの物理的なアドレスを単純に追加したものです。

  • を指定すると、ハードウェアはその物理的な位置でメモリを取得します。

同じように、プロセス1では次のような変換が行われます。

linear     physical
---------  ---------
00000 002  00001 002
00000 003  00001 003
00000 FFF  00001 FFF
00001 000  00000 000
00001 001  00000 001
00001 FFF  00000 FFF
00002 000  00002 000
FFFFF 000  00005 000

例えば、アドレスにアクセスする場合 00001000 にアクセスした場合、ページ部分は 00001 の場合、ハードウェアはそのページテーブルエントリーが RAM アドレスにあることを知っています。 PT1 + 1 * L ( 1 というのはページ部分だからです)、そこで探します。

OSがプロセス2に切り替えたいとき、必要なのは cr3 をページ 2 を指すようにするだけです。とても簡単です!

さて、プロセス2については、以下のような翻訳が行われます。

linear     physical
---------  ---------
00000 002  00001 002
00000 003  00001 003
00000 FFF  00001 FFF
00001 000  00000 000
00001 001  00000 001
00001 FFF  00000 FFF
00003 000  00003 000
FFFFF 000  00004 000

同じリニアアドレスが、プロセスごとに異なる物理アドレスに変換される の中の値のみによって、異なる物理アドレスに変換されます。 cr3 .

このようにして、すべてのプログラムは、そのデータが次の場所から始まることを期待できます。 0 で始まり FFFFFFFF のように、正確な物理的アドレスを気にすることなく使用できます。

ページフォルト

プロセス1が、存在しないページ内のアドレスにアクセスしようとした場合はどうなりますか?

ハードウェアは Page Fault Exception を介してソフトウェアに通知します。

その後、何をしなければならないかを決めるために例外ハンドラを登録するのは、通常 OS に任されています。

テーブル上にないページにアクセスすることは、プログラミングエラーである可能性があります。

int is[1];
is[2] = 1;

を使用することができますが、Linuxの場合など、許容される場合もあります。

  • プログラムがスタックを増やしたい。

    それはただ、与えられた可能な範囲の特定のバイトにアクセスしようとし、OSが満足すれば、そのページをプロセスのアドレス空間に追加します。

  • は、ページがディスクにスワップされたことを示します。

    OS は、ページを RAM に戻すために、プロセスの背後で何らかの作業を行う必要があります。

    現在フラグがクリアされている場合、ページテーブルエントリの他のエントリは完全にOSが望むものに任されているので、OSはページテーブルエントリの残りの内容に基づいてこれがそうであることを発見することができます。

    たとえば Linux では、present = 0 のとき。

    • の場合、ページテーブルエントリのすべてのフィールドが 0 であれば、無効なアドレスです。

    • でなければ、ページはディスクにスワップされ、それらのフィールドの実際の値は、ディスク上のページの位置をエンコードします。

いずれにせよ、OS はどのアドレスでページフォルトが発生したかを知っていなければ、問題に対処することはできません。このため、親切な IA32 開発者は、ページフォルトを発生させるアドレスに cr2 の値を、ページフォルトが発生するたびにそのアドレスに設定するのです。例外ハンドラは cr2 を調べてそのアドレスを取得することができます。

簡略化

この例を理解しやすくするために、現実を単純化したものです。

  • 実際のページング回路では、省スペースのために多段ページングを使用しますが、今回はシンプルな1段ページング方式を示しました。

  • ページ テーブルは、20 ビットのアドレスと 1 ビットの存在フラグという 2 つのフィールドのみを含んでいました。

    実際のページ テーブルは合計 12 のフィールドを含んでおり、したがって、省略されている他の機能もあります。

例:マルチレベルのページングスキーム

シングルレベルのページングスキームの問題は、4G / 4K = 1M エントリと、あまりにも多くの RAM を消費してしまうことです。 につき プロセスあたり 1M エントリです。各エントリが 4 バイトの長さである場合、4M 個の プロセスあたり となり、デスクトップ コンピュータでも多すぎます。 ps -A | wc -l によると、私は今 244 のプロセスを実行しているので、RAM の約 1GB を消費することになります!

このため、x86 の開発者は RAM の使用量を減らすためにマルチレベルのスキームを使用することにしました。

この方式の欠点は、アクセス時間が若干長くなることです。

PAEのない32ビットプロセッサで使われる単純な3レベルページング方式では、32のアドレスビットは次のように分割されます。

| directory (10 bits) | table (10 bits) | offset (12 bits) |

各プロセスには、1つだけのページディレクトリが関連付けられていなければならないので、少なくとも 2^10 = 1K ページディレクトリのエントリが含まれ、単一レベルのスキームで必要とされる最小の 1M よりもはるかに優れています。

ページ テーブルは OS が必要とするときだけ割り当てられます。各ページテーブルは 2^10 = 1K ページディレクトリエントリ

ページディレクトリは...ページディレクトリエントリを含んでいます! ページディレクトリエントリは、以下の点を除いて、ページテーブルエントリと同じです。 テーブルの物理アドレスではなく、ページ テーブルの RAM アドレスを指していること . これらのアドレスはわずか 20 ビット幅なので、ページ テーブルは 4KB ページの先頭になければなりません。

cr3 は、ページテーブルの代わりに、現在のプロセスのページディレクトリの RAM 上の位置を指すようになりました。

ページテーブルのエントリは、単一レベルのスキームから全く変更されません。

ページテーブルが単一レベルのスキームから変化するのは

  • 各プロセスは最大 1K ページ テーブルを持つことができ、ページ ディレクトリ エントリごとに 1 つあります。
  • 各ページテーブルは、1M エントリではなく、正確に 1K エントリを含みます。

最初の 2 レベルに 10 ビットを使用する理由は、(そして、例えば 12 | 8 | 12 などではなく)、各ページテーブルエントリが 4 バイト長であるためです。そうすると、ページディレクトリとページテーブルの 2^10 個のエントリは、4Kb ページにうまく収まることになります。これは、その目的のためにページを割り当てたり解放したりするのがより速く、より簡単であることを意味します。

マルチレベル スキームでのアドレス変換

OSからプロセス1に与えられたページディレクトリ。

RAM location     physical address   present
---------------  -----------------  --------
PD1 + 0     * L  0x10000            1
PD1 + 1     * L                     0
PD1 + 2     * L  0x80000            1
PD1 + 3     * L                     0
...                                 ...
PD1 + 0x3FF * L                     0

でOSからプロセス1に与えられたページテーブル。 PT1 = 0x10000000 ( 0x10000 * 4K):

RAM location      physical address   present
---------------   -----------------  --------
PT1 + 0     * L   0x00001            1
PT1 + 1     * L                      0
PT1 + 2     * L   0x0000D            1
...                                  ...
PT1 + 0x3FF * L   0x00005            1

でOSからプロセス1に与えられたページテーブル。 PT2 = 0x80000000 ( 0x80000 * 4K):

RAM location      physical address   present
---------------   -----------------  --------
PT2 + 0     * L   0x0000A            1
PT2 + 1     * L   0x0000C            1
PT2 + 2     * L                      0
...                                  ...
PT2 + 0x3FF * L   0x00003            1

のところ。

  • PD1 : RAM 上のプロセス 1 のページディレクトリの初期位置。
  • PT1 そして PT2 : RAM 上のプロセス 1 のページテーブル 1 とページテーブル 2 の初期位置です。

この例では、ページディレクトリとページテーブルはRAMに次のように格納される可能性があります。

----------------> 0xFFFFFFFF


----------------> PT2 + 0x3FF * L
Page Table 1
----------------> PT2

----------------> PD1 + 0x3FF * L
Page Directory 1
----------------> PD1


----------------> PT1 + 0x3FF * L
Page Table 2
----------------> PT1

----------------> 0x0

リニアアドレスを翻訳してみましょう 0x00801004 を一歩一歩訳していきましょう。

私たちは、次のように考えます。 cr3 = PD1 は、つまり、先ほど説明したページディレクトリを指しています。

バイナリでは、リニアアドレスは

0    0    8    0    1    0    0    4
0000 0000 1000 0000 0001 0000 0000 0100

としてグループ化 10 | 10 | 12 を与えます。

0000000010 0000000001 000000000100
0x2        0x1        0x4

を与える。

  • ページ ディレクトリ エントリ = 0x2
  • ページテーブルエントリ = 0x1
  • オフセット = 0x4

つまり、ハードウェアはページディレクトリのエントリ 2 を探します。

ページディレクトリのテーブルによると、ページテーブルが 0x80000 * 4K = 0x80000000 . これはプロセスの最初の RAM アクセスです。

ページテーブルのエントリは 0x1 であるため、ハードウェアはページテーブルのエントリ 1 を 0x80000000 にあり、物理ページがアドレス 0x0000C * 4K = 0x0000C000 . これは、このプロセスの2回目のRAMアクセスです。

最後に、ページングハードウェアがオフセットを追加し、最終的なアドレスは 0x0000C004 .

その他、翻訳されたアドレスの例として

linear    10 10 12 split   physical
--------  ---------------  ----------
00000001  000 000 001      00001001
00001001  000 001 001      page fault
003FF001  000 3FF 001      00005001
00400000  001 000 000      page fault
00800001  002 000 001      0000A001
00801008  002 001 008      0000C008
00802008  002 002 008      page fault
00B00001  003 000 000      page fault

ページディレクトリまたはページテーブルエントリのいずれかが存在しない場合、ページフォルトが発生します。

OS が別のプロセスを同時に実行したい場合、2 番目のプロセスに別のページディレクトリを与え、そのディレクトリを別のページテーブルにリンクします。

64 ビット アーキテクチャ

64 ビットは現在の RAM サイズではまだ多すぎるアドレスなので、ほとんどのアーキテクチャではより少ないビットを使用することになります。

x86_64 は 48 ビット (256 TiB) を使用し、レガシー モードの PAE はすでに 52 ビット (4 PiB) のアドレスを許可しています。

これらの 48 ビットのうち 12 ビットはすでにオフセット用に予約されており、36 ビットが残ります。

2 レベルのアプローチを取る場合、最適な分割は 2 つの 18 ビット レベルになります。

しかし、その場合、ページディレクトリには 2^18 = 256K のエントリーを持つことになり、これはあまりに多くの RAM を消費します: 32 ビット アーキテクチャのシングルレベルのページングに近いものです!

そのため、64 ビット アーキテクチャでは、さらにページ レベルを増やし、通常は 3 または 4 にします。

x86_64 では 4 レベルを 9 | 9 | 9 | 12 スキームで、上位レベルは単に 2^9 より上位のエントリのみを取り込むようにします。

PAE

物理アドレスの拡張。

32 ビットでは、4GB の RAM しかアドレス指定できません。

これは大規模なサーバーでは制限になり始めたので、Intel は Pentium Pro に PAE メカニズムを導入しました。

この問題を解消するために、Intelは新たに4本のアドレスラインを追加し、64GBのアドレスを取得できるようにしました。

PAE がオンである場合、ページ テーブル構造も変更されます。変更される正確な方法は、PSE がオンであるかオフであるかに依存します。

PAE のオン/オフは PAE のビット cr4 .

たとえアドレス指定可能なメモリの合計が 64GB であっても、個々のプロセスは 4GB までしか使用することができません。しかし、OS は異なるプロセスを異なる 4GB のチャンクに配置することができます。

PSE

ページサイズの拡張。

ページの長さを 4K から 4M ( PAE がオンの場合は 2M ) にすることを可能にします。

PSE のオン/オフは PAE のビット cr4 .

PAEとPSEのページテーブルスキーム

PAE と PSE のいずれかがアクティブな場合、異なるページングレベルのスキームが使用されます。

  • PAE および PSE を使用しない。 10 | 10 | 12

  • PAEとPSEはありません。 10 | 22 .

    22は4Mbページ内のオフセットで、22ビットが4Mbをアドレスしているからです。

  • PAEとPSEはありません。 2 | 9 | 9 | 12

    9 が 10 の代わりに 2 回使用される設計上の理由は、エントリが 32 ビットに収まりきらなくなったためです。この 32 ビットは、20 のアドレス ビットと 12 の意味のあるまたは予約されたフラグ ビットによってすべて埋め尽くされていました。

    その理由は、20 ビットはページ テーブルのアドレスを表すにはもう十分ではないからです。プロセッサに 4 本の余分な配線が追加されたため、現在は 24 ビットが必要です。

    そのため、設計者はエントリサイズを 64 ビットに増やすことを決定し、それらを 1 つのページ テーブルに収めるには、エントリ数を 2^10 から 2^9 に減らす必要があります。

    開始の 2 は、PDPT (Page Directory Pointer Table) と呼ばれる新しいページ レベルで、その理由は ポイント を指し、32 ビットのリニア アドレスを埋めます。PDPT はまた、64 ビット幅です。

    cr3 は、アドレス指定効率を高めるために、4 つの 4GB メモリと 32 ビットの倍数で整列される必要がある PDPT を指すようになりました。これは、現在 cr3 は 20 ビットではなく 27 ビットの有効ビットを持つことになります。

  • PAE と PSE。 2 | 9 | 21

    デザイナーは、1 ページに収まるように 9 ビットの幅のフィールドを維持することにしました。

    これは 23 ビットのままです。PSE のない PAE のケースで統一するために、PDPT 用に 2 を残して、オフセット用に 21 を残しています。

TLB

TLB (Translation Lookahead Buffer) は、ページングアドレスのキャッシュです。

キャッシュであるため、連想レベルなどCPUキャッシュの設計上の問題点を多く共有しています。

このセクションでは、4 つの単一アドレス エントリを持つ単純化された完全連想型 TLB を説明します。他のキャッシュと同様に、実際の TLB は通常、完全な連想型ではないことに注意してください。

基本的な操作

リニアアドレスと物理アドレスの変換が行われた後、TLBに格納されます。例えば、4エントリのTLBは以下の状態で開始されます。

  valid   linear   physical
  ------  -------  ---------
> 0       00000    00000
  0       00000    00000
  0       00000    00000
  0       00000    00000

> は置換される現在のエントリを示します。

と、ページリニアアドレスの後に 00003 は物理アドレスに変換され 00005 になると、TLBは

  valid   linear   physical
  ------  -------  ---------
  1       00003    00005
> 0       00000    00000
  0       00000    00000
  0       00000    00000

を2回翻訳した後 0000700009 となる。

  valid   linear   physical
  ------  -------  ---------
  1       00003    00005
  1       00007    00009
> 0       00000    00000
  0       00000    00000

では、もし 00003 が再び翻訳される必要がある場合、ハードウェアはまず TLB を検索し、1 回の RAM アクセスでそのアドレスを見つけます。 00003 --> 00005 .

もちろん 00000 を含む有効なエントリはないため、TLB上にはありません。 00000 をキーとして含む有効なエントリはないため、TLB上にはありません。

置き換えの方針

TLB が一杯になると、古いアドレスが上書きされます。CPU キャッシュの場合と同様に、置換ポリシーは潜在的に複雑な操作ですが、シンプルで合理的なヒューリスティックは、最も最近使用されたエントリ (LRU) を削除することです。

LRU を使用して、状態から開始します。

  valid   linear   physical
  ------  -------  ---------
> 1       00003    00005
  1       00007    00009
  1       00009    00001
  1       0000B    00003

追加 0000D -> 0000A は与えるだろう。

  valid   linear   physical
  ------  -------  ---------
  1       0000D    0000A
> 1       00007    00009
  1       00009    00001
  1       0000B    00003

CAM

TLBを使用すると、最初の翻訳が1回のアクセスで済むため、翻訳が高速になります。 TLB レベルあたり これは、単純な 32 ビット スキームでは 2 回ですが、64 ビット アーキテクチャでは 3 回または 4 回になります。

TLB は通常、CAM (Content-addressable Memory) と呼ばれる高価なタイプの RAM として実装されます。CAM はハードウェア上で連想マップを実装します。つまり、キー (線形アドレス) を与えると値を取得する構造体です。

マッピングは RAM アドレスに実装することもできますが、CAM マッピングは RAM マッピングよりもはるかに少ないエントリしか必要としないかもしれません。

たとえば、以下のようなマップがあります。

  • キーと値の両方が20ビット(単純なページングスキームの場合)
  • 毎回、最大4つの値を保存する必要があります。

は4つのエントリを持つTLBに格納される可能性があります。

linear   physical
-------  ---------
00000    00001
00001    00010
00010    00011
FFFFF    00000

しかし、これをRAMで実装するには 2^20 のアドレスを持つ必要があります。 :

linear   physical
-------  ---------
00000    00001
00001    00010
00010    00011
... (from 00011 to FFFFE)
FFFFF    00000

というように、TLBを使用するよりもさらに高価になります。

エントリーの無効化

いつ cr3 が変更されると、すべての TLB エントリが無効になります。新しいプロセス用の新しいページ テーブルが使用されるため、古いエントリが何らかの意味を持つことはほとんどないからです。

また、x86 では invlpg 命令もあり、これは明示的に 1 つの TLB エントリを無効にします。他のアーキテクチャでは、与えられた範囲上のすべてのエントリを無効にするなど、無効化された TLB エントリに対してさらに多くの命令が提供されています。

x86 CPU の中には、x86 仕様の要件を超え、それが保証する以上のコヒーレンスを提供するものがあります。 ページ テーブル エントリを変更してからそれを使用するまでの間に、それがまだ TLB にキャッシュされていなかった場合 . Windows 9x では、正確さを保つためにこれに依存していたようですが、最近の AMD CPU はコヒーレント ページウォークを提供しません。 Intel の CPU は、そのためにミススペキュレーションを検出する必要があるにもかかわらず、それを行っています。 おそらく得るものはあまりなく、デバッグが困難な微妙なタイミングに依存する問題を引き起こす大きなリスクがあるため、これを利用するのはおそらく悪い考えです。

Linux カーネルの使用法

Linuxカーネルは、小さなデータの断片化で高速なプロセス切り替えを可能にするために、x86のページング機能を広範に利用しています。

v4.2 の下を見てください。 arch/x86/ :

  • include/asm/pgtable*
  • include/asm/page*
  • mm/pgtable*
  • mm/page*

ページを表す構造体は定義されていないようで、マクロのみです。 include/asm/page_types.h は特に興味深いです。抜粋

#define _PAGE_BIT_PRESENT   0   /* is present */
#define _PAGE_BIT_RW        1   /* writeable */
#define _PAGE_BIT_USER      2   /* userspace addressable */
#define _PAGE_BIT_PWT       3   /* page write through */

arch/x86/include/uapi/asm/processor-flags.h を定義します。 CR0 を定義しており、特に PG のビット位置が重要です。

#define X86_CR0_PG_BIT      31 /* Paging */

書誌情報

無料です。

  • ラトガース-PXK-416 章 "メモリ管理:講義ノート"。

    古い OS で使用されたメモリ構成技術の歴史的なレビューです。

非フリー。

  • bovet05 章 "メモリアドレッシング"。

    x86 メモリアドレッシングの妥当な入門書です。いくつかの良い例と簡単な例がありません。