このページの目次

物理メモリ管理

カバー範囲: 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 ノード記述子

// 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 の種類

// 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

// 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)とメモリ圧力

Watermarks: 3つの水位線が異なる強度のリクライムをトリガー pages 0 high high 水位 kswapd が起動し、バックグラウンドリクライムを開始 low low 水位 カーネルによる直接リクライム(同期、割り当てブロック) min min 水位 緊急割り当てのみがこのラインを突破可能 水位は高い方から低い方へ段階的に警告を発する: high を下回ると → kswapd によるバックグラウンド非同期リクライム; low を下回ると → カーネルによる同期直接リクライム(割り当てブロック); min を下回ると → 緊急割り当てのみが突破可能、メモリ枯渇の境界に迫る。
// アロケーション時の watermark チェック:
// zone_watermark_ok(zone, order, mark, classzone_idx, alloc_flags)
//   zone の空闲ページが order 分の割り当てを満たし、mark 以下にならないか計算
//   不十分の場合 → リクライムをトリガー(kswapd または direct reclaim)

Buddy Allocator

データ構造

// 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)
};

コアアルゴリズム

// アロケーション:
// 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 フラグ

// 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)

// 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

// 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

// 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 によって割り当て可能

デバッグ

# 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