---
title: 物理メモリ管理
url: https://doc.liz6.com/ja/linux-kernel/02-memory-management/03-physical-memory-management
locale: ja
area: linux-kernel
tags:
- linux-kernel
- memory-management
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: NUMA → Zone (DMA/DMA32/Normal/Movable) → Buddy Allocator → page allocator API → page struct → memory hotplug。カーネルバージョン: 2.6 ~ 6.x'
---

# 物理メモリ管理

> カバー範囲: NUMA → Zone (DMA/DMA32/Normal/Movable) → Buddy Allocator → page allocator API → page struct → memory hotplug
> カーネルバージョン: 2.6 ~ 6.x

## 概要

仮想メモリ管理は「どの仮想アドレスがどの物理ページにマッピングされるか」に答えるのに対し、物理メモリ管理はより低レベルな問題を扱います。**「この 4KB の物理ページは誰に属し、空闲か割り当て済みか、どの NUMA ノードから取得すべきか？」**

物理メモリ管理の中核は **buddy allocator**（ペアアルゴリズム）です。これは空闲ページを異なるオーダ（order）の連続ブロックとして整理し、分割と結合によって効率的な割り当てと解放を実現します。buddy の上位には slab/slub（小オブジェクト用アロケータ）があり、下位には NUMA 対応の zone およびノードトポロジーが存在します。

---

## NUMA アーキテクチャ

```
UMA (Uniform Memory Access):
  全 CPU が全メモリにアクセスするレイテンシが同一
  典型: 単ソケット、デスクトップ

NUMA (Non-Uniform Memory Access):
  メモリは複数のノードに分割される
  各 CPU にはローカルメモリがあり（高速アクセス）、
  リモートメモリへのアクセスは遅い（30%〜40%遅延）
  典型: 複数ソケットサーバー

確認方法: numactl --hardware
          ls /sys/devices/system/node/
```

### pg_data_t: NUMA ノード記述子

```c
// include/linux/mmzone.h
typedef struct pglist_data {
    struct zone     node_zones[MAX_NR_ZONES];
    struct zonelist node_zonelists[MAX_ZONELISTS];  // アロケーションのフォールバック順序
    int             nr_zones;
    unsigned long   node_start_pfn;   // 開始物理ページ番号
    unsigned long   node_present_pages;
    unsigned long   node_spanned_pages;
    int             node_id;
    struct page     *node_mem_map;    // このノード内の全 page struct の配列

    // リクライム
    struct lruvec   lruvec;          // LRU リスト（匿名/ファイル）
    wait_queue_head_t kcompactd_wait;

    // 統計情報
    struct per_cpu_nodestat *per_cpu_nodestats;
} pg_data_t;
```

---

## Zone: 物理メモリの分区

### Zone が必要な理由

```
Zone が存在する根本的な理由: ハードウェアの制約により、特定の物理メモリは
特定の目的には使用できないため

16-bit ISA DMA:  物理アドレス 0〜16MB しかアクセス不可
32-bit PCI DMA:  0〜4GB しかアクセス不可（IOMMU なしの場合）
32-bit カーネル:  896MB までしか直接マッピングできない（highmem）
64-bit:         highmem は存在しないが、zone 構造は DMA/DMA32 の制約を維持
```

### Zone の種類

```c
// include/linux/mmzone.h
enum zone_type {
    ZONE_DMA,       // 0-16MB:    ISA DMA
    ZONE_DMA32,     // 0-4GB:     32-bit PCI DMA
    ZONE_NORMAL,    // 直接マッピング（カーネルが直接アクセス可能、線形マッピング）
    ZONE_HIGHMEM,   // 32-bit のみ: 動的マッピング（64-bit ではこの zone は存在しない）
    ZONE_MOVABLE,   // 移動可能なページ（メモリ断片化対策/ホットプラグ用）
    ZONE_DEVICE,    // PMEM / GPU VRAM / CXL
    __MAX_NR_ZONES
};
```

### struct zone

