このページの目次

プロセスのメモリレイアウト

カバー範囲: /proc/pid/maps の読み方 → stack/heap/mmap/vDSO/vvar/vsyscall → セグメント (text/data/bss) → メモリレイアウトと ASLR → mmap のマッピングタイプ 対象: Linux x86-64/ARM64 ユーザーランド

概要

Linux の各プロセスは、48ビット(または57ビット)の仮想アドレス空間を認識しています。この空間のレイアウトを理解することは、メモリ境界違反のデバッグ、core dump の解析、メモリ使用量の最適化にとって極めて重要です。/proc/pid/maps は、このレイアウトの「地図」となります。

典型的な 64-bit レイアウト

64-bit 仮想アドレス空間レイアウト (48-bit 有効, 128TB ユーザー + 128TB カーネル) 0x0000_0000_0000_0000 NULLページ (未マップ, NULL参照の検出) 0x0000_0000_0040_0000 PIE バイナリセグメント .text(R+X)・ .rodata ・ .plt/.got ・ .data ・ .bss 0x0000_0000_0060_0000 heap(brk/sbrk)↑ 上方成長 …… 広大な未マップギャップ …… 0x0000_7f00_0000_0000 mmap 領域 (mmap base) 共有ライブラリ(.so)・ 匿名マップ(malloc 大規模割り当て/スレッドスタック)・ ファイルmmap 0x0000_7fff_ffff_ffff [stack] メインスレッドスタック ↓ 下方成長 ≈0x7fff_ffff_ffff [vvar] vDSO データページ (カーネル timekeeper, 読み取り専用) [vdso] vDSO コードページ (clock_gettime など, システムコール不要) [vsyscall] 廃止された高速呼び出しページ (固定アドレス, セキュリティリスク, デフォルトはエミュレート) 0xffff_ffff_ffff_ffff カーネル空間 (ユーザーランドからアクセス不可) ヒープはバイナリセグメントの末尾から上方へ成長し、mmap領域(スタックを含む)は高アドレスから下方へ成長します—— 両者は互いに向かって成長し、その中間にある巨大な未マップギャップが安全な緩衝帯となります。

/proc/pid/maps の詳細解説

$ cat /proc/self/maps
# 形式: アドレス 権限 オフセット デバイス inode パス名

# === バイナリ (PIE) ===
55a8e3c00000-55a8e3c01000 r--p 00000000 08:02 1234567 /bin/sleep
55a8e3c01000-55a8e3c03000 r-xp 00001000 08:02 1234567 /bin/sleep  ← .text (R+X)
55a8e3c03000-55a8e3c04000 r--p 00003000 08:02 1234567 /bin/sleep  ← .rodata
55a8e3c04000-55a8e3c05000 r--p 00003000 08:02 1234567 /bin/sleep
55a8e3c05000-55a8e3c06000 rw-p 00004000 08:02 1234567 /bin/sleep  ← .data+.bss

# === ヒープ ===
55a8e4c00000-55a8e4c21000 rw-p 00000000 00:00 0       [heap]

# === 共有ライブラリ ===
7f1234000000-7f1234001000 r--p 00000000 08:02 9999 /lib64/ld-linux-x86-64.so.2
7f1234001000-7f123402a000 r-xp 00001000 08:02 9999 /lib64/ld-linux-x86-64.so.2
7f123402a000-7f1234034000 r--p 0002a000 08:02 9999 /lib64/ld-linux-x86-64.so.2
7f1234035000-7f1234037000 rw-p 00034000 08:02 9999 /lib64/ld-linux-x86-64.so.2

# === スレッドスタック (匿名 mmap) ===
7f1233e00000-7f1234000000 rw-p 00000000 00:00 0       ← スレッドスタック

# === スタック ===
7ffc12345000-7ffc12366000 rw-p 00000000 00:00 0       [stack]
7ffc123de000-7ffc123e2000 r--p 00000000 00:00 0       [vvar]
7ffc123e2000-7ffc123e4000 r-xp 00000000 00:00 0       [vdso]

