1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

x86-64 Linuxのメモリ保護はどう動くのか ― セグメント、リニアアドレス、仮想メモリ空間、CPU特権をまとめて整理する

1
Posted at

1. はじめに

Linux や x86-64 の説明を読んでいると、次の言葉が何度も出てきます。

  • セグメント
  • リニアアドレス
  • ユーザー空間 / カーネル空間
  • ring 0 / ring 3
  • MMU (Memory Management Unit)

単語ごとの説明は見つかっても、実際のメモリ保護がどの順番で成り立っているかはつながりにくいです。特に混乱しやすいのは、次の点です。

  • セグメントは今も効いているのか
  • リニアアドレスと仮想アドレスは同じなのか
  • 各プロセスは user 空間と kernel 空間をどう持っているのか
  • CPU (Central Processing Unit) の特権とページ保護はどこで組み合わさるのか

この記事では、x86-64 Linux のメモリ保護を次の順番で整理します。

  1. 最初に CPU 特権を最小限だけ押さえる
  2. セグメントとリニアアドレスをつなぐ
  3. page table と PTE (Page Table Entry) で物理アドレスへの変換を見る
  4. 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 系では、CPUsegment: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 modex86-64 Linux を並べて、セグメントの役割がどう変わったかを見ます。

ここでの CSDSES (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) になるため、CSDS の base はどちらも同じになります。

flat model では CSDS の base がどちらも同じので、同じ offset に対して

  • CS:offset
  • DS:offset

は同じリニアアドレスになります。

項目 CS:0x00001234 DS:0x00001234
segment base 0x00000000 0x00000000
linear address 0x00001234 0x00001234

その意味で、リニアアドレス自体には「これはコード」「これはデータ」という区別はありません。ただし区別が消えるわけではありません。

何で区別するか いつ区別するか
CSDS か、descriptor の種別 セグメントを使う段階
命令 fetch か read/write か、ページ属性はどうか 実際のアクセス時

注: リニアアドレスになった時点では、アドレス値そのものにコード / データの区別はありません。

4.4. flat model 時に使用するセグメント関連のレジスタについて

これまで見たように、flat model 時には CSDSESSS の base によるアドレス変換はほぼ使用しなくなります。
しかし、継続して使用するレジスタもあります。

ここでは、flat model 時でも用途を持つ FSGS を見ます。

FSGS は、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/SupervisorR/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 KiB4096 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 pagePTE の関係を図にすると次のようになります。

ここで重要なのは、リニアアドレスの 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/SupervisorR/WXD などの属性と、現在の 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 3ring 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 をふるいにかける仕組み」**です。

個々の用語は別々に見えますが、実際には

  1. segmentation が前段を作る
  2. paging が mapping と保護を担当する
  3. CPU 特権が user / kernel の境界を守る

という 3 層としてつながっています。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?