```c
// include/linux/mmzone.h
struct zone {
    unsigned long   _watermark[NR_WMARK];  // low, min, high の水位線
    unsigned long   managed_pages;         // buddy が管理するページ数
    unsigned long   spanned_pages;
    unsigned long   present_pages;

    // Per-CPU pagesets（ホットな単一ページをキャッシュし、zone lock の取得を回避）
    struct per_cpu_pages  pcp;

    // Buddy の空闲リスト
    struct free_area    free_area[MAX_ORDER];  // order 0〜10

    // LRU（匿名/ファイル）
    struct lruvec       lruvec;

    // ロック: free_area を保護
    spinlock_t          lock;
};
```

### 水位線（Watermarks）とメモリ圧力

<svg viewBox="0 0 720 350" xmlns="http://www.w3.org/2000/svg" font-family="-apple-system,'Source Han Sans CN','Microsoft YaHei',sans-serif" role="img" aria-label="Zone Watermark 水位線とメモリリクライム強度の対応図">
  <defs><marker id="wm-arrow" markerWidth="10" markerHeight="8" refX="8" refY="3" orient="auto"><path d="M0,0 L8,3 L0,6 Z" fill="#475569"/></marker></defs>
  <rect width="720" height="350" fill="#ffffff"/>
  <text x="360" y="28" text-anchor="middle" font-size="17" font-weight="700" fill="#1f2933">Watermarks: 3つの水位線が異なる強度のリクライムをトリガー</text>
  <line x1="90" y1="250" x2="90" y2="56" stroke="#475569" stroke-width="1.8" marker-end="url(#wm-arrow)"/>
  <text x="90" y="46" text-anchor="middle" font-size="11" fill="#475569">pages</text>
  <text x="90" y="266" text-anchor="middle" font-size="11" fill="#64748b">0</text>
  <line x1="90" y1="96" x2="130" y2="96" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="4 3"/>
  <text x="80" y="100" text-anchor="end" font-size="12" font-weight="700" fill="#15803d">high</text>
  <rect x="140" y="76" width="500" height="40" rx="8" fill="#dcfce7" stroke="#4ade80"/>
  <text x="156" y="93" font-size="12" font-weight="700" fill="#166534">high 水位</text>
  <text x="156" y="109" font-size="11" fill="#15803d">kswapd が起動し、バックグラウンドリクライムを開始</text>
  <line x1="90" y1="166" x2="130" y2="166" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="4 3"/>
  <text x="80" y="170" text-anchor="end" font-size="12" font-weight="700" fill="#c2410c">low</text>
  <rect x="140" y="146" width="500" height="40" rx="8" fill="#ffedd5" stroke="#f97316"/>
  <text x="156" y="163" font-size="12" font-weight="700" fill="#9a3412">low 水位</text>
  <text x="156" y="179" font-size="11" fill="#c2410c">カーネルによる直接リクライム（同期、割り当てブロック）</text>
  <line x1="90" y1="236" x2="130" y2="236" stroke="#94a3b8" stroke-width="1.2" stroke-dasharray="4 3"/>
  <text x="80" y="240" text-anchor="end" font-size="12" font-weight="700" fill="#dc2626">min</text>
  <rect x="140" y="216" width="500" height="40" rx="8" fill="#ffffff" stroke="#ef4444" stroke-dasharray="5 4"/>
  <text x="156" y="233" font-size="12" font-weight="700" fill="#dc2626">min 水位</text>
  <text x="156" y="249" font-size="11" fill="#dc2626">緊急割り当てのみがこのラインを突破可能</text>
  <rect x="60" y="288" width="600" height="42" rx="8" fill="#eef2ff" stroke="#c7d2fe"/>
  <text x="76" y="307" font-size="12.5" fill="#3730a3">水位は高い方から低い方へ段階的に警告を発する: high を下回ると → kswapd によるバックグラウンド非同期リクライム; low を下回ると → カーネルによる同期直接リクライム（割り当てブロック）;</text>
  <text x="76" y="323" font-size="12.5" fill="#3730a3">min を下回ると → 緊急割り当てのみが突破可能、メモリ枯渇の境界に迫る。</text>
</svg>

```c
// アロケーション時の watermark チェック:
// zone_watermark_ok(zone, order, mark, classzone_idx, alloc_flags)
//   zone の空闲ページが order 分の割り当てを満たし、mark 以下にならないか計算
//   不十分の場合 → リクライムをトリガー（kswapd または direct reclaim）
```

