- メモリ空間をページと呼ばれる単位に分割
- 1つのページは4KiB
- ソフトウェアが指定する論理アドレス(logical address)を、物理メモリアドレス(物理アドレス)にマッピングする機能をページングと呼ぶ
- 正確には、論理アドレス→(セグメンテーション)→リニアアドレス→(ページング)→物理アドレス
- 64bitモードではセグメンテーションによるアドレス変換が行われない
- 正確には、論理アドレス→(セグメンテーション)→リニアアドレス→(ページング)→物理アドレス
- 論理アドレスを物理アドレスに変換するための変換表をページテーブルという
- これはページエントリの配列のようなものになっている
- ページエントリは8byteで、
[62:52]および[11:9]はOSが自由に使える領域、63bitと[8:0]がフラグ、それ以外のビット[51:12]でアドレスを示すページエントリの値 & 0x000_ff'ffff'ffff_000が変換後の物理アドレスになるようにする
- ページエントリは8byteで、
- これはページエントリの配列のようなものになっている
- ページテーブルを複数の層に分けることで、メモリ消費を抑えたりすることが出来る?
- この場合、「ページテーブル」は最下層のことを指す
- x86_64ではページテーブルが4層ある
- 呼び方は上から順に
- ページマップレベル4テーブル(PML4 Table)
- ページディレクトリポインタテーブル(PDP Table)
- ページディレクトリ
- ページテーブル
- Ice Lake以降のCPUは5層テーブルも可能らしい……
- 呼び方は上から順に
- 各テーブルに512個のエントリが存在する
- 512個 * 8byte = 4096だから、1つのページにちょうど1つのページテーブルが収まる
- 仮想アドレス(64bitモードでは当然64bit幅)は6つの区間に分けられており、上位から順に
Sign Extention(16bit) ,Level[4-1] Index(9bit * 4),Offset(12bit) となっている- Sign Extentionの全bitはLevel 4 Index区間の最上位と等しくなければならない
- 異なっていたらCPU例外
- Sign Extentionの全bitはLevel 4 Index区間の最上位と等しくなければならない
ページエントリのフラグ
| bit | name | desc | |
|---|---|---|---|
| 0 | present | ページがメモリ内に存在する | |
| 1 | writable | 書込み可能なページ | |
| 2 | user accessible | ユーザー権限でアクセス可能なページ | |
| 3 | write through caching | メモリに直接書き込みを行う | |
| 4 | disable cache | ページをキャッシュしない | |
| 5 | accessed | このページが使用中である時、CPUが1に設定する | |
| 6 | dirty | このページに書き込みが行われた時、CPUが1に設定する | |
| 7 | huge page / null | 後述 | |
| 8 | global | アドレス空間変更時、キャッシュ上のこのページが初期化されない | |
| 63 | no execute | このページでのプログラム実行を禁止する |
- huge page / nullは、そのページエントリがどのレベルのページテーブルに属するかによって挙動が変わる
- Level 1 / 4では0である必要がある
- Level 2で1であった場合、2MiBのページを作成する
- Level 3で1であった場合、1GiBのページを作成する
- global:CR4レジスタのPGEが1である必要がある
- no execute:EFERレジスタのNXEが1である必要がある
動作例
以下のアセンブリを実行する際におけるCPUのきもちを考える
mov rax, 1
mov rbx. 0x80'8060'4005
mov [rbx], rax- まずraxに1を代入
- 次にrbxに0x0080’8060’4005を代入する
- 64bit表現:0000’0000’0000’0000_0’0000’0001_0’0000’0010_0’0000’0011_0’0000’0100_0000’0000’0101
- この例では、Level 4 Indexは1・Level 3 Indexは2・Level 2 Indexは3・Level 1 Indexは4・Offsetは5となっている
- 次のmovで、raxの値をrbxが指す場所に代入する
- 今回は
*0x0080'8060'4005 = 1という意味
- 今回は
- CPUは0x0c02’0034に直接代入するのではなく、このアドレスを仮想メモリアドレスと解釈して、物理アドレスに変換してから代入を行う
- まずCR3(CPUのレジスタ)の値を見る 例えばここが0x1234だった場合、物理アドレス 0x1234にLevel 4のページテーブルが置かれているという意味になる
- Level 4 Indexは1だから、
0x1234[1]にLevel 3のページテーブルがある - こういうふうにどんどん辿っていって、
0x1234[1][2][3][4]がページエントリとなる
- 例えばこの
0x1234[1][2][3][4]が 0x000_00’5678’0000_000 だった場合、仮想アドレス 0x0080’8060’4000 == 物理アドレス 0x5678_0000 と変換できるということになる - あとはこれにOffsetを加算(+5)すれば良いだけ
- 仮想アドレス 0x0080’8060’4005 == 物理アドレス 0x5678_0005ということがわかりました