権限ビットの解釈

r--p: 読み取り専用, プライベート              ← .rodata, 読み取り専用セグメント
r-xp: 読み取り+実行, プライベート           ← .text
rw-p: 読み取り+書き込み, プライベート             ← .data, .bss, ヒープ, スタック
r--s: 読み取り専用, 共有               ← ほとんど使用されない
rw-s: 読み取り+書き込み, 共有              ← MAP_SHARED マッピング
---p: アクセス不可, プライベート              ← ガードページ (スタック/ヒープ用)

匿名マップ vs ファイルマップ

[heap], [stack], [vdso]:              匿名マップ (ファイル名なし, カーネルが作成)
/lib64/libc.so.6:                     ファイルマップ (ファイル名あり, ファイルからの mmap)
7f1233e00000 (名前なし):             匿名マップ (malloc, スレッドスタック)
                                        → [heap] ではない! malloc は mmap を使用

各領域の詳細

Stack: スタックの自動成長

// スタックは mmap または brk で割り当てられる (カーネル設定による)
// VM_GROWSDOWN: push 操作で現在のスタック底を超えると → ページフォルト → カーネルが自動的にスタックを拡張
//   → 拡張は RLIMIT_STACK (デフォルト 8MB) に制限される
//   → スタックトップの下方にはガードページ (---p) があり、境界違反を防ぐ

// メインスレッドスタック: [stack] に位置する
// サブスレッドスタック: pthread_create → mmap (通常 8MB, mmap 領域内に位置)

Heap: brk vs mmap

// 従来の brk/sbrk: データセグメントを拡張 (.bss の直後)
//   → ユーザーランド: brk(addr) → プログラムブレークを変更
//   → 問題: 断片化, 個別に解放できない

// malloc の現在の戦略:
//   1. 小規模割り当て (< 128KB デフォルト): brk を使用 → [heap] 領域に配置
//   2. 大規模割り当て (>= 128KB): mmap を使用 → 独立した匿名マップ
//   3. mmap しきい値: mallopt(M_MMAP_THRESHOLD) で調整可能

vDSO / vvar / vsyscall: カーネルによる注入

[vdso]:   各プロセスのアドレス空間に注入される .so
          提供: __vdso_clock_gettime, __vdso_getcpu, __vdso_time, __vdso_getrandom
          → ユーザーランドで実行, システムコール不要 → 極めて高速

[vvar]:   vDSO のデータページ (カーネル timekeeper 構造体, CPU 固有データ)
          → ユーザーランド向けに読み取り専用 (R/O)

[vsyscall]: 廃止された高速システムコールページ (x86 のみ)
            → 固定アドレス: 0xffffffffff600000 (セキュリティリスク)
            → 現在はデフォルトでエミュレート (ページフォルト → カーネルがシミュレート → 返却)

ツール

# プロセスのメモリレイアウト
cat /proc/self/maps
cat /proc/self/smaps | head -50        # 各マッピングの詳細統計 (RSS/PSS/スワップ)

# メモリマップの要約
pmap -x <pid>

# メモリ使用量の概要
cat /proc/self/status | grep Vm
# VmPeak: 歴史的な最大仮想メモリ
# VmSize: 現在の仮想メモリ
# VmRSS:  物理メモリ (Resident)
# VmData/VmStk/VmExe/VmLib: データ/スタック/コード/ライブラリ

# ASLR の設定
cat /proc/sys/kernel/randomize_va_space  # 0=オフ, 1=部分的, 2=完全 (デフォルト)

参考

  • カーネルドキュメント⁠: Documentation/filesystems/proc.rst (maps/smaps)
  • ソースコード⁠: fs/proc/task_mmu.c (/proc/pid/maps の実装), arch/x86/entry/vdso/
  • LWN: "The vDSO and vvar", "ASLR for 64-bit Linux"

キーワード: /proc/pid/maps, stack, heap, vDSO, vvar, ASLR, PIE, smaps, brk, mmap