---

## Buddy Allocator

### データ構造

```c
// mm/page_alloc.c
// 各 zone の各 order ごとに free_area が存在
// free_area[order]: 2^order ページサイズの空闲ブロックのリスト

#define MAX_ORDER 11  // 通常: 最大 2^10 = 4MB 連続（1024 × 4KB）

struct free_area {
    struct list_head    free_list[MIGRATE_TYPES];  // 移行タイプ別に分類
    unsigned long       nr_free;                   // この order の空闲ブロック数
};

// 移行タイプ（断片化対策）:
enum migratetype {
    MIGRATE_UNMOVABLE,  // カーネルデータ構造（移動不可）
    MIGRATE_MOVABLE,    // ユーザーページ（移動可能 → 断片化対策）
    MIGRATE_RECLAIMABLE,// リクライム可能（例: inode cache）
    MIGRATE_PCPTYPES,   // Per-CPU 用予約
    MIGRATE_HIGHATOMIC, // 高原子性アロケーション（緊急用）
    MIGRATE_CMA,        // Contiguous Memory Allocator
    MIGRATE_ISOLATE,    // 隔離（memory hotplug / compaction）
};
```

### コアアルゴリズム

```c
// アロケーション:
// alloc_pages(gfp_mask, order) → __alloc_pages()
//   → get_page_from_freelist()  // 適切な zone から割り当てを試みる
//     → rmqueue()                // buddy の free_area から取得
//       → __rmqueue_smallest()   // order 以上の最小空闲ブロックを検索
//         → expand()             // 見つかったブロックが必要より大きい場合 → 分割
//              余剰部分を buddy の free_area に戻す

// 例: order=1（2ページ = 8KB）を要求
//   free_area[2] に 16KB の空闲がある場合 → expand():
//     → order 0〜1 の最初の 8KB を呼び出し元に割り当て
//     → 残りの 8KB を free_area[1] に格納

// 解放:
// free_pages(addr, order) → __free_pages()
//   → __free_one_page()
//     → buddy（相手側ブロック）も空闲か確認
//     → buddy が空闲の場合 → order+1 のブロックに結合 → 再帰的に結合
//     → buddy が空闲でない場合 → 対応する order の free_area に格納

// Buddy チェック:
//   buddy_pfn = page_pfn ^ (1 << order)  // XOR: buddy の PFN
//   buddy も空闲で同じ order なら → 結合
```

### GFP フラグ

```c
// include/linux/gfp_types.h
// アロケーション動作の制御:

// Zone モディファイア:
#define __GFP_DMA       // ZONE_DMA から割り当て
#define __GFP_DMA32     // ZONE_DMA32 から割り当て
#define __GFP_HIGHMEM   // HIGHMEM からの割り当てを許可（32-bit のみ）
#define __GFP_MOVABLE   // 移動可能なタイプで割り当て

// Watermark モディファイア:
#define __GFP_ATOMIC    // 高優先度、emergency reserves を使用可能
#define __GFP_HIGH      // emergency reserves を使用可能
#define __GFP_MEMALLOC  // ALLOC_NO_WATERMARKS を許可（極めて緊急）

// Reclaim モディファイア:
#define __GFP_IO        // IO を許可（スワップへの書き戻し）
#define __GFP_FS        // ファイルシステム操作を許可（inode cache のクリアなど）
#define __GFP_DIRECT_RECLAIM  // 同期リクライムを許可
#define __GFP_RECLAIM   // __GFP_DIRECT_RECLAIM の新名称
#define __GFP_NORETRY   // リトライしない

// 一般的な組み合わせ:
#define GFP_KERNEL      (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
                        // 最も一般的: スリープ/リクライム/IO を許可
#define GFP_ATOMIC      (__GFP_HIGH)        
                        // スリープ不可: 割り込み/atomic コンテキスト用
#define GFP_USER        (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
                        // ユーザー空間での割り当て
#define GFP_NOWAIT      (0)                 
                        // 待たない: 割り当て失敗時に即座に NULL を返す
```

### Per-CPU Pages (PCP)

