1. はじめに
Linux や x86-64 の説明を読んでいると、次の言葉が何度も出てきます。
- セグメント
- リニアアドレス
- ユーザー空間 / カーネル空間
-
ring 0/ring 3 MMU (Memory Management Unit)
単語ごとの説明は見つかっても、実際のメモリ保護がどの順番で成り立っているかはつながりにくいです。特に混乱しやすいのは、次の点です。
- セグメントは今も効いているのか
- リニアアドレスと仮想アドレスは同じなのか
- 各プロセスは user 空間と kernel 空間をどう持っているのか
-
CPU (Central Processing Unit)の特権とページ保護はどこで組み合わさるのか
この記事では、x86-64 Linux のメモリ保護を次の順番で整理します。
- 最初に
CPU特権を最小限だけ押さえる - セグメントとリニアアドレスをつなぐ
- page table と
PTE (Page Table Entry)で物理アドレスへの変換を見る - user / kernel 仮想空間とアクセス保護をまとめる
2. 前提となる知識
2.1. CPU の権限
x86 には ring 0 から ring 3 までの特権レベルがあります。現代の Linux で主に使うのは次の 2 つです。
| ring | 主な用途 | 典型例 |
|---|---|---|
ring 0 |
kernel | カーネル本体、デバイス制御、例外処理 |
ring 3 |
user | 通常のアプリケーション |
現在の権限は、ざっくり CPL (Current Privilege Level) として捉えると分かりやすいです。
この段階では、次の 2 点だけ押さえれば十分です。
| ポイント | 意味 |
|---|---|
ふつうの call では権限は変わらない |
CPL=3 のまま kernel page へ飛ぼうとしても入れない |
後で PTE と照合される |
User/Supervisor などの page 属性は、現在の CPL と組み合わさって効く |
つまり CPU 特権は、記事の最後に出てくる追加要素ではなく、page 保護を読むための前提です。以降はこの前提を持ったままアドレス変換の流れを見ていきます。
2.2. 用語の説明
ここでは、この記事で何度も出てくる用語が、それぞれ何を表しているか記載します。
| 用語 | この記事での役割 | 後で効いてくる場所 |
|---|---|---|
CPU 特権 |
いま user なのか kernel なのかを表す |
User/Supervisor 判定 |
| セグメント | x86 の歴史的な前段で、論理アドレスをリニアアドレスへつなぐ |
FS/GS を除きほぼ flat |
| リニアアドレス | paging に入力されるアドレス | page walk と TLB
|
PTE |
4 KiB page を物理 page と属性に対応付ける |
Present, R/W, XD
|
| user / kernel 仮想空間 | どの範囲を誰が主に使うか | lower / upper canonical |
結論だけ先に言うと、**x86-64 Linux のメモリ保護は「ほぼ flat な segmentation の上で、page table の属性と CPU 特権を照合してアクセスを許可 / 拒否する仕組み」**です。
3. 全体像
最初に、メモリアクセス時の大きな流れを 1 枚で見ます。
この図の見どころは次の通りです。
| 段階 | ここで見るもの |
|---|---|
| segmentation |
CS (Code Segment) / DS (Data Segment) / SS (Stack Segment) / FS / GS と offset |
MMU |
TLB、page table、PTE 属性 |
| permission check |
PTE の属性と現在の CPL、access type |
現代の x86-64 Linux では、実際の保護の主役は MMU 側の page 保護です。以降は、その前段にある segmentation と、その後段にある page table を順にほどきます。
4. セグメント
セグメントは、もともと 16 bit のレジスタしかない時代に、20 bit のアドレス空間へアクセスするための仕組みでした。
特に 8086 系では、CPU は segment:offset という形でアドレスを扱い、segment * 16 + offset で物理アドレスを作ります。つまり segmentation は、保護機構である前に、狭いレジスタ幅でより広いメモリ空間を扱うためのアドレス指定方式でした。
x86 の基本形を式で書くと、次のようになります。
logical address = segment selector : offset
linear address = segment base + offset
physical address = paging(linear address)
セグメントは、単なる名前ではなく次の情報を持ちます。
| セグメントが持つもの | 役割 |
|---|---|
| base | offset をどこから数えるか |
| limit | どこまで有効か |
| type | code/data などの種別 |
DPL (Descriptor Privilege Level) など |
CPU の特権レベルのうち、どの権限で使えるか |
4.1. 現代での位置づけ
ここでは、386 protected mode と x86-64 Linux を並べて、セグメントの役割がどう変わったかを見ます。
ここでの CS、DS、ES (Extra Segment)、SS は、昔ながらの代表的な segment register です。
| 観点 | 386 protected mode | x86-64 Linux |
|---|---|---|
CS/DS/ES/SS base |
実際に効く | 通常は実質 0 扱い |
| segment limit | 保護に使う | 通常アクセスでは主役でない |
| 保護の主役 | segmentation と paging の併用 | paging が主役 |
FS/GS |
追加セグメント | 例外的に base が今も有効 |
つまり、x86-64 Linux では segmentation が消えたわけではないが、普通のコード / データ保護はほぼ paging で説明できるということです。
4.2. 基本動作
ここでは、CPU が DS を基準に、データセグメント先頭から offset 0x00001234 の位置にあるデータへアクセスしようとする例を考えます。
この例では、DS が指す descriptor に次の情報が入っているとします。
| 項目 | 値 |
|---|---|
| segment register | DS |
| segment base | 0x00400000 |
| offset | 0x00001234 |
| linear address | 0x00401234 |
計算は単純で、linear address = segment base + offset です。
0x00400000 + 0x00001234 = 0x00401234
この時点では、まだ physical address は決まっていません。paging が無効ならそのまま physical address になり、paging が有効ならさらに page table をたどります。
| paging の状態 | どうなるか |
|---|---|
| 無効 | linear address = physical address |
| 有効 | linear address -> page table -> physical address |
4.3. flat model の見え方
ここでは flat model だとセグメントがどう見えるかを整理します。開始アドレスが同じ (0x00000000) になるため、CS と DS の base はどちらも同じになります。
flat model では CS と DS の base がどちらも同じので、同じ offset に対して
CS:offsetDS:offset
は同じリニアアドレスになります。
| 項目 | CS:0x00001234 |
DS:0x00001234 |
|---|---|---|
| segment base | 0x00000000 |
0x00000000 |
| linear address | 0x00001234 |
0x00001234 |
その意味で、リニアアドレス自体には「これはコード」「これはデータ」という区別はありません。ただし区別が消えるわけではありません。
| 何で区別するか | いつ区別するか |
|---|---|
CS か DS か、descriptor の種別 |
セグメントを使う段階 |
| 命令 fetch か read/write か、ページ属性はどうか | 実際のアクセス時 |
注: リニアアドレスになった時点では、アドレス値そのものにコード / データの区別はありません。
4.4. flat model 時に使用するセグメント関連のレジスタについて
これまで見たように、flat model 時には CS、DS、ES、SS の base によるアドレス変換はほぼ使用しなくなります。
しかし、継続して使用するレジスタもあります。
ここでは、flat model 時でも用途を持つ FS と GS を見ます。
FS と GS は、x86-64 でも例外的に base がアドレス計算へ残るセグメントです。
| レジスタ | 現代 Linux/x86-64 での典型用途 |
|---|---|
FS |
user space の TLS (Thread Local Storage)
|
GS |
kernel 側の per-CPU data など |
つまり FS/GS は、昔ながらの segmentation 全体を復活させているのではなく、限定的な base register として生き残っていると見るのが近いです。
5. ページング
ここでは、segmentation の後に現れるリニアアドレスが、page table を通って物理アドレスへどうつながるかを見ます。
x86 の用語では、segmentation の後、paging の前にあるアドレスがリニアアドレスです。
| 段階 | 何を表すか |
|---|---|
| logical address |
segment + offset の論理的な見え方 |
| linear address | segmentation 後の paging 入力 |
| physical address | 実メモリ上の位置 |
ただし x86-64 Linux では segmentation がほぼ flat なので、実務上は virtual address と linear address をほぼ同じものとして考えて差し支えありません。
5.1. 全体像
リニアアドレスから物理アドレスへの変換は、概念的には次の流れです。
この図での要点は次の通りです。
| ポイント | 意味 |
|---|---|
TLB hit |
すでに変換結果が cache されていれば page walk は不要 |
TLB miss |
CR3 を起点に PML4 -> PDPT -> PD -> PT をたどる |
| permission check |
PTE の属性と現在の CPL、access type を照合する |
ここでいう TLB (Translation Lookaside Buffer) は、最近使ったリニアアドレス変換結果を CPU 内部に cache しておく仕組みです。
TLB が持つもの |
役割 |
|---|---|
| リニアアドレス上の page 番号 | どの page の変換かを識別する |
| 対応する physical page frame | 物理メモリのどこへ行くかを示す |
| ページ属性の要約 |
User/Supervisor、R/W、実行可否などの判定に使う |
このため TLB hit したときは、物理アドレスへの変換だけでなく、そのアクセスが許されるかの判定に必要な page 側情報も、その場で使えると考えてよいです。
ただし、TLB は正本ではありません。
| どこにあるか | 役割 |
|---|---|
| page table | 正本。CR3 を起点にメモリ上に置かれている |
TLB |
その一部を CPU 内部に cache した高速コピー |
つまり、「変換結果と page 側の保護情報が一緒になった表」は確かにあります。ただしそれは
- 正本としては page table entry
- 高速化のための cache としては TLB entry
の 2 つの形で存在すると見るのが正確です。
5.2. 代表例
ここでは代表例として、最も基本的な 4 KiB page を使う場合を見ます。
page table は、リニアアドレスを table をたどるための index と、最終 page の中の位置に分けて解釈します。
4 KiB page は、仮想メモリと物理メモリを対応付けるときの最小単位の 1 つです。4 KiB は 4096 byte なので、CPU はある linear address を「どの 4 KiB page に属するか」と「その page の中の何 byte 目か」に分けて扱います。
| 見るもの | 意味 |
|---|---|
4 KiB page |
4096 byte 単位の page |
| page offset | その 4 KiB page の中の何 byte 目か |
PTE (Page Table Entry) |
その 4 KiB page をどの物理 page に対応付け、どんな属性で保護するか |
典型的な 4 KiB page では、リニアアドレスは次のように分解されます。
| 項目 | bit 範囲 | 役割 |
|---|---|---|
| canonical address | 63:48 |
canonical address のための符号拡張領域 |
PML4 index |
47:39 |
最上位 page table を引くための index |
PDPT index |
38:30 |
次段の table を引くための index |
PD index |
29:21 |
次段の table を引くための index |
PT index |
20:12 |
最終 table から、その 4 KiB page に対応する PTE を引くための index |
| page offset | 11:0 |
その 4 KiB page の中の位置 |
各段の index が 9 bit なのは、各 table が 512 entry を持つからです。
4 KiB page と PTE の関係を図にすると次のようになります。
ここで重要なのは、リニアアドレスの bit 列と、PTE が持つ情報は別だという点です。4 KiB page の場合、PT index で選ばれる最終 entry が PTE であり、その PTE が「この 4 KiB page はどの物理 page に対応するか」「どの属性で保護するか」を持ちます。
PTE が持つもの |
役割 |
|---|---|
| physical page frame | 実際に対応付く物理 4 KiB page の位置 |
Present |
その page が有効か |
User/Supervisor |
user から触れてよいか |
R/W |
書き込み可能か |
XD |
実行禁止か |
つまり 4 KiB page では、リニアアドレスの上位 bit で PTE を選び、PTE が指す physical page frame に page offset を足して physical address を作る、という流れです。
注: この段階では physical address を作るだけでなく、
PTEが持つUser/Supervisor、R/W、XDなどの属性と、現在のCPU特権レベルやアクセス種別を照合して、アクセス可否のチェックも行われます。
5.3. アドレス空間の制約
ここでは、canonical address があるために 64 bit 全域がそのまま仮想アドレスになるわけではない点を見ます。
RIP (Instruction Pointer) やポインタは 64 bit ですが、4-level paging では すべての 64 bit 値が有効な仮想アドレスではありません。有効なのは canonical address です。
bit 47 |
63:48 に要求される値 |
意味 |
|---|---|---|
0 |
すべて 0
|
lower canonical |
1 |
すべて 1
|
upper canonical |
このため、4-level paging で有効な仮想アドレス空間は合計 2^48 = 256 TiB です。
| 領域 | 範囲 | 主な用途 |
|---|---|---|
| lower canonical |
0x0000000000000000 - 0x00007fffffffffff
|
主に user |
| non-canonical |
0x0000800000000000 - 0xffff7fffffffffff
|
無効 |
| upper canonical |
0xffff800000000000 - 0xffffffffffffffff
|
主に kernel |
したがって、512 * 512 * 512 * 512 * 4096 という計算は正しいですが、その意味は **「4-level paging が表現できる canonical virtual address space の総量」**です。
6. 仮想メモリ空間
ここでは、user 空間と kernel 空間が仮想アドレス上でどう配置され、どこまで共有されるかを見ます。
Linux/x86-64 では、概念的には次のように考えると分かりやすいです。
lower canonical -> 主に user space
upper canonical -> 主に kernel space
6.1. 基本形
結論だけ言うと、user 側はプロセスごと、kernel 側は基本的に共通です。
| 領域 | 誰のものか | 補足 |
|---|---|---|
| user space | プロセスごと | 別プロセスとは基本的に別 mapping |
| kernel space | 基本は共通 | 各プロセスの page table にほぼ同じ kernel mapping が入る |
感覚的にはこうです。
| プロセス | lower canonical | upper canonical |
|---|---|---|
| process A | A 専用の user mapping | 共通の kernel mapping |
| process B | B 専用の user mapping | 共通の kernel mapping |
6.2. 例外と注意点
ここでは、kernel 空間が共有されるといっても、全領域が同じ物理ページを指すわけではない点を補います。
kernel 空間は共有されますが、upper canonical の全領域が「各プロセスで同じ物理ページを指す」という言い方は強すぎます。
| 種類 | 典型例 | 各プロセスでの見え方 |
|---|---|---|
| グローバルに共有される kernel mapping | kernel text, direct map の一部 | ほぼ共通 |
| 個別オブジェクトを含む mapping | per-CPU data, 各 task の kernel stack など | kernel 空間にあるが、内容は個別 |
つまり、kernel 空間は共有された仮想アドレス空間だが、その中身には共有ページと個別ページが混在するという理解が正確です。
6.3. 実装上の補足
ここでは、PTI/KPTI が入ると page table の見え方がどう変わるかを補足します。
近年の Linux では、セキュリティ対策として PTI (Page Table Isolation) が入ることがあります。これは
- user 実行中の page table
- kernel 実行中の page table
を分け、user 実行中には full kernel mapping を極力見せないようにする仕組みです。
したがって、「常に 1 枚の page table の中に user と kernel が共存する」と言い切ると強すぎます。ただし メモリ保護の考え方を理解するための第一近似としては、lower canonical と upper canonical に分けて考えて問題ありません。
7. メモリ保護
ここでは、CPU 特権と page 属性が実際のアクセス可否をどう決めるかをまとめます。
ここまでの話をまとめると、アクセス可否は page 側の属性 と CPU の現在状態 を照合して決まります。
| 参照元 | 何を見るか |
|---|---|
PTE または TLB
|
User/Supervisor, R/W, XD, Present など |
CPU の現在状態 |
CPL、読み取り / 書き込み / 命令 fetch の種別 |
7.1. 判定の仕組み
ここで重要なのは、アドレスだけではなく「今の権限」と「アクセス種別」を組み合わせて判定することです。
| いまの状態 | page 側の属性 | 結果 |
|---|---|---|
CPL=3 |
supervisor page | access 不可 |
CPL=0 |
supervisor page | access 可 |
CPL=3 |
user page + R/W=0 へ write |
access 不可 |
CPL=3 |
user page + XD=1 を fetch |
access 不可 |
つまり、ring だけで決まるわけでも、page 属性だけで決まるわけでもありません。両方を照合して初めてアクセス可否が決まります。
7.2. 典型的な振る舞い
ここでは、user から kernel address に普通に飛べない理由を例として見ます。
ふつうの call は、戻り先を積んで RIP を変えるだけです。権限は変えません。
そのため CPL=3 のまま kernel 側仮想アドレスへ飛ぼうとしても、命令 fetch の時点で MMU が止めます。
ring 0 に入るには、syscall、割り込み、例外のような CPU が認めた正規入口 が必要です。
7.3. メモリアクセスの手順
ここまでを 1 回のアクセス手順として並べると、次のようになります。
| Step | 何が起きるか | 主に見るもの |
|---|---|---|
| 1 | 命令または load/store がアドレスを作る | effective address |
| 2 | 必要なら segment base が加わる | 主に FS/GS
|
| 3 | リニアアドレスが決まる | canonical かどうか |
| 4 |
TLB を引き、外れたら page walk |
CR3, page table |
| 5 | page 属性を確認する |
U/S, R/W, XD, Present
|
| 6 | 現在の特権と照合する |
CPL, access type |
| 7 | 許可ならアクセス、違反なら fault | page fault など |
この表から見える本質は、「アドレス変換」と「アクセス許可判定」は、現代の x86-64 Linux では page walk の中で一体として進むことです。
8. ありがちな誤解
| 誤解 | 実際の見方 |
|---|---|
| セグメントが今も主役 | x86-64 Linux ではページ保護が主役 |
| リニアアドレスと物理アドレスは同じ | 物理アドレスになる前に paging がある |
| 各プロセスは kernel 空間も完全に別 | kernel 側 mapping は基本的に共通 |
| upper canonical 全域が同じ物理ページ | 共有 mapping と個別オブジェクトが混在する |
call で kernel に入れる |
権限は変わらないので入れない |
ring だけ見れば十分 |
実際のアクセス可否は CPL と page 属性の組み合わせで決まる |
9. まとめ
最後に全体を 1 枚でまとめます。
| 観点 | 要点 |
|---|---|
CPU 特権 |
Linux では主に ring 3 と ring 0 を使い、CPL として現在の権限が効く |
| セグメント | 過去は segment:offset からリニアアドレスを作り、paging 無効時はそのまま物理アドレスとして使えた。いまはほぼ flat で FS/GS が例外 |
| リニアアドレス | segmentation 後、paging 前のアドレス。実務上は仮想アドレスとほぼ同じ見え方 |
PTE |
4 KiB page を physical page frame と属性に対応付ける |
| 仮想アドレス空間 | lower canonical は主に user、upper canonical は主に kernel |
| メモリアクセス保護 |
CPL と page table 属性の組み合わせで決まる |
x86-64 Linux のメモリ保護は、要するに **「ほぼ flat なアドレス空間の上で、page table と CPU 特権が access をふるいにかける仕組み」**です。
個々の用語は別々に見えますが、実際には
- segmentation が前段を作る
- paging が mapping と保護を担当する
-
CPU特権が user / kernel の境界を守る
という 3 層としてつながっています。