vmallocと高位メモリ・カーネルアドレス空間レイアウト
カーネルの仮想アドレスがどう区画されているかが腑に落ちます。direct map/vmalloc/vmemmap/fixmap の役割分担と、KASLR・ハイメモリの背景を64bitの実レイアウトで押さえられます。
- 1.64bit Linuxの上位半分はカーネル専用で、direct map・vmalloc・vmemmap・fixmapなど用途別の区画に固定オフセットで割り当てられている。
- 2.direct mapは全物理RAMを線形に写し、phys=virt-PAGE_OFFSETの定数差で物理アドレスへ即変換できるが、vmallocは仮想的に連続でも物理は非連続になる。
- 3.ハイメモリは32bitで物理RAMがdirect mapに収まらない問題への対処で、64bitでは空間が広大なため不要になり姿を消した。
なぜカーネル空間を区画に分けるのか
仮想記憶では、各プロセスが独自の仮想アドレス空間を持ちます。x86-64 の 4 レベルページングが使う 48bit 空間(256TB)は、下位半分をユーザー空間、上位半分をカーネル空間に分けます。下位は 0x0000_0000_0000_0000〜0x0000_7fff_ffff_ffff、上位は 0xffff_8000_0000_0000〜0xffff_ffff_ffff_ffff で、中央の広大な穴はカノニカル形式(上位ビットの符号拡張)が要求する非正規領域です。
カーネル空間はすべてのプロセスで共有されます。コンテキストスイッチで CR3 を切り替えてもユーザー側のマッピングだけが入れ替わり、上位半分のカーネルマッピングは不変です。これにより、システムコールや割り込みでカーネルに入ったとき、どのプロセスの文脈でもカーネルは同じアドレスで自分のコード・データ・全物理メモリに届きます。
この上位半分は1枚の平板ではなく、用途別の**区画(region)**に固定オフセットで分割されています。なぜ分けるのか——区画ごとに「物理メモリとの対応規則」が違うからです。ある区画は物理 RAM を線形に丸ごと写し、別の区画は非連続な物理ページを仮想的に連続させ、また別の区画は固定アドレスにデバイスを貼り付けます。規則が異なるものを混ぜないことで、アドレスを見ただけで意味が決まる設計になっています。
x86-64(4レベル, 48bit)の仮想アドレス空間
0x0000_0000_0000_0000 ┌─────────────┐
│ ユーザー空間 │ プロセスごとに固有・CR3で切替
0x0000_7fff_ffff_ffff └─────────────┘
(非カノニカルの穴:符号拡張で到達不可)
0xffff_8000_0000_0000 ┌─────────────┐
│ カーネル空間 │ 全プロセスで共有・区画に分割
0xffff_ffff_ffff_ffff └─────────────┘
カーネル空間の主要区画
x86-64 Linux のカーネル空間は、おおむね次の順で並びます。各区画は固定の開始アドレスとサイズを持ち、間に**ガードホール(未マップの隙間)**を挟んで隣接区画への踏み越しを検知します。
| 区画 | 役割 | 物理との対応 |
|---|---|---|
| direct map | 全物理RAMを線形に写した窓 | phys = virt - PAGE_OFFSET の定数差 |
| vmalloc | 仮想的に連続な大きな確保 | 物理は非連続・ページ単位で散在 |
| vmemmap | 全物理ページのstruct page配列 | ページ番号で線形に索引 |
| fixmap | コンパイル時に固定した特殊用途 | 起動初期・割り込み等の固定スロット |
| カーネルtext/module | カーネル本体とモジュールのコード | ロード時に物理へ写像 |
direct map(線形マッピング)
最も基本的なのが direct map(linear mapping、PAGE_OFFSET から始まる領域)です。これは搭載された物理 RAM の全体を、仮想空間にそのままの順序で線形に貼り付けた窓です。物理アドレス P のページは、必ず仮想アドレス PAGE_OFFSET + P に現れます。
物理RAM : 0 ──────────────────────── 物理メモリ上限
| |
direct map : PAGE_OFFSET ───────────── PAGE_OFFSET + 上限
(恒等オフセットで1対1に対応)
この定数オフセットの効果は絶大です。カーネルがあるページの物理アドレスを知りたいとき、ページテーブルを引く必要はなく、phys = virt - PAGE_OFFSET という引き算1回で求まります(マクロ __pa / __va の正体)。カーネルが「任意の物理ページにいつでも触れる」のは、この direct map が全 RAM を常時マップしているからです。カーネルアロケータが返すページの大半は、この領域のアドレスとして渡されます。
direct map 上で仮想的に連続な範囲は、物理的にも連続です(オフセットが定数だから)。そのため DMA のように「物理連続なバッファ」を要求するデバイス I/O では、direct map 上の連続領域(kmalloc 由来)が使えます。次の vmalloc にはこの保証がありません。
vmalloc:仮想連続・物理非連続
大きなバッファを確保したいが、物理メモリが断片化していて連続フレームが取れない——このとき使うのが vmalloc 区画です。vmalloc は仮想アドレス上では連続な範囲を割り当てますが、その裏に貼るのはばらばらの物理ページです。ページテーブルが非連続な物理ページを連続した仮想アドレスへ束ね直すことで、連続性の幻を作ります。
vmalloc領域(仮想): [ページA][ページB][ページC][ページD] ← 連続
│ │ │ │
物理フレーム : frame 91 frame 7 frame 50 frame 23 ← 非連続でよい
direct map と vmalloc の使い分けは、カーネルメモリ確保の基本判断です。
| 観点 | kmalloc(direct map) | vmalloc |
|---|---|---|
| 物理連続性 | 連続(DMA可) | 非連続(基本DMA不可) |
| 仮想連続性 | 連続 | 連続 |
| 物理→仮想変換 | 引き算1回で高速 | ページテーブルを引く必要あり |
| 大きな確保 | 連続フレーム枯渇で失敗しやすい | 断片化に強く大容量を取りやすい |
| TLB/表コスト | 低い | 専用PTEを張るぶん高い |
小さく性能が要る確保や DMA バッファは kmalloc(direct map)、数百KB以上で物理連続が不要な確保(モジュールのロード領域、大きなハッシュ表など)は vmalloc が定石です。vmalloc はアドレスの物理変換に表参照が要るぶん割高なので、安易に常用しません。
vmemmap と fixmap
カーネルは全物理ページを管理するため、ページ1枚ごとに struct page(参照カウント、フラグ、所属など)を持ちます。これを物理ページ番号で線形に索引できる巨大配列として置いたのが vmemmap(sparse-vmemmap)区画です。pfn_to_page(pfn) が単純なポインタ演算で済むのは、この配列が仮想空間に線形配置されているからです。物理メモリに穴(ホットプラグ等)があっても、対応部分だけを遅延マップして疎な配列を成立させます。
fixmap は、コンパイル時に用途とスロットを固定した特殊領域です。ページテーブルがまだ十分に立ち上がっていない起動初期や、特定の固定アドレスが必要な場面(一部の割り込み・APIC・初期コンソール等)で使われます。アドレスがビルド時に確定しているため、変数を介さず即座に参照できるのが利点です。
ハイメモリ:32bitが抱えた歴史的制約
direct map は「全物理 RAM を仮想空間に線形マップする」設計でした。これは仮想空間が物理 RAM より十分広い前提で成り立ちます。ところが 32bit では前提が崩れます。
32bit Linux の典型構成では、4GB の仮想空間をユーザー 3GB + カーネル 1GBに分けます。カーネルの取り分が 1GB しかないため、direct map に線形マップできる物理 RAM は約 896MB が上限でした。これを超える物理メモリ(例えば 4GB 搭載)は、direct map に常時居場所がありません。この「direct map に常駐できない物理メモリ」が ハイメモリ(high memory) です。
32bit カーネル空間(約1GB)
[ direct map(~896MB) ][ vmalloc/fixmap/kmap 等(~128MB) ]
↑ ここに収まる物理RAMだけ常時マップ
896MB を超える物理RAM = ハイメモリ:必要時に一時的に窓へ貼って使う
ハイメモリのページは、触りたいときに kmap で空間内の一時スロットへ貼り(マップ)、用が済んだら剥がす(アンマップ)、という出し入れで扱いました。常時マップでないため、ハイメモリ上のページはアドレスを直接持てず、コードが煩雑になり、kmap スロットの枯渇やマップ/アンマップのコストという固有の問題を抱えました。
ハイメモリは物理メモリが足りないのではなく、それを写すための仮想アドレス空間(カーネル側の窓)が足りないために生じた問題です。物理 RAM 量ではなく、direct map に割けるアドレス幅が制約だった、という因果を取り違えないことが要点です。
64bit では事情が一変します。カーネル空間だけで数十 TB 以上(実装上の direct map 上限は 64TB 規模)が使え、現実の搭載 RAM をすべて direct map に線形マップしても余ります。全物理 RAM が常時 direct map に居場所を持つため、ハイメモリという概念そのものが不要になり、64bit カーネルからは姿を消しました。ハイメモリは「アドレス空間が物理メモリより狭かった時代」の遺物です。
KASLR:区画の位置を起動ごとにずらす
ここまでの区画配置は「固定オフセット」と述べましたが、セキュリティ上の理由から起動ごとに基準位置をランダムにずらすのが現代の既定です。これが KASLR(Kernel Address Space Layout Randomization) で、ユーザー空間の ASLR のカーネル版にあたります。
狙いは、攻撃者がカーネルのコードやデータの絶対アドレスを事前に決め打ちできないようにすることです。固定配置だと、脆弱性を突いて特定の関数アドレスへ飛ばす攻撃(ROP 等)が成立しやすい。起動ごとにオフセットが変われば、攻撃者はまずアドレスを漏らす(情報リーク)必要が生じ、攻撃難度が上がります。
KASLR がずらすのは主に2系統です。
- カーネル text/module 配置:カーネル本体のロードアドレスを乱数化し、関数の絶対位置を隠す。
- direct map / vmalloc / vmemmap の基準:各区画の開始位置を乱数化(physical/memory KASLR)し、データ領域の位置を隠す。
起動A: text=base+0x1a00000 direct map=base+0x...37
起動B: text=base+0x0c00000 direct map=base+0x...c2
↑ 同じカーネルでも毎回ずれる → 絶対アドレス決め打ちを困難に
KASLR はオフセットを秘密にする防御なので、そのオフセットを漏らす情報リークがあれば一撃で無力化されます。dmesg や /proc からカーネルアドレスが見える、サイドチャネル(Meltdown 等)でカーネル空間が観測できる、といった経路でオフセットが復元されると、以降の配置は固定同然になります。KASLR は単独の防壁ではなく、リーク対策(KPTI、アドレス秘匿)と組み合わせて初めて効きます。
「上位半分=全プロセス共有のカーネル空間」「direct map は全物理RAMの線形写像で phys=virt-PAGE_OFFSET の定数変換」「vmalloc は仮想連続・物理非連続でDMA不可」「vmemmap は struct page の線形配列」「ハイメモリは32bitのアドレス空間不足が原因で64bitでは消滅」「KASLR は区画位置の乱数化でリークに弱い」の6点が頻出です。kmalloc とvmalloc の連続性の違いが最も問われます。
まとめ
- 64bit Linux は仮想空間の上位半分をカーネル専用とし、全プロセスで共有する。区画は用途別に固定オフセット(+KASLR)で並ぶ。
- direct map は全物理 RAM の線形写像で、
phys = virt - PAGE_OFFSETの定数差で物理へ即変換でき、連続性も保つため DMA に使える。 - vmalloc は仮想連続だが物理は非連続で、断片化に強く大容量を取りやすい代わりに DMA 不可・変換コスト高。vmemmap は
struct pageの線形配列、fixmap は起動初期等の固定スロット。 - ハイメモリは 32bit でカーネル側のアドレス空間が物理 RAM を線形マップしきれなかった制約への対処で、空間が広大な 64bit では不要になり消滅した。
- KASLR は区画とコードの基準位置を起動ごとに乱数化して絶対アドレスの決め打ちを防ぐが、情報リークに弱くリーク対策との併用が前提。
変換機構の詳細は多段ページテーブルとMMUとTLBの内部、ユーザー側の配置とASLRはプロセスアドレス空間も合わせてどうぞ。
OS Article
vmallocと高位メモリ・カーネルアドレス空間レイアウトを実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
カーネル
比較で見る軸
難易度: advanced / カテゴリ: OS / タグ数: 6
導入後に効く点
direct mapは全物理RAMを線形に写し、phys=virt-PAGE_OFFSETの定数差で物理アドレスへ即変換できるが、vmallocは仮想的に連続でも物理は非連続になる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- OS
- タグ数
- 6
判断チェックリスト
- 自社の用途が「カーネル / 仮想記憶」に近いか確認する。
- 強みである「64bit Linuxの上位半分はカーネル専用で、direct map・vmalloc・vmemmap・fixmapなど用途別の区画に固定オフセットで割り当てられている。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。