```c
// mm/page_alloc.c
// 各 CPU には per-CPU の 1ページキャッシュ（struct per_cpu_pages）が存在
// 
// なぜ必要か? 
//   単一ページの割り当て（order 0）は最も一般的な操作
//   毎回 zone->lock（グローバルロック）を取得すると → ロック競合が深刻
//   PCP は L1 キャッシュとして機能: ローカルにバッチでページをプリフェッチ
//     → alloc: PCP にあれば → 直接取得（ロック不要）
//              PCP が空の場合 → buddy からバッチ取得（zone lock を1回取得）
//     → free:  PCP が満杯でない場合 → PCP に戻す（ロック不要）
//              PCP が満杯の場合 → buddy に戻す（zone lock を1回取得）
//
// 設計思想: バッチ処理によりロックオーバーヘッドを分散
```

---

## struct page

```c
// include/linux/mm_types.h
// 各物理ページには struct page が1つ対応（4KB ページに対して 64バイト → 約1.5%のオーバーヘッド）

struct page {
    unsigned long flags;       // ページフラグ（PG_locked, PG_dirty, PG_uptodate, ...）

    union {
        // buddy allocator 内: リストノード
        struct list_head lru;

        // slab 内: slab 情報
        struct slab *slab_cache;

        // compound page のヘッドとして: テールページの数
        unsigned long compound_head;
    };

    union {
        atomic_t _mapcount;    // PTE によってマッピングされている数
        atomic_t _refcount;    // 引用カウント（get_page/put_page）
    };

    // address_space へのポインタ（このページが page cache の一部の場合）
    struct address_space *mapping;
    pgoff_t index;             // mapping 内のオフセット

    // 固有データ（用途に応じて解釈）:
    //   匿名ページ: anon_vma
    //   slab:     slab 情報
    //   buffer:   buffer_head
    void *private;
};
```

`struct page` は過負荷のかかった構造体です——ページの用途（buddy 空闲、slab、匿名メモリ、page cache、THP compound）によって、同じフィールドが異なる意味を持ちます。カーネルは `flags` 内のビットを使用してこれを区別します。

---

## Memory Hotplug

```c
// mm/memory_hotplug.c
// 物理メモリの追加/削除:
//   online: add_memory() → __add_memory() → page struct の初期化 → buddy への登録
//   offline: remove_memory() → __remove_memory() → 使用中のページを移行 → buddy からの解除

// DIMM (NVDIMM/CXL):
//   ZONE_DEVICE として構成可能（buddy 管理外）
//   または通常のメモリ（ZONE_NORMAL）として構成し、buddy によって割り当て可能
```

---

## デバッグ

```bash
# Buddy allocator の状態
cat /proc/buddyinfo
# Node 0, zone Normal: 11 4 2 1 0 0 ...
#   order 0: 11 blocks（4KB ページとして 44KB 空闲）
#   order 1: 4 blocks  （8KB チャンクとして 32KB 空闲）

# Zone 情報
cat /proc/zoneinfo
# 含まれる情報: per-zone 水位線、空闲ページ、保護情報

# NUMA 統計
numastat  # ノード別アロケーション/ミス統計

# メモリ断片化指数
cat /sys/kernel/debug/extfrag/unusable_index
# 1.000 = 完全に断片化しており、連続ページを割り当て不可
# 0.000 = 断片化なし

# トリガーされたリクライムの統計
cat /proc/vmstat | grep -E 'pgsteal|pgscan|compact'
```

---

## 参考と拡張

- **カーネルドキュメント**: `Documentation/mm/page_alloc.rst`, `Documentation/admin-guide/sysctl/vm.rst`
- **LWN**: 
  - "The design of the buddy allocator" (lwn.net/Articles/259832/)
  - "Memory management APIs" シリーズ
- **ソースファイル**:
  - `mm/page_alloc.c` — buddy allocator（約7000行）
  - `include/linux/mmzone.h` — zone, pg_data_t, free_area
  - `include/linux/mm_types.h` — struct page
  - `include/linux/gfp_types.h` — GFP フラグ

---

*キーワード: NUMA, Zone, Buddy Allocator, GFP_KERNEL, GFP_ATOMIC, struct page, watermark, kswapd, memory hotplug, compaction*
