Linux Kernel Memory Management

Linux Kernel メモリ管理 - 包括的技術ガイド

対象読者: Linux カーネルのメモリ管理サブシステムを深く理解したいシステムエンジニア、SRE、カーネル開発者

カーネルバージョン: 主に Linux 5.x / 6.x 系を対象

最終更新: 2026年4月


目次

  1. はじめに
  2. 仮想メモリとページング
  3. ページテーブル(4段・5段ページテーブル)
  4. バディシステムアロケータ
  5. スラブアロケータ(SLUB / SLAB / SLOB)
  6. kmalloc / vmalloc / kzalloc
  7. ページキャッシュとバッファキャッシュ
  8. メモリゾーン(DMA / Normal / HighMem)
  9. NUMAメモリ管理
  10. Transparent Huge Pages (THP)
  11. HugePages(明示的)
  12. メモリオーバーコミットとOOM Killer
  13. スワップと zswap / zram
  14. メモリコンパクションとマイグレーション
  15. KSM(Kernel Same-page Merging)
  16. Memory cgroups(v1 / v2)
  17. /proc インターフェース
  18. メモリ監視ツール
  19. トラブルシューティング実践
  20. まとめとベストプラクティス

1. はじめに

Linux カーネルのメモリ管理サブシステムは、オペレーティングシステムの中で最も複雑かつ重要なコンポーネントの一つである。物理メモリという有限のリソースを、多数のプロセスやカーネル自身が効率的かつ安全に利用できるよう、多層的な抽象化と最適化を提供している。

1.1 メモリ管理の全体像

Linux のメモリ管理は以下の主要コンポーネントから構成される:

┌─────────────────────────────────────────────────────┐
│                ユーザ空間プロセス                       │
│   malloc() / mmap() / brk() / sbrk()                │
├─────────────────────────────────────────────────────┤
│              システムコールインターフェース               │
├─────────────────────────────────────────────────────┤
│              仮想メモリサブシステム                     │
│   ┌─────────┐  ┌──────────┐  ┌──────────────┐      │
│   │VMA管理    │  │ページフォルト│  │ mmap / munmap│      │
│   └─────────┘  └──────────┘  └──────────────┘      │
├─────────────────────────────────────────────────────┤
│              ページ回収 / スワップ                     │
│   ┌──────┐  ┌────────┐  ┌──────┐  ┌─────────┐     │
│   │kswapd │  │ページ回収 │  │OOM    │  │コンパクション│     │
│   └──────┘  └────────┘  └──────┘  └─────────┘     │
├─────────────────────────────────────────────────────┤
│             物理メモリアロケータ                       │
│   ┌────────────┐  ┌──────────────┐                  │
│   │バディシステム │  │スラブアロケータ │                  │
│   └────────────┘  └──────────────┘                  │
├─────────────────────────────────────────────────────┤
│              ハードウェア抽象化                        │
│   ┌─────────┐  ┌──────┐  ┌──────────┐             │
│   │ページテーブル│  │TLB    │  │NUMAトポロジ│             │
│   └─────────┘  └──────┘  └──────────┘             │
├─────────────────────────────────────────────────────┤
│              物理メモリ (DRAM)                        │
└─────────────────────────────────────────────────────┘

1.2 本ドキュメントの目的

本ドキュメントでは、各コンポーネントの内部動作を詳細に解説するとともに、実際のシステム運用で役立つ設定例、コマンド、コードスニペットを豊富に提供する。単なる理論の解説にとどまらず、本番環境でのトラブルシューティングやパフォーマンスチューニングに直結する実践的な知識を目指す。

1.3 前提知識

  • Linux の基本的なコマンド操作
  • C 言語の基礎的な理解
  • コンピュータアーキテクチャの基本概念(CPU、キャッシュ、メモリ階層)

2. 仮想メモリとページング

2.1 仮想メモリの基本概念

仮想メモリは、各プロセスに独立した連続的なアドレス空間を提供する抽象化レイヤーである。これにより以下の利点が得られる:

  • プロセス間の隔離: 各プロセスは他のプロセスのメモリにアクセスできない
  • アドレス空間の統一: 各プロセスが同じ仮想アドレスから開始できる
  • 物理メモリの効率的利用: 実際に使用されるページのみが物理メモリに配置される
  • メモリマップドI/O: ファイルをメモリ空間にマッピングできる

2.2 x86_64 の仮想アドレス空間レイアウト

64ビット Linux (x86_64) では、仮想アドレス空間は以下のように分割される:

x86_64 仮想アドレス空間 (4段ページテーブルの場合)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

0xFFFFFFFFFFFFFFFF  ┌──────────────────────┐
                    │   カーネル空間          │
                    │   (128 TB)            │
0xFFFF800000000000  ├──────────────────────┤
                    │                      │
                    │   正規でない領域        │ ← ここのアドレスは使えない
                    │   (canonical hole)    │   (48ビット → 64ビットの
                    │                      │    符号拡張による)
0x00007FFFFFFFFFFF  ├──────────────────────┤
                    │   ユーザ空間           │
                    │   (128 TB)            │
0x0000000000000000  └──────────────────────┘

カーネル空間の詳細な仮想アドレスマップ:

カーネル仮想アドレスマップ (x86_64, 4段ページテーブル)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

アドレス範囲                          用途
──────────────────────────────────────────────────────
0xFFFFFFFF80000000 - 0xFFFFFFFFa0000000  カーネルテキストマッピング
0xFFFFFFFFE0000000 - 0xFFFFFFFFFE000000  モジュール用マッピング
0xFFFFEA0000000000 - 0xFFFFEAFFFFFFFFFF  vmemmap (struct page 配列)
0xFFFFC90000000000 - 0xFFFFE8FFFFFFFFFF  vmalloc / ioremap 空間
0xFFFF888000000000 - 0xFFFFC87FFFFFFFFF  直接マップ領域
0xFFFF800000000000 - 0xFFFF87FFFFFFFFFF  ガード / 未使用

2.3 ページングの仕組み

Linux はページング方式を採用しており、メモリをページ(通常 4KB)という単位で管理する。

仮想アドレスから物理アドレスへの変換:

仮想アドレス:  0x00007F3A4B201038
                │
                ▼
         ┌──────────────┐
         │ MMU (メモリ管理 │
         │  ユニット)      │
         └──────┬───────┘
                │ ページテーブル
                │ ウォーク
                ▼
物理アドレス:  0x000000012C401038

ページの状態遷移:

                    ┌──────────┐
         ページフォルト │  未割当   │
      ┌────────────│ (未マップ) │
      │            └──────────┘
      ▼
┌──────────┐    スワップアウト    ┌──────────┐
│  メモリ上  │──────────────────→│ スワップ上  │
│ (アクティブ)│←──────────────────│           │
└──────────┘    スワップイン     └──────────┘
      │
      │ ページ回収
      ▼
┌──────────┐
│   解放    │
└──────────┘

2.4 ページフォルトの種類

ページフォルトは仮想メモリシステムの中核的なメカニズムである:

種類説明処理コスト
マイナーフォルトページは物理メモリ上にあるがページテーブルにマッピングされていない低い
メジャーフォルトページがディスク(スワップやファイル)から読み込まれる必要がある高い
不正フォルトアクセス権限の違反またはマッピングされていない領域へのアクセスSIGSEGV

ページフォルトの統計を確認する:

# プロセスのページフォルト統計
cat /proc/<pid>/stat | awk '{print "minor faults:", $10, "major faults:", $12}'

# システム全体のページフォルト
vmstat 1 5
#  procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
#  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
#  1  0      0 2891404  45672 1892340    0    0    12     8  156  312  2  1 97  0  0

# /proc/vmstat からの詳細
grep -E "pgfault|pgmajfault" /proc/vmstat
# pgfault 1285647392
# pgmajfault 2847

2.5 Copy-on-Write (COW)

fork() システムコールは Copy-on-Write メカニズムを使用して効率的にプロセスを複製する:

fork() 直後:

親プロセスのPT          物理ページ          子プロセスのPT
┌────────┐          ┌────────┐          ┌────────┐
│ VPN 0  │────────→ │ PFN 100│ ←────────│ VPN 0  │  読み取り専用
│ VPN 1  │────────→ │ PFN 200│ ←────────│ VPN 1  │  読み取り専用
│ VPN 2  │────────→ │ PFN 300│ ←────────│ VPN 2  │  読み取り専用
└────────┘          └────────┘          └────────┘

子プロセスがVPN 1に書き込み後:

親プロセスのPT          物理ページ          子プロセスのPT
┌────────┐          ┌────────┐          ┌────────┐
│ VPN 0  │────────→ │ PFN 100│ ←────────│ VPN 0  │
│ VPN 1  │────────→ │ PFN 200│          │ VPN 1  │────→ PFN 400 (コピー)
│ VPN 2  │────────→ │ PFN 300│ ←────────│ VPN 2  │
└────────┘          └────────┘          └────────┘

2.6 デマンドページング

Linux はデマンドページングを採用している。malloc() で割り当てたメモリは、実際にアクセスされるまで物理ページが割り当てられない。

/* デマンドページングの実例 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

int main(void)
{
    size_t size = 1UL * 1024 * 1024 * 1024; /* 1 GB */

    printf("Before malloc: check /proc/%d/status\n", getpid());
    sleep(5);

    /* malloc は仮想アドレス空間のみ確保する */
    char *buf = malloc(size);
    if (!buf) {
        perror("malloc");
        return 1;
    }
    printf("After malloc (VmSize increased, VmRSS not): check /proc/%d/status\n", getpid());
    sleep(5);

    /* メモリへの書き込みで初めて物理ページが割り当てられる */
    memset(buf, 0x42, size);
    printf("After memset (VmRSS increased): check /proc/%d/status\n", getpid());
    sleep(5);

    free(buf);
    return 0;
}

別ターミナルで確認:

# プロセスのメモリ使用量を監視
watch -n 1 "grep -E 'VmSize|VmRSS|VmData' /proc/<pid>/status"

2.7 mmap によるメモリマッピング

mmap() は仮想メモリ管理の中核的なシステムコールである:

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int main(void)
{
    /* 匿名マッピング(スワップバックのメモリ確保) */
    void *anon = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

    /* ファイルマッピング(ファイルバックのメモリ) */
    int fd = open("/etc/hostname", O_RDONLY);
    void *file_map = mmap(NULL, 4096, PROT_READ,
                          MAP_PRIVATE, fd, 0);
    printf("hostname: %s", (char *)file_map);

    /* 共有マッピング(プロセス間共有) */
    void *shared = mmap(NULL, 4096, PROT_READ | PROT_WRITE,
                        MAP_SHARED | MAP_ANONYMOUS, -1, 0);

    munmap(anon, 4096);
    munmap(file_map, 4096);
    munmap(shared, 4096);
    close(fd);
    return 0;
}

プロセスのメモリマッピング一覧を確認する:

# /proc/<pid>/maps でマッピング情報を確認
cat /proc/self/maps
# 出力例:
# 5574e8a00000-5574e8a02000 r--p 00000000 08:01 1835008  /usr/bin/cat
# 5574e8a02000-5574e8a06000 r-xp 00002000 08:01 1835008  /usr/bin/cat
# 5574e8a06000-5574e8a08000 r--p 00006000 08:01 1835008  /usr/bin/cat
# 5574e8a09000-5574e8a0a000 rw-p 00008000 08:01 1835008  /usr/bin/cat
# 7f8a3c000000-7f8a3c021000 rw-p 00000000 00:00 0
# 7ffc5d6c9000-7ffc5d6ea000 rw-p 00000000 00:00 0        [stack]

# /proc/<pid>/smaps で詳細統計
cat /proc/self/smaps | head -30

3. ページテーブル

3.1 ページテーブルの概要

ページテーブルは仮想アドレスから物理アドレスへの変換を行うデータ構造であり、MMU(メモリ管理ユニット)によってハードウェア的に参照される。

3.2 4段ページテーブル(x86_64 標準)

x86_64 での4段ページテーブル構造:

48ビット仮想アドレスの分解:
┌─────────┬─────────┬─────────┬─────────┬──────────┐
│ PGD(9)  │ PUD(9)  │ PMD(9)  │ PTE(9)  │Offset(12)│
│ bit47-39│ bit38-30│ bit29-21│ bit20-12│ bit11-0  │
└─────────┴─────────┴─────────┴─────────┴──────────┘
   512項目    512項目    512項目    512項目     4KB

ページテーブルウォーク:

CR3 (PGD ベース)
  │
  ├─→ PGD[idx] ──→ PUD ベース
  │                   │
  │                   ├─→ PUD[idx] ──→ PMD ベース
  │                   │                   │
  │                   │                   ├─→ PMD[idx] ──→ PTE ベース
  │                   │                   │                   │
  │                   │                   │                   ├─→ PTE[idx]
  │                   │                   │                   │     │
  │                   │                   │                   │     ▼
  │                   │                   │                   │  物理フレーム番号
  │                   │                   │                   │  + オフセット
  │                   │                   │                   │  = 物理アドレス

各レベルのカバー範囲:

レベルエントリ数カバー範囲用途
PGD512512 GBPage Global Directory
PUD5121 GBPage Upper Directory
PMD5122 MBPage Middle Directory
PTE5124 KBPage Table Entry

3.3 5段ページテーブル(LA57)

Intel Ice Lake 以降で対応した5段ページテーブル(LA57拡張):

57ビット仮想アドレスの分解:
┌─────────┬─────────┬─────────┬─────────┬─────────┬──────────┐
│ P4D(9)  │ PGD(9)  │ PUD(9)  │ PMD(9)  │ PTE(9)  │Offset(12)│
│ bit56-48│ bit47-39│ bit38-30│ bit29-21│ bit20-12│ bit11-0  │
└─────────┴─────────┴─────────┴─────────┴─────────┴──────────┘

対応する仮想アドレス空間:
- 4段: 256 TB (ユーザ128TB + カーネル128TB)
- 5段: 128 PB (ユーザ64PB + カーネル64PB)

5段ページテーブルの有効化確認:

# カーネルが5段ページテーブルをサポートしているか確認
grep CONFIG_X86_5LEVEL /boot/config-$(uname -r)
# CONFIG_X86_5LEVEL=y

# 現在の動作モードを確認
dmesg | grep -i "page table"
# [    0.000000] x86/mm: 5-level paging enabled

# cpuinfo でLA57サポートを確認
grep la57 /proc/cpuinfo
# flags : ... la57 ...

# カーネルブートパラメータで無効化する場合
# GRUB_CMDLINE_LINUX="no5lvl"

3.4 ページテーブルエントリの構造

x86_64 のページテーブルエントリ(PTE)のビットレイアウト:

63  62       52 51                    12 11  9 8 7 6 5 4 3 2 1 0
┌─┬──────────┬─────────────────────────┬─────┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│NX│  利用可能 │     物理フレーム番号      │AVL  │G│PAT│D│A│PCD│PWT│U/S│R/W│P│
└─┴──────────┴─────────────────────────┴─────┴─┴─┴─┴─┴─┴─┴─┴─┴─┘

P   (Present)       : ページが物理メモリに存在する
R/W (Read/Write)    : 書き込み可能
U/S (User/Supervisor): ユーザ空間からアクセス可能
PWT (Page-level Write-Through): ライトスルーキャッシュ
PCD (Page-level Cache Disable): キャッシュ無効
A   (Accessed)      : アクセスされた(参照ビット)
D   (Dirty)         : 書き込みされた(ダーティビット)
PAT (Page Attribute Table): メモリタイプ指定
G   (Global)        : TLBフラッシュ対象外(カーネルページ用)
NX  (No Execute)    : 実行不可(DEP/NXbit)

3.5 TLB (Translation Lookaside Buffer)

TLB はページテーブルウォークの結果をキャッシュするハードウェアバッファである:

# TLB 情報の確認
perf stat -e dTLB-loads,dTLB-load-misses,iTLB-loads,iTLB-load-misses -- ./my_program
# Performance counter stats for './my_program':
#     1,234,567,890      dTLB-loads
#            45,678      dTLB-load-misses    # 0.00% of all dTLB loads
#       567,890,123      iTLB-loads
#            12,345      iTLB-load-misses    # 0.00% of all iTLB loads

# TLBフラッシュ統計
grep -E "nr_tlb" /proc/vmstat
# nr_tlb_remote_flush 12345
# nr_tlb_remote_flush_received 67890
# nr_tlb_local_flush_all 1234
# nr_tlb_local_flush_one 56789

3.6 カーネルのページテーブル操作 API

/* カーネル内でのページテーブル操作の例 */
#include <linux/mm.h>
#include <linux/pgtable.h>

/* 仮想アドレスから物理ページを取得する */
static struct page *vaddr_to_page(struct mm_struct *mm, unsigned long addr)
{
    pgd_t *pgd;
    p4d_t *p4d;
    pud_t *pud;
    pmd_t *pmd;
    pte_t *pte;

    pgd = pgd_offset(mm, addr);
    if (pgd_none(*pgd) || pgd_bad(*pgd))
        return NULL;

    p4d = p4d_offset(pgd, addr);
    if (p4d_none(*p4d) || p4d_bad(*p4d))
        return NULL;

    pud = pud_offset(p4d, addr);
    if (pud_none(*pud) || pud_bad(*pud))
        return NULL;

    pmd = pmd_offset(pud, addr);
    if (pmd_none(*pmd) || pmd_bad(*pmd))
        return NULL;

    pte = pte_offset_map(pmd, addr);
    if (!pte || pte_none(*pte)) {
        if (pte)
            pte_unmap(pte);
        return NULL;
    }

    struct page *page = pte_page(*pte);
    pte_unmap(pte);
    return page;
}

3.7 ページテーブルのメモリ消費

ページテーブル自体もメモリを消費する。大量のプロセスが広範なアドレス空間を使用する場合、この消費量は無視できない:

# システム全体のページテーブルメモリ使用量
grep PageTables /proc/meminfo
# PageTables:       123456 kB

# プロセス毎のページテーブル使用量
grep VmPTE /proc/<pid>/status
# VmPTE:       512 kB

# 全プロセスのページテーブル使用量をソート表示
for pid in /proc/[0-9]*; do
    pt=$(grep VmPTE "$pid/status" 2>/dev/null | awk '{print $2}')
    name=$(grep Name "$pid/status" 2>/dev/null | awk '{print $2}')
    if [ -n "$pt" ]; then
        echo "$pt $name $(basename $pid)"
    fi
done | sort -rn | head -20

4. バディシステムアロケータ

4.1 バディシステムの概要

バディシステムは Linux カーネルの物理ページアロケータである。連続した物理ページ(ページフレーム)の割り当てと解放を管理する。

4.2 アルゴリズムの仕組み

バディシステムは 2^n ページ(n = 0 〜 MAX_ORDER-1、通常0〜10)の単位でメモリを管理する:

バディシステムのフリーリスト構造:

Order 10: [1024ページ = 4MB]  ── ○ ── ○ ── ...
Order  9: [512ページ  = 2MB]  ── ○ ── ○ ── ...
Order  8: [256ページ  = 1MB]  ── ○ ── ○ ── ...
Order  7: [128ページ  = 512KB]── ○ ── ○ ── ...
Order  6: [64ページ   = 256KB]── ○ ── ○ ── ...
Order  5: [32ページ   = 128KB]── ○ ── ○ ── ...
Order  4: [16ページ   = 64KB] ── ○ ── ○ ── ...
Order  3: [8ページ    = 32KB] ── ○ ── ○ ── ...
Order  2: [4ページ    = 16KB] ── ○ ── ○ ── ...
Order  1: [2ページ    = 8KB]  ── ○ ── ○ ── ...
Order  0: [1ページ    = 4KB]  ── ○ ── ○ ── ...

割り当て例: 8KB (2ページ = order 1) を要求
1. order 1 のフリーリストを確認
2. 空きがなければ order 2 を確認
3. order 2 のブロック(4ページ)を見つけたら、2つに分割
4. 一方を返却、もう一方を order 1 のフリーリストに追加

解放例: order 1 のブロックを解放
1. バディ(隣接する同サイズのブロック)が空いているか確認
2. バディも空いていればマージして order 2 のブロックにする
3. さらに上位 order でもマージ可能なら再帰的にマージ

4.3 /proc/buddyinfo の読み方

cat /proc/buddyinfo
# Node 0, zone      DMA      1      0      0      1      2      1      1      0      1      1      3
# Node 0, zone    DMA32    785    572    321    176     98     54     30     16      8      4     67
# Node 0, zone   Normal  12345   8765   4321   2100   1050    525    262    131     65     32    128
# Node 1, zone   Normal   9876   7654   3210   1600    800    400    200    100     50     25     96

# 各列は order 0 〜 order 10 の空きブロック数
# 例: Node 0, zone Normal の order 0 に 12345 ブロック (12345 × 4KB = 約48MB)
# 例: Node 0, zone Normal の order 10 に 128 ブロック (128 × 4MB = 512MB)

空きメモリの断片化を確認する:

# 断片化指数 (0=断片化なし, 1=完全に断片化)
cat /proc/sys/vm/extfrag_threshold
# 500 (デフォルト値)

# 断片化の詳細情報
cat /sys/kernel/debug/extfrag/extfrag_index
# Node 0, zone      DMA  -1.000 -1.000 -1.000 -1.000 -1.000 -1.000 ...
# Node 0, zone   Normal   0.000  0.001  0.003  0.008  0.021  0.058 ...

# 断片化を定量的に確認
cat /sys/kernel/debug/extfrag/unusable_index

4.4 GFP フラグ(Get Free Pages)

カーネル内でメモリを要求する際に使用される GFP フラグ:

/* 主要な GFP フラグ */

/* ゾーン指定フラグ */
#define GFP_DMA         __GFP_DMA       /* DMAゾーンから割り当て */
#define GFP_DMA32       __GFP_DMA32     /* DMA32ゾーンから割り当て */
#define GFP_HIGHMEM     __GFP_HIGHMEM   /* HighMemゾーンから割り当て */

/* 複合フラグ(よく使われる組み合わせ) */
#define GFP_ATOMIC      (__GFP_HIGH | __GFP_KSWAPD_RECLAIM)
    /* 割り込みコンテキストなどスリープ不可の状況で使用 */

#define GFP_KERNEL      (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
    /* カーネルの通常のメモリ割り当て(スリープ可能) */

#define GFP_USER        (__GFP_RECLAIM | __GFP_IO | __GFP_FS | __GFP_HARDWALL)
    /* ユーザ空間向けの割り当て */

#define GFP_NOIO        (__GFP_RECLAIM)
    /* I/Oを伴うページ回収を行わない */

#define GFP_NOFS        (__GFP_RECLAIM | __GFP_IO)
    /* ファイルシステムのコールバックを伴うページ回収を行わない */

/* 使用例 */
struct page *page;

/* 通常のカーネルメモリ割り当て */
page = alloc_pages(GFP_KERNEL, 0);  /* 1ページ (4KB) */

/* 割り込みハンドラでの割り当て */
page = alloc_pages(GFP_ATOMIC, 0);

/* 連続した8ページ (32KB) の割り当て */
page = alloc_pages(GFP_KERNEL, 3);  /* order 3 = 2^3 = 8ページ */

4.5 alloc_pages の内部動作

/* 簡略化された alloc_pages のフロー */
struct page *alloc_pages(gfp_t gfp_mask, unsigned int order)
{
    /* 1. 適切なゾーンリストを選択 */
    /* 2. ゾーンのフリーリストから割り当て試行 (fast path) */
    /* 3. 失敗した場合: */
    /*    a. kswapd を起動してバックグラウンドでページ回収 */
    /*    b. 直接ページ回収 (direct reclaim) を実行 */
    /*    c. メモリコンパクションを試行 */
    /*    d. 最終手段として OOM killer を呼び出し */
    /* 4. 成功するまでリトライ、またはNULLを返す */
}

4.6 ページアロケータの統計

# ページアロケータの統計情報
grep -E "pgalloc|pgfree|pgsteal|pgscan" /proc/vmstat
# pgalloc_dma 0
# pgalloc_dma32 234567
# pgalloc_normal 89012345
# pgfree 89246912
# pgsteal_kswapd 1234567
# pgsteal_direct 5678
# pgscan_kswapd 2345678
# pgscan_direct 12345

# perf でページアロケータのイベントをトレース
perf stat -e kmem:mm_page_alloc,kmem:mm_page_free -- sleep 10

5. スラブアロケータ

5.1 スラブアロケータの概要

バディシステムはページ単位(4KB)でメモリを管理するが、カーネル内の多くのオブジェクトはそれよりも小さい(例: struct inode は約600バイト、struct task_struct は約8KB)。スラブアロケータは、バディシステムから取得したページを小さなオブジェクト単位に分割して管理する。

バディシステムとスラブアロケータの関係:

バディシステム (ページ単位の管理)
  │
  │ alloc_pages() で複数ページを取得
  ▼
┌──────────────────────────────────────┐
│           スラブ (Slab)               │
│  ┌─────┐┌─────┐┌─────┐┌─────┐      │
│  │obj 0 ││obj 1 ││obj 2 ││obj 3 │...  │
│  └─────┘└─────┘└─────┘└─────┘      │
│  割り当て済  空き     空き    割り当て済  │
└──────────────────────────────────────┘

スラブキャッシュの構造:
kmem_cache ("task_struct")
  ├── full slabs    (全オブジェクト使用中)
  ├── partial slabs (一部使用中)
  └── empty slabs   (全オブジェクト未使用)

5.2 SLAB / SLUB / SLOB の比較

Linux カーネルには3つのスラブアロケータ実装がある:

特性SLABSLUBSLOB
状態レガシーデフォルト (2.6.23以降)非推奨 (6.4で削除)
対象汎用汎用組み込み
CPU キャッシュあり (per-CPU 配列)あり (per-CPU freelist)なし
メタデータスラブ外スラブ内 (ページ構造体利用)最小限
デバッグ複雑充実 (RED_ZONE, POISON等)最小限
メモリ効率最高 (小規模システム向け)
スケーラビリティ最高

5.3 SLUB アロケータの内部構造

SLUB は現在のデフォルトスラブアロケータである:

SLUB の内部構造:

kmem_cache
  │
  ├── cpu_slab (per-CPU)
  │     ├── freelist ──→ 空きオブジェクトのリンクリスト
  │     ├── page     ──→ 現在使用中のスラブページ
  │     └── partial  ──→ パーシャルスラブのローカルリスト
  │
  ├── node[0] (NUMAノード0)
  │     └── partial  ──→ パーシャルスラブのリスト
  │
  └── node[1] (NUMAノード1)
        └── partial  ──→ パーシャルスラブのリスト

オブジェクト割り当てフロー:
1. cpu_slab->freelist から取得 (最速、ロック不要)
2. cpu_slab->page の freelist から取得
3. cpu_slab->partial から新しいスラブを選択
4. node->partial から新しいスラブを選択
5. バディシステムから新しいスラブページを割り当て

5.4 スラブキャッシュの作成と利用

#include <linux/slab.h>

/* カスタムスラブキャッシュの作成(カーネルモジュール内) */
static struct kmem_cache *my_cache;

struct my_object {
    int id;
    char name[64];
    struct list_head list;
};

static int __init my_module_init(void)
{
    /* スラブキャッシュの作成 */
    my_cache = kmem_cache_create(
        "my_object_cache",          /* 名前 */
        sizeof(struct my_object),   /* オブジェクトサイズ */
        0,                          /* アラインメント (0=デフォルト) */
        SLAB_HWCACHE_ALIGN,        /* フラグ */
        NULL                        /* コンストラクタ (NULLの場合なし) */
    );
    if (!my_cache)
        return -ENOMEM;

    /* オブジェクトの割り当て */
    struct my_object *obj = kmem_cache_alloc(my_cache, GFP_KERNEL);
    if (!obj)
        return -ENOMEM;

    obj->id = 42;
    snprintf(obj->name, sizeof(obj->name), "test_object");

    /* オブジェクトの解放 */
    kmem_cache_free(my_cache, obj);

    return 0;
}

static void __exit my_module_exit(void)
{
    /* スラブキャッシュの破棄 */
    kmem_cache_destroy(my_cache);
}

5.5 slabtop コマンド

# スラブキャッシュの使用状況をリアルタイム表示
slabtop -s c  # キャッシュサイズでソート

# 出力例:
#  Active / Total Objects (% used)    : 1234567 / 1345678 (91.7%)
#  Active / Total Slabs (% used)     : 45678 / 45678 (100.0%)
#  Active / Total Caches (% used)    : 123 / 178 (69.1%)
#  Active / Total Size (% used)      : 456.78M / 512.34M (89.2%)
#  Minimum / Average / Maximum Object: 0.02K / 0.38K / 16.00K
#
#   OBJS ACTIVE  USE OBJ SIZE  SLABS OBJ/SLAB CACHE SIZE NAME
#  98765  96543  97%    0.19K   4703       21     18812K dentry
#  87654  85432  97%    0.57K   6261       14     50088K inode_cache
#  76543  74321  97%    1.06K   2551       30     81632K ext4_inode_cache
#  65432  63210  96%    0.12K   1921       34      7684K kernfs_node_cache
#  54321  52100  95%    0.50K   1697       32     27152K kmalloc-512

# /proc/slabinfo でも確認可能
cat /proc/slabinfo | head -5
# slabinfo - version: 2.1
# # name            <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab>
# ext4_inode_cache  76543  78000   1080   30    8
# dentry            98765  98910    192   21    1
# inode_cache       87654  87696    584   14    2

# 特定のスラブキャッシュの詳細
cat /sys/kernel/slab/kmalloc-256/
# aliases        alloc_calls  cache_dma  cpu_slabs  ...

5.6 SLUB デバッグ機能

# カーネルブートパラメータでSLUBデバッグを有効化
# slub_debug=FZPU
# F: 解放済みオブジェクトの検証 (Sanity checks)
# Z: Red Zone(前後にガード領域を追加)
# P: Poisoning(解放時に特定パターンで埋める)
# U: ユーザトラッキング(割り当て/解放のコールスタック記録)

# 特定のキャッシュのみデバッグ
# slub_debug=FZP,dentry

# ランタイムでのデバッグ制御
echo 1 > /sys/kernel/slab/kmalloc-256/trace
echo 1 > /sys/kernel/slab/kmalloc-256/validate

# デバッグ情報の確認
cat /sys/kernel/slab/kmalloc-256/alloc_calls
#      12345 kmalloc+0x1a/0x30 [kernel] age=5234/7890/12345 pid=0-32767
#       6789 __alloc_skb+0x84/0x170 [kernel] age=123/456/789 pid=0-12345

cat /sys/kernel/slab/kmalloc-256/free_calls

6. カーネルメモリ割り当てAPI

6.1 kmalloc

kmalloc は物理的に連続したメモリを割り当てる。カーネル内で最も頻繁に使用されるメモリ割り当て関数である。

#include <linux/slab.h>

/* 基本的な kmalloc の使用 */
void *ptr = kmalloc(256, GFP_KERNEL);
if (!ptr)
    return -ENOMEM;

/* 使用後の解放 */
kfree(ptr);

/* kmalloc のサイズクラス (SLUB の場合) */
/*
 * kmalloc-8, kmalloc-16, kmalloc-32, kmalloc-64,
 * kmalloc-96, kmalloc-128, kmalloc-192, kmalloc-256,
 * kmalloc-512, kmalloc-1024 (1k), kmalloc-2048 (2k),
 * kmalloc-4096 (4k), kmalloc-8192 (8k),
 * kmalloc-16384 (16k), kmalloc-32768 (32k),
 * kmalloc-65536 (64k), kmalloc-131072 (128k),
 * kmalloc-262144 (256k), kmalloc-524288 (512k),
 * kmalloc-1048576 (1M), kmalloc-2097152 (2M),
 * kmalloc-4194304 (4M)
 *
 * 128バイト要求 → kmalloc-128 から割り当て
 * 200バイト要求 → kmalloc-256 から割り当て (内部断片化56バイト)
 */

6.2 kzalloc

kzalloc はゼロクリアされたメモリを割り当てる。セキュリティと正確性のため、新しいコードでは kmalloc よりも kzalloc が推奨される。

/* kzalloc = kmalloc + memset(0) */
struct my_struct *s = kzalloc(sizeof(*s), GFP_KERNEL);
if (!s)
    return -ENOMEM;

/* 全フィールドが0/NULLに初期化されている */
/* s->count == 0, s->ptr == NULL, etc. */

kfree(s);

6.3 vmalloc

vmalloc は仮想的に連続だが物理的には不連続なメモリを割り当てる:

#include <linux/vmalloc.h>

/* vmalloc の使用 */
void *ptr = vmalloc(1024 * 1024);  /* 1MB */
if (!ptr)
    return -ENOMEM;

/* DMAやハードウェアレジスタアクセスには使用不可 */
/* (物理的に不連続のため) */

vfree(ptr);

/* vzalloc = vmalloc + ゼロクリア */
void *zptr = vzalloc(1024 * 1024);

6.4 kmalloc vs vmalloc の比較

特性kmallocvmalloc
物理的連続性連続不連続(仮想的には連続)
最大サイズ4MB (通常)仮想アドレス空間の制限まで
DMA使用可能不可
パフォーマンス高速やや遅い(ページテーブル操作が必要)
TLB効率良い(直接マップ)やや劣る
断片化時失敗しやすい成功しやすい
用途小〜中サイズの割り当て大きなバッファ
/* 使い分けの指針 */

/* kmalloc を使うべき場合: */
/* - サイズが小さい (< ページサイズ) */
/* - DMA に使用する */
/* - パフォーマンスが重要 */
void *dma_buf = kmalloc(4096, GFP_KERNEL | GFP_DMA);

/* vmalloc を使うべき場合: */
/* - 大きなバッファが必要 (数十KB以上) */
/* - 物理的連続性が不要 */
/* - kmalloc が失敗する可能性がある */
void *large_buf = vmalloc(256 * 1024);  /* 256KB */

/* kvmalloc: kmalloc を試行し、失敗したら vmalloc にフォールバック */
void *buf = kvmalloc(size, GFP_KERNEL);
kvfree(buf);  /* kvmalloc で割り当てたメモリの解放 */

6.5 メモリ割り当ての監視

# カーネルメモリ割り当ての追跡
echo 1 > /sys/kernel/debug/tracing/events/kmem/kmalloc/enable
echo 1 > /sys/kernel/debug/tracing/events/kmem/kfree/enable
cat /sys/kernel/debug/tracing/trace_pipe

# 出力例:
# bash-1234 [002] .... 12345.678: kmalloc: call_site=ffffffff81234567
#   ptr=ffff888012345678 bytes_req=128 bytes_alloc=128 gfp_flags=GFP_KERNEL

# vmalloc の使用状況
cat /proc/vmallocinfo | head -20
# 0xffffc90000000000-0xffffc90000002000    8192 acpi_os_map_memory+0x...
# 0xffffc90000002000-0xffffc90000004000    8192 hpet_enable+0x...
# ...

# vmalloc 使用量の合計
grep VmallocUsed /proc/meminfo
# VmallocUsed:      123456 kB

7. ページキャッシュとバッファキャッシュ

7.1 ページキャッシュの概要

ページキャッシュは、ディスクI/Oを削減するためにファイルの内容をメモリにキャッシュする仕組みである。Linux のメモリ管理において最も重要な最適化の一つ。

ページキャッシュの構造:

ファイル A (inode)
  │
  └── address_space
        └── Radix Tree (XArray)
              ├── ページ 0 ──→ [物理ページ: ファイルの先頭4KB]
              ├── ページ 1 ──→ [物理ページ: ファイルの次4KB]
              ├── ページ 2 ──→ (キャッシュされていない)
              ├── ページ 3 ──→ [物理ページ: dirty]
              └── ...

読み取り時の動作:
1. ページキャッシュに存在? → ヒット → メモリから直接返す
2. 存在しない → ミス → ディスクから読み込み → キャッシュに追加 → 返す

書き込み時の動作:
1. ページキャッシュに書き込む → ページを dirty としてマーク
2. pdflush/writeback スレッドが定期的に dirty ページをディスクに書き戻す

7.2 ページキャッシュの統計

# ページキャッシュの使用量
grep -E "^Cached|^Buffers|^Active\(file\)|^Inactive\(file\)" /proc/meminfo
# Buffers:          245760 kB
# Cached:          4587520 kB
# Active(file):    2345678 kB
# Inactive(file):  2241842 kB

# ページキャッシュヒット率の確認(perf を使用)
perf stat -e cache-references,cache-misses -- dd if=/path/to/file of=/dev/null bs=4k
# Performance counter stats:
#     1,234,567,890      cache-references
#           123,456      cache-misses    # 0.01% of all cache references

# cachestat (Linux 6.1+) で詳細なキャッシュ統計
# bcc-tools の cachestat
cachestat 1
#     HITS   MISSES  DIRTIES HITRATIO   BUFFERS_MB  CACHED_MB
#    12345      123       45   99.01%          240       4480
#    11234       89       12   99.21%          240       4482

7.3 ページキャッシュの制御

# ページキャッシュの手動ドロップ
sync                              # まずdirtyページを書き戻す
echo 1 > /proc/sys/vm/drop_caches  # ページキャッシュのドロップ
echo 2 > /proc/sys/vm/drop_caches  # dentry、inode キャッシュのドロップ
echo 3 > /proc/sys/vm/drop_caches  # 上記すべてドロップ

# ダーティページの書き戻し制御
sysctl vm.dirty_ratio              # ダーティページの上限(%)→ この比率を超えると同期書き込み
# vm.dirty_ratio = 20

sysctl vm.dirty_background_ratio   # バックグラウンド書き戻し開始閾値(%)
# vm.dirty_background_ratio = 10

sysctl vm.dirty_expire_centisecs   # ダーティページの有効期限(1/100秒)
# vm.dirty_expire_centisecs = 3000  (30秒)

sysctl vm.dirty_writeback_centisecs  # 書き戻しスレッドの起動間隔
# vm.dirty_writeback_centisecs = 500  (5秒)

# データベースサーバ向けチューニング例
sysctl -w vm.dirty_ratio=5
sysctl -w vm.dirty_background_ratio=2
sysctl -w vm.dirty_expire_centisecs=1000

7.4 バッファキャッシュ

バッファキャッシュはブロックデバイスのメタデータ(スーパーブロック、inodeテーブル等)をキャッシュする:

# バッファキャッシュの確認
grep ^Buffers /proc/meminfo
# Buffers:          245760 kB

# ブロックデバイスのI/O統計
iostat -x 1
# Device  r/s     w/s    rkB/s    wkB/s   await  svctm  %util
# sda     12.50   45.30  500.00  1812.00   2.34   0.89  4.50
# nvme0n1 156.00  234.00 9984.00 14976.00  0.12   0.05  1.23

7.5 readahead(先読み)

# ファイルの先読みサイズ確認(ブロック数、1ブロック=512バイト)
blockdev --getra /dev/sda
# 256 (= 128KB)

# 先読みサイズの設定
blockdev --setra 2048 /dev/sda  # 1MB に設定

# プロセス毎の先読みヒント
# posix_fadvise(fd, offset, len, POSIX_FADV_SEQUENTIAL)  → 先読み増加
# posix_fadvise(fd, offset, len, POSIX_FADV_RANDOM)      → 先読み無効化
# posix_fadvise(fd, offset, len, POSIX_FADV_DONTNEED)    → キャッシュ破棄

# /proc/vmstat で先読み統計
grep -E "pgpgin|pgpgout|pgfault" /proc/vmstat

8. メモリゾーン

8.1 メモリゾーンの概要

Linux カーネルは物理メモリをゾーン(Zone)に分割して管理する。これはハードウェアの制約(DMAの物理アドレス制限など)に対応するためである。

8.2 各ゾーンの説明

x86_64 でのメモリゾーン配置:

物理アドレス
0x0000000000000000 ┌──────────────┐
                   │   DMA Zone    │  0 〜 16 MB
                   │  (ISA DMA用)  │  古いISAデバイスの制約
0x0000000001000000 ├──────────────┤
                   │  DMA32 Zone   │  16 MB 〜 4 GB
                   │ (32bit DMA用) │  32ビットPCIデバイスの制約
0x0000000100000000 ├──────────────┤
                   │  Normal Zone  │  4 GB 〜 物理メモリ上限
                   │              │
                   │  (大部分のメモリ│
                   │   がここに属する)│
0x0000XXXXXXXXXXXX └──────────────┘

※ x86_64 では HighMem ゾーンは存在しない
   (すべての物理メモリを直接マッピングできるため)

※ 32bit (x86) では:
   - DMA:     0 〜 16 MB
   - Normal:  16 MB 〜 896 MB
   - HighMem: 896 MB 〜 物理メモリ上限
   HighMem はカーネルの仮想アドレス空間で直接マッピングできない領域

8.3 ゾーン情報の確認

# ゾーンごとのメモリ情報
cat /proc/zoneinfo | grep -E "^Node|pages free|managed|present|min|low|high"
# Node 0, zone      DMA
#   pages free     3840
#         min      64
#         low      80
#         high     96
#         managed  3840
#         present  4096
# Node 0, zone    DMA32
#   pages free   234567
#         min     5678
#         low     7097
#         high    8516
#         managed  654321
#         present  655360
# Node 0, zone   Normal
#   pages free  1234567
#         min    12345
#         low    15431
#         high   18517
#         managed 7654321
#         present 7864320

# ゾーンのウォーターマーク
#  min  : これ以下になるとメモリ割り当てが失敗(極めて危険)
#  low  : kswapd がページ回収を開始する閾値
#  high : kswapd がページ回収を停止する閾値

# ウォーターマークの調整
sysctl vm.min_free_kbytes
# vm.min_free_kbytes = 67584

# 増やすと予備メモリが増える(OOM の可能性が減るがキャッシュに使えるメモリが減る)
sysctl -w vm.min_free_kbytes=131072  # 128MB

8.4 ゾーンの回収ポリシー

# zone_reclaim_mode: NUMAシステムでのゾーン回収動作
cat /proc/sys/vm/zone_reclaim_mode
# 0 = 無効(デフォルト: リモートノードからも割り当て可)
# 1 = ゾーン回収有効(ローカルノードのキャッシュを優先的に回収)
# 2 = ダーティページの書き戻しも許可
# 4 = スワップも許可

# 低メモリ予約比率
cat /proc/sys/vm/lowmem_reserve_ratio
# 256   256   32   0
# DMA   DMA32 Normal  (各ゾーンの下位ゾーンからの借用比率)

9. NUMAメモリ管理

9.1 NUMAの概要

NUMA (Non-Uniform Memory Access) は、マルチプロセッサシステムにおけるメモリアーキテクチャである。各CPUソケットが「ノード」を形成し、各ノードにローカルメモリが接続される。

NUMA トポロジの例 (2ソケットシステム):

┌──────────────────────┐    ┌──────────────────────┐
│     ノード 0           │    │     ノード 1           │
│  ┌──────┐  ┌──────┐  │    │  ┌──────┐  ┌──────┐  │
│  │CPU 0  │  │CPU 1  │  │    │  │CPU 2  │  │CPU 3  │  │
│  └──┬───┘  └──┬───┘  │    │  └──┬───┘  └──┬───┘  │
│     └────┬────┘      │    │     └────┬────┘      │
│          │           │    │          │           │
│     ┌────┴────┐      │    │     ┌────┴────┐      │
│     │ L3 Cache │      │    │     │ L3 Cache │      │
│     └────┬────┘      │    │     └────┬────┘      │
│          │           │    │          │           │
│     ┌────┴────┐      │    │     ┌────┴────┐      │
│     │メモリ 64GB│      │    │     │メモリ 64GB│      │
│     └─────────┘      │    │     └─────────┘      │
│                      │    │                      │
└──────────┬───────────┘    └──────────┬───────────┘
           │                           │
           └─────── QPI / UPI ─────────┘
                  (ノード間接続)

ローカルアクセス:  〜100 ns
リモートアクセス:  〜150-300 ns (1.5x 〜 3x 遅い)

9.2 NUMAトポロジの確認

# NUMA ノード構成の確認
numactl --hardware
# available: 2 nodes (0-1)
# node 0 cpus: 0 1 2 3 4 5 6 7 16 17 18 19 20 21 22 23
# node 0 size: 65536 MB
# node 0 free: 32768 MB
# node 1 cpus: 8 9 10 11 12 13 14 15 24 25 26 27 28 29 30 31
# node 1 size: 65536 MB
# node 1 free: 30720 MB
# node distances:
# node   0   1
#   0:  10  21
#   1:  21  10

# lscpu でNUMAノード情報
lscpu | grep -i numa
# NUMA node(s):          2
# NUMA node0 CPU(s):     0-7,16-23
# NUMA node1 CPU(s):     8-15,24-31

# /sys/devices/system/node/ でノード情報
ls /sys/devices/system/node/
# node0  node1

cat /sys/devices/system/node/node0/meminfo
# Node 0 MemTotal:       67108864 kB
# Node 0 MemFree:        33554432 kB
# Node 0 MemUsed:        33554432 kB
# ...

9.3 NUMA メモリポリシー

# numactl でメモリポリシーを指定してプロセスを実行

# ローカルノード優先(デフォルト)
numactl --localalloc ./my_application

# 特定ノードのメモリのみ使用
numactl --membind=0 ./my_application

# メモリをノード間でインターリーブ(ラウンドロビン)
numactl --interleave=all ./my_application

# CPUとメモリを特定ノードに固定
numactl --cpunodebind=0 --membind=0 ./my_application

# 既存プロセスのNUMAポリシー確認
cat /proc/<pid>/numa_maps
# 00400000 default file=/usr/bin/bash mapped=96 active=96 N0=96
# 7f3a4b200000 default anon=128 dirty=128 active=128 N0=96 N1=32
# 7ffc5d6c9000 default stack anon=12 dirty=12 active=12 N0=12

9.4 numastat コマンド

# システム全体のNUMA統計
numastat
#                           node0           node1
# numa_hit               123456789        98765432
# numa_miss                  12345           23456
# numa_foreign               23456           12345
# interleave_hit              1234            1234
# local_node             123000000        98000000
# other_node                456789          765432

# numa_hit    : ポリシー通りのノードから割り当て成功
# numa_miss   : ポリシーとは異なるノードから割り当て
# local_node  : ローカルノードから割り当て
# other_node  : リモートノードから割り当て

# プロセス毎のNUMA統計
numastat -p <pid>
# Per-node process memory usage (in MBs) for PID 1234 (my_app)
#                  Node 0    Node 1    Total
# Huge              0.00      0.00      0.00
# Heap            256.00     64.00    320.00
# Stack             8.00      0.00      8.00
# Private        1024.00    256.00   1280.00
# --------       -------   -------   -------
# Total          1288.00    320.00   1608.00

# NUMA バランシングの有効化
sysctl vm.numa_balancing
# vm.numa_balancing = 1

# NUMA バランシングの統計
grep numa /proc/vmstat
# numa_pte_updates 123456
# numa_huge_pte_updates 12345
# numa_hint_faults 67890
# numa_hint_faults_local 56789
# numa_pages_migrated 23456

9.5 NUMAチューニングのベストプラクティス

# データベースサーバ(MySQL/PostgreSQL)向けNUMA設定
# 方法1: NUMAインターリーブでメモリ配置を均等化
numactl --interleave=all mysqld

# 方法2: CPUとメモリを同じノードに固定
numactl --cpunodebind=0 --membind=0 mysqld

# systemd サービスファイルでの設定例
# /etc/systemd/system/mysqld.service.d/numa.conf
# [Service]
# ExecStart=
# ExecStart=/usr/bin/numactl --interleave=all /usr/sbin/mysqld

# カーネルの自動NUMAバランシング設定
sysctl -w vm.numa_balancing=1          # 有効化
sysctl -w kernel.numa_balancing_scan_delay_ms=1000
sysctl -w kernel.numa_balancing_scan_period_min_ms=1000
sysctl -w kernel.numa_balancing_scan_period_max_ms=60000
sysctl -w kernel.numa_balancing_scan_size_mb=256

10. Transparent Huge Pages (THP)

10.1 THP の概要

Transparent Huge Pages (THP) は、アプリケーションの変更なしに自動的に 2MB のヒュージページを使用する仕組みである。通常の 4KB ページと比較して TLB ミスが大幅に減少し、メモリアクセス性能が向上する。

THP の効果:

通常ページ (4KB):
  仮想アドレス空間 2MB = 512 × 4KB ページ = 512 TLB エントリ必要

Huge Page (2MB):
  仮想アドレス空間 2MB = 1 × 2MB ページ = 1 TLB エントリで済む

TLB カバレッジの比較 (典型的な TLB エントリ数: 1536):
  4KB ページ: 1536 × 4KB = 6 MB
  2MB ページ: 1536 × 2MB = 3 GB  ← 512倍のカバレッジ!

10.2 THP の設定

# THP の現在の状態確認
cat /sys/kernel/mm/transparent_hugepage/enabled
# [always] madvise never

# THP の設定
echo always > /sys/kernel/mm/transparent_hugepage/enabled   # 常に有効
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled  # madvise時のみ
echo never > /sys/kernel/mm/transparent_hugepage/enabled    # 無効

# デフラグ設定
cat /sys/kernel/mm/transparent_hugepage/defrag
# [always] defer defer+madvise madvise never

echo defer+madvise > /sys/kernel/mm/transparent_hugepage/defrag
# defer: バックグラウンドでデフラグ(khugepaged)
# defer+madvise: madvise指定時は即時デフラグ、それ以外はバックグラウンド
# madvise: madvise(MADV_HUGEPAGE)指定時のみデフラグ
# always: 常に即時デフラグ(割り当て時にコンパクション実行、遅延あり)
# never: デフラグなし

# khugepaged の設定
cat /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs
# 10000 (10秒ごとにスキャン)

cat /sys/kernel/mm/transparent_hugepage/khugepaged/pages_to_scan
# 4096 (1回のスキャンで確認するページ数)

cat /sys/kernel/mm/transparent_hugepage/khugepaged/alloc_sleep_millisecs
# 60000 (割り当て失敗時のスリープ時間)

10.3 THP の統計

# THP の統計情報
grep -i huge /proc/meminfo
# AnonHugePages:    524288 kB   ← 匿名THPの使用量
# ShmemHugePages:        0 kB   ← 共有メモリTHP
# FileHugePages:         0 kB   ← ファイルバックTHP
# HugePages_Total:       0      ← 明示的HugePages
# HugePages_Free:        0
# HugePages_Rsvd:        0
# HugePages_Surp:        0
# Hugepagesize:       2048 kB

# /proc/vmstat の THP 統計
grep thp /proc/vmstat
# thp_fault_alloc 12345          ← ページフォルト時のTHP割り当て成功数
# thp_fault_fallback 678         ← THP割り当て失敗→通常ページフォールバック
# thp_collapse_alloc 234         ← khugepaged によるTHP統合成功数
# thp_collapse_alloc_failed 12   ← khugepaged によるTHP統合失敗数
# thp_split_page 89              ← THPの分割(2MBを512×4KBに分解)
# thp_split_pmd 45               ← PMDレベルの分割
# thp_zero_page_alloc 34         ← ゼロページTHP割り当て
# thp_swpout 0                   ← THPのスワップアウト
# thp_swpout_fallback 0          ← THPスワップアウトフォールバック

# プロセス毎のTHP使用状況
grep -i huge /proc/<pid>/smaps_rollup
# AnonHugePages:    262144 kB

10.4 THP とアプリケーションの最適化

#include <sys/mman.h>

/* madvise でTHPを明示的に要求 */
void *addr = mmap(NULL, 2 * 1024 * 1024, PROT_READ | PROT_WRITE,
                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
madvise(addr, 2 * 1024 * 1024, MADV_HUGEPAGE);

/* THP を明示的に拒否 */
madvise(addr, 2 * 1024 * 1024, MADV_NOHUGEPAGE);

10.5 THP の注意点

# データベースサーバでの THP 無効化推奨(Redis, MongoDB, Oracle等)
# THP はレイテンシのスパイクを引き起こす可能性がある

# 永続的な無効化(systemd のサービスファイル)
# /etc/systemd/system/disable-thp.service
cat << 'EOF' > /etc/systemd/system/disable-thp.service
[Unit]
Description=Disable Transparent Huge Pages (THP)
DefaultDependencies=no
After=sysinit.target local-fs.target
Before=mongod.service redis.service

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/enabled'
ExecStart=/bin/sh -c 'echo never > /sys/kernel/mm/transparent_hugepage/defrag'

[Install]
WantedBy=basic.target
EOF

systemctl daemon-reload
systemctl enable disable-thp
systemctl start disable-thp

# GRUB でのカーネルパラメータ設定
# /etc/default/grub
# GRUB_CMDLINE_LINUX="transparent_hugepage=never"
# update-grub

11. HugePages(明示的)

11.1 明示的 HugePages の概要

明示的 HugePages は、システム起動時またはランタイムに予約する固定サイズの巨大ページである。THP と異なり、事前に確保されスワップされない。

HugePages のサイズ:
- x86_64: 2MB (デフォルト), 1GB (pdpe1gb CPUフラグが必要)
- ARM64: 2MB, 32MB, 1GB (ページサイズ依存)

THP vs 明示的 HugePages:
┌──────────────┬─────────────────┬─────────────────────┐
│              │ THP             │ 明示的 HugePages     │
├──────────────┼─────────────────┼─────────────────────┤
│ 設定         │ 自動的           │ 手動で予約が必要      │
│ スワップ      │ 可能(分割後)     │ 不可(ロックされる)    │
│ 断片化        │ 影響あり         │ 影響なし(事前予約)    │
│ サイズ        │ 2MB             │ 2MB, 1GB            │
│ アプリ変更    │ 不要            │ 必要(mmap/shmget)    │
│ 管理         │ カーネルが自動    │ 管理者が手動管理     │
│ 用途         │ 汎用            │ DB, VM, DPDK等      │
└──────────────┴─────────────────┴─────────────────────┘

11.2 HugePages の設定

# 1GB ページ対応確認
grep pdpe1gb /proc/cpuinfo

# 現在のHugePages設定確認
grep -i huge /proc/meminfo
# HugePages_Total:       0
# HugePages_Free:        0
# HugePages_Rsvd:        0
# HugePages_Surp:        0
# Hugepagesize:       2048 kB

# 2MB HugePages の予約 (512ページ = 1GB)
echo 512 > /proc/sys/vm/nr_hugepages
# または
sysctl -w vm.nr_hugepages=512

# 永続的な設定 (/etc/sysctl.conf)
echo "vm.nr_hugepages=512" >> /etc/sysctl.conf

# NUMAノードごとの予約
echo 256 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 256 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages

# 1GB HugePages (カーネルブートパラメータで設定)
# GRUB_CMDLINE_LINUX="hugepagesz=1G hugepages=16 default_hugepagesz=1G"

# hugetlbfs のマウント
mount -t hugetlbfs nodev /mnt/hugepages
# fstab に追加:
# nodev /mnt/hugepages hugetlbfs pagesize=2M,mode=0770,gid=1000 0 0

# 1GB ページ用のマウントポイント
mount -t hugetlbfs -o pagesize=1G nodev /mnt/hugepages-1G

11.3 HugePages の利用(プログラミング)

#include <sys/mman.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>

/* 方法1: mmap + MAP_HUGETLB */
void *huge_mmap(size_t size)
{
    void *addr = mmap(NULL, size,
                      PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
                      -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap MAP_HUGETLB");
        return NULL;
    }
    return addr;
}

/* 方法2: 1GB ページの明示指定 */
void *huge_mmap_1gb(size_t size)
{
    void *addr = mmap(NULL, size,
                      PROT_READ | PROT_WRITE,
                      MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB
                      | MAP_HUGE_1GB,  /* 1GB ページ指定 */
                      -1, 0);
    if (addr == MAP_FAILED) {
        perror("mmap MAP_HUGE_1GB");
        return NULL;
    }
    return addr;
}

/* 方法3: hugetlbfs 経由 */
void *huge_hugetlbfs(size_t size)
{
    int fd = open("/mnt/hugepages/my_hugepage_file",
                  O_CREAT | O_RDWR, 0600);
    if (fd < 0) {
        perror("open hugetlbfs");
        return NULL;
    }

    void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
                      MAP_SHARED, fd, 0);
    close(fd);
    if (addr == MAP_FAILED) {
        perror("mmap hugetlbfs");
        return NULL;
    }
    return addr;
}

/* 方法4: shmget + SHM_HUGETLB */
#include <sys/shm.h>

void *huge_shm(size_t size)
{
    int shmid = shmget(IPC_PRIVATE, size,
                       IPC_CREAT | SHM_HUGETLB | 0600);
    if (shmid < 0) {
        perror("shmget SHM_HUGETLB");
        return NULL;
    }

    void *addr = shmat(shmid, NULL, 0);
    if (addr == (void *)-1) {
        perror("shmat");
        shmctl(shmid, IPC_RMID, NULL);
        return NULL;
    }
    return addr;
}

11.4 データベースでの HugePages 設定

# PostgreSQL での HugePages 設定
# 1. 必要なHugePages数を計算
# shared_buffers = 8GB の場合: 8GB / 2MB = 4096 ページ + バッファ
echo 4200 > /proc/sys/vm/nr_hugepages

# 2. PostgreSQL 設定
# postgresql.conf:
# huge_pages = on
# shared_buffers = 8GB

# 3. PostgreSQL ユーザにHugePages使用権限を付与
# /etc/security/limits.conf:
# postgres    soft    memlock    unlimited
# postgres    hard    memlock    unlimited

# Oracle Database での HugePages 計算スクリプト
# SGA = 32GB の場合
# 必要HugePages = SGA / HugePageSize + バッファ
# = 32GB / 2MB + 100 = 16384 + 100 = 16484

# DPDK での HugePages 設定
echo 1024 > /sys/devices/system/node/node0/hugepages/hugepages-2048kB/nr_hugepages
echo 1024 > /sys/devices/system/node/node1/hugepages/hugepages-2048kB/nr_hugepages
# DPDK は hugetlbfs を使用してメモリプールを確保

12. メモリオーバーコミットとOOM Killer

12.1 メモリオーバーコミットの概要

Linux はデフォルトでメモリのオーバーコミットを許可している。これは、物理メモリ + スワップの合計を超える仮想メモリの割り当てを許可する仕組みである。

オーバーコミットの概念:

物理メモリ: 16 GB + スワップ: 4 GB = 合計 20 GB

プロセス A: malloc(8GB)  → 仮想 8GB 確保(実際の使用: 2GB)
プロセス B: malloc(8GB)  → 仮想 8GB 確保(実際の使用: 3GB)
プロセス C: malloc(8GB)  → 仮想 8GB 確保(実際の使用: 1GB)
                          ─────────
仮想メモリ合計:            24 GB (> 20 GB のオーバーコミット!)
実際の使用量:               6 GB (< 20 GB なので問題ない)

12.2 オーバーコミットポリシー

# オーバーコミットモード
cat /proc/sys/vm/overcommit_memory
# 0 = ヒューリスティック (デフォルト)
#     カーネルが「合理的」な範囲でオーバーコミットを許可
# 1 = 常に許可
#     チェックなしで常にオーバーコミットを許可(OOM リスク高)
# 2 = 厳密モード
#     コミット可能量 = スワップ + RAM × overcommit_ratio/100
#     この量を超える割り当ては拒否される

# オーバーコミット比率(モード2の場合)
cat /proc/sys/vm/overcommit_ratio
# 50 (デフォルト: 物理メモリの50%)

# コミット済みメモリとコミット上限
grep -E "Committed|CommitLimit" /proc/meminfo
# CommitLimit:    12345678 kB   ← コミット可能上限
# Committed_AS:    8765432 kB   ← 現在のコミット済み量

# 設定例: オーバーコミット無効化
sysctl -w vm.overcommit_memory=2
sysctl -w vm.overcommit_ratio=80

# 設定例: 科学計算向け(大量メモリ確保が必要)
sysctl -w vm.overcommit_memory=1

12.3 OOM Killer

物理メモリとスワップが枯渇した場合、OOM Killer がプロセスを強制終了する:

# OOM Killer のスコア確認(値が高いほど kill されやすい)
cat /proc/<pid>/oom_score
# 667

# OOM スコアの調整 (-1000 〜 1000)
echo -1000 > /proc/<pid>/oom_score_adj  # OOM Killer から完全に保護
echo 0 > /proc/<pid>/oom_score_adj      # デフォルト
echo 1000 > /proc/<pid>/oom_score_adj   # 最優先で kill

# 重要なプロセスを OOM Killer から保護
echo -1000 > /proc/$(pidof mysqld)/oom_score_adj
echo -1000 > /proc/$(pidof sshd)/oom_score_adj

# systemd サービスファイルでの設定
# [Service]
# OOMScoreAdjust=-900

# OOM Killer の動作ログ確認
dmesg | grep -i "out of memory\|oom\|killed process"
# [12345.678901] Out of memory: Killed process 5678 (my_app)
#   total-vm:1234567kB, anon-rss:987654kB, file-rss:12345kB, shmem-rss:0kB

# OOM Killer の動作をトリガーするテスト(注意: 危険!)
# stress --vm 1 --vm-bytes $(awk '/MemTotal/{print $2}' /proc/meminfo)k

12.4 OOM Killer の内部動作

OOM Killer のプロセス選択アルゴリズム:

1. 各プロセスの oom_score を計算:
   - ベーススコア = (プロセスのRSS + スワップ使用量) / 全メモリ × 1000
   - oom_score_adj による調整を適用

2. スコアが最も高いプロセスを選択

3. 以下のプロセスは除外:
   - oom_score_adj が -1000 のプロセス
   - カーネルスレッド
   - init プロセス (PID 1)

4. 選択されたプロセスとその子プロセスに SIGKILL を送信

12.5 cgroup レベルの OOM

# cgroup v2 での memory.oom.group
# OOM 時にグループ内の全プロセスを kill
echo 1 > /sys/fs/cgroup/my_group/memory.oom.group

# cgroup v2 での OOM イベント監視
# memory.events ファイルを監視
cat /sys/fs/cgroup/my_group/memory.events
# low 0
# high 0
# max 123
# oom 2
# oom_kill 2
# oom_group_kill 0

13. スワップと zswap / zram

13.1 スワップの概要

スワップは物理メモリが不足した際に、使用頻度の低いページをディスクに退避する仕組みである。

# スワップの状態確認
swapon --show
# NAME      TYPE      SIZE  USED PRIO
# /dev/sda2 partition 4G    256M -2
# /swap.img file      2G    0B   -3

free -h
#                total    used    free  shared  buff/cache   available
# Mem:            31Gi    8.2Gi   12Gi   256Mi       11Gi       22Gi
# Swap:           6.0Gi   256Mi   5.8Gi

# スワップの追加
fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile

# /etc/fstab に追加
# /swapfile none swap sw 0 0

# スワップの削除
swapoff /swapfile
rm /swapfile

13.2 スワップの動作パラメータ

# swappiness: スワップへの積極性 (0-200, デフォルト60)
cat /proc/sys/vm/swappiness
# 60

# 0: 匿名ページのスワップを極力避ける(OOM リスク増加)
# 60: デフォルト(バランス型)
# 100: ファイルキャッシュと匿名ページを均等に扱う
# 200: 匿名ページを積極的にスワップ(6.1以降)

# サーバ向け設定例
sysctl -w vm.swappiness=10       # メモリ重視型サーバ
sysctl -w vm.swappiness=60       # 汎用サーバ
sysctl -w vm.swappiness=0        # データベースサーバ(スワップ最小化)

# vfs_cache_pressure: inodeキャッシュ/dentryキャッシュの回収積極性
cat /proc/sys/vm/vfs_cache_pressure
# 100 (デフォルト)
# 0: キャッシュを回収しない
# 50: 回収を控えめにする
# 100: デフォルト
# 200: 積極的に回収

13.3 zswap

zswap は、スワップに書き出す前にページを圧縮してメモリ上のプールに格納する仕組みである:

# zswap の有効化
echo 1 > /sys/module/zswap/parameters/enabled

# zswap の設定
echo lz4 > /sys/module/zswap/parameters/compressor    # 圧縮アルゴリズム
echo zsmalloc > /sys/module/zswap/parameters/zpool     # メモリプール
echo 20 > /sys/module/zswap/parameters/max_pool_percent # 最大プールサイズ(%)

# カーネルブートパラメータでの設定
# GRUB_CMDLINE_LINUX="zswap.enabled=1 zswap.compressor=lz4 zswap.max_pool_percent=20"

# zswap の統計
grep -r . /sys/kernel/debug/zswap/ 2>/dev/null
# /sys/kernel/debug/zswap/pool_total_size:123456789
# /sys/kernel/debug/zswap/stored_pages:234567
# /sys/kernel/debug/zswap/written_back_pages:12345
# /sys/kernel/debug/zswap/reject_compress_poor:678
# /sys/kernel/debug/zswap/reject_alloc_fail:0
# /sys/kernel/debug/zswap/same_filled_pages:89012

# 圧縮率の確認
# 圧縮率 = stored_pages × PAGE_SIZE / pool_total_size

13.4 zram

zram は RAM 上に圧縮ブロックデバイスを作成し、スワップとして使用する:

# zram モジュールのロード
modprobe zram num_devices=1

# zram デバイスの設定
echo lz4 > /sys/block/zram0/comp_algorithm
echo 4G > /sys/block/zram0/disksize

# zram をスワップとして使用
mkswap /dev/zram0
swapon -p 100 /dev/zram0  # 優先度を高くして通常スワップより先に使用

# zram の統計
cat /sys/block/zram0/mm_stat
# orig_data_size  compr_data_size  mem_used  mem_limit  max_used  same_pages  pages_compacted  huge_pages
# 1234567890      345678901        456789012  0          567890123  12345       6789             0

# zram の圧縮率
zramctl
# NAME       ALGORITHM DISKSIZE   DATA   COMPR  TOTAL STREAMS MOUNTPOINT
# /dev/zram0 lz4            4G   2.1G   512M   567M       8  [SWAP]

# systemd-zram-setup による自動設定
# /etc/systemd/zram-generator.conf
# [zram0]
# zram-size = ram / 2
# compression-algorithm = zstd

13.5 スワップの監視

# スワップI/Oの監視
vmstat 1
#  procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
#  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
#  1  0  51200 2891404  45672 1892340   0   10    12    18  156  312  2  1 97  0  0
# si: スワップイン (KB/s)
# so: スワップアウト (KB/s)

# /proc/vmstat からのスワップ統計
grep -E "pswpin|pswpout|pgswap" /proc/vmstat
# pswpin 12345    ← スワップイン回数
# pswpout 67890   ← スワップアウト回数

# プロセスごとのスワップ使用量
for pid in /proc/[0-9]*; do
    swap=$(grep VmSwap "$pid/status" 2>/dev/null | awk '{print $2}')
    name=$(grep Name "$pid/status" 2>/dev/null | awk '{print $2}')
    if [ -n "$swap" ] && [ "$swap" -gt 0 ] 2>/dev/null; then
        echo "$swap $name $(basename $pid)"
    fi
done | sort -rn | head -20

# smem でスワップ使用量を確認
smem -s swap -r | head -20

14. メモリコンパクションとマイグレーション

14.1 メモリコンパクション

メモリコンパクションは、断片化した物理メモリを整理して連続した空きページを作り出す仕組みである:

コンパクション前:
│使│空│使│空│空│使│空│使│使│空│使│空│空│空│使│使│
 ↓
コンパクション後:
│使│使│使│使│使│使│使│空│空│空│空│空│空│空│空│空│

コンパクションは2つのスキャナで動作:
- Migration Scanner: 下位アドレスから使用中のページを探索
- Free Scanner: 上位アドレスから空きページを探索
- 両者が出会うまでページを移動

14.2 コンパクションの設定

# 手動コンパクション実行
echo 1 > /proc/sys/vm/compact_memory

# コンパクションの閾値
cat /proc/sys/vm/extfrag_threshold
# 500 (0-1000)
# 値が小さいほどコンパクションが頻繁に発生

# コンパクションの統計
grep compact /proc/vmstat
# compact_migrate_scanned 1234567
# compact_free_scanned 2345678
# compact_isolated 345678
# compact_stall 123          ← 直接コンパクションの発生回数
# compact_fail 12            ← コンパクション失敗回数
# compact_success 111        ← コンパクション成功回数

# プロアクティブコンパクション (Linux 5.9+)
cat /proc/sys/vm/compaction_proactiveness
# 20 (0-100, 0=無効)
echo 40 > /proc/sys/vm/compaction_proactiveness  # より積極的に

14.3 ページマイグレーション

NUMAノード間のページ移動:

# migratepages コマンドで特定プロセスのページを移動
migratepages <pid> 0 1  # ノード0からノード1へ移動

# move_pages() システムコールによるページ単位の移動
# (プログラムから使用)

# マイグレーション統計
grep -E "pgmigrate|numa_pages" /proc/vmstat
# pgmigrate_success 123456
# pgmigrate_fail 789
# numa_pages_migrated 67890

15. KSM(Kernel Same-page Merging)

15.1 KSM の概要

KSM は、同一内容のメモリページを検出して共有することでメモリを節約する仕組みである。仮想マシン環境で特に有効。

KSM の動作:

VM1 のメモリ     VM2 のメモリ     VM3 のメモリ
┌──────┐        ┌──────┐        ┌──────┐
│Page A │ = = = │Page A │ = = = │Page A │  同一内容
│(4KB)  │        │(4KB)  │        │(4KB)  │
└──────┘        └──────┘        └──────┘
   │                │                │
   ▼     KSM適用後   ▼                ▼
┌──────┐
│Page A │ ←── 全VMが同一物理ページを共有 (COW)
│(4KB)  │     メモリ節約: 12KB → 4KB
└──────┘

15.2 KSM の設定

# KSM の有効化
echo 1 > /sys/kernel/mm/ksm/run

# KSM のパラメータ
echo 200 > /sys/kernel/mm/ksm/sleep_millisecs  # スキャン間のスリープ時間
echo 1000 > /sys/kernel/mm/ksm/pages_to_scan   # 1回のスキャンでチェックするページ数

# KSM の統計
cat /sys/kernel/mm/ksm/pages_shared     # 共有されているユニークページ数
cat /sys/kernel/mm/ksm/pages_sharing    # 共有によって節約されているページ数
cat /sys/kernel/mm/ksm/pages_unshared   # ユニークだが共有候補のページ数
cat /sys/kernel/mm/ksm/pages_volatile   # 変更が頻繁でKSMが追いつかないページ数

# メモリ節約量の計算
saved_kb=$(( $(cat /sys/kernel/mm/ksm/pages_sharing) * 4 ))
echo "KSM saved: ${saved_kb} KB"

# QEMU/KVM でのKSM使用
# virsh コマンドで確認
virsh node-memory-tune

15.3 madvise による KSM 登録

#include <sys/mman.h>

/* KSM にマージ対象として登録 */
void *addr = mmap(NULL, size, PROT_READ | PROT_WRITE,
                  MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
madvise(addr, size, MADV_MERGEABLE);

/* KSM 対象から除外 */
madvise(addr, size, MADV_UNMERGEABLE);

16. Memory cgroups

16.1 cgroups v1 メモリコントローラ

# cgroup v1 のマウント確認
mount | grep cgroup | grep memory

# cgroup v1 でのメモリ制限設定
mkdir -p /sys/fs/cgroup/memory/my_group

# メモリ上限の設定 (hard limit)
echo 1G > /sys/fs/cgroup/memory/my_group/memory.limit_in_bytes

# メモリ + スワップの上限
echo 2G > /sys/fs/cgroup/memory/my_group/memory.memsw.limit_in_bytes

# ソフトリミット (メモリ圧迫時に回収対象になる閾値)
echo 512M > /sys/fs/cgroup/memory/my_group/memory.soft_limit_in_bytes

# プロセスの追加
echo <pid> > /sys/fs/cgroup/memory/my_group/cgroup.procs

# メモリ使用量の確認
cat /sys/fs/cgroup/memory/my_group/memory.usage_in_bytes
cat /sys/fs/cgroup/memory/my_group/memory.max_usage_in_bytes

# 詳細統計
cat /sys/fs/cgroup/memory/my_group/memory.stat
# cache 123456789
# rss 987654321
# rss_huge 12345678
# shmem 0
# mapped_file 23456789
# dirty 456789
# writeback 0
# pgpgin 123456
# pgpgout 654321
# pgfault 789012
# pgmajfault 123
# inactive_anon 234567890
# active_anon 765432100
# inactive_file 34567890
# active_file 89012345
# unevictable 0
# hierarchical_memory_limit 1073741824

# OOM制御
echo 1 > /sys/fs/cgroup/memory/my_group/memory.oom_control  # OOM無効化

16.2 cgroups v2 メモリコントローラ

# cgroup v2 のマウント確認
mount | grep cgroup2

# cgroup v2 でメモリコントローラを有効化
echo "+memory" > /sys/fs/cgroup/cgroup.subtree_control

# メモリグループの作成
mkdir /sys/fs/cgroup/my_group

# メモリ上限の設定
echo 1G > /sys/fs/cgroup/my_group/memory.max     # hard limit
echo 512M > /sys/fs/cgroup/my_group/memory.high   # soft limit (スロットリング)
echo 256M > /sys/fs/cgroup/my_group/memory.low     # best-effort 保護
echo 128M > /sys/fs/cgroup/my_group/memory.min     # 絶対保護 (回収されない)

# スワップ制限
echo 512M > /sys/fs/cgroup/my_group/memory.swap.max

# プロセスの追加
echo <pid> > /sys/fs/cgroup/my_group/cgroup.procs

# メモリ使用量の確認
cat /sys/fs/cgroup/my_group/memory.current
# 524288000

# 詳細統計
cat /sys/fs/cgroup/my_group/memory.stat
# anon 234567890
# file 123456789
# kernel 45678901
# kernel_stack 2345678
# pagetables 9012345
# sec_pagetables 0
# percpu 567890
# sock 123456
# vmalloc 0
# shmem 0
# zswap 0
# zswapped 0
# file_mapped 23456789
# file_dirty 456789
# file_writeback 0
# swapcached 0
# anon_thp 12345678
# file_thp 0
# shmem_thp 0
# inactive_anon 123456789
# active_anon 111111101
# inactive_file 67890123
# active_file 55566666
# unevictable 0
# slab_reclaimable 12345678
# slab_unreclaimable 2345678
# slab 14691356
# workingset_refault_anon 1234
# workingset_refault_file 5678
# workingset_activate_anon 234
# workingset_activate_file 567
# workingset_restore_anon 12
# workingset_restore_file 34
# workingset_nodereclaim 0
# pgscan 234567
# pgsteal 123456
# pgscan_kswapd 200000
# pgscan_direct 34567
# pgsteal_kswapd 100000
# pgsteal_direct 23456
# pgfault 789012
# pgmajfault 123
# pgrefill 45678
# pgactivate 34567
# pgdeactivate 12345
# pglazyfree 0
# pglazyfreed 0
# thp_fault_alloc 123
# thp_collapse_alloc 45

# メモリイベント
cat /sys/fs/cgroup/my_group/memory.events
# low 0
# high 1234
# max 56
# oom 2
# oom_kill 2
# oom_group_kill 0

# メモリ圧力 (PSI: Pressure Stall Information)
cat /sys/fs/cgroup/my_group/memory.pressure
# some avg10=0.00 avg60=0.00 avg300=0.12 total=123456
# full avg10=0.00 avg60=0.00 avg300=0.01 total=12345

16.3 systemd によるメモリ制限

# systemd サービスのメモリ制限
systemctl set-property my_service.service MemoryMax=1G
systemctl set-property my_service.service MemoryHigh=768M

# サービスファイルでの設定
# [Service]
# MemoryMax=1G
# MemoryHigh=768M
# MemoryLow=256M
# MemorySwapMax=512M

# 現在のメモリ使用量確認
systemctl status my_service.service
# Memory: 456.7M (max: 1.0G available: 543.3M)

# systemd-cgtop でリアルタイム監視
systemd-cgtop -m

17. /proc インターフェース

17.1 /proc/meminfo の詳細

cat /proc/meminfo
# MemTotal:       32768000 kB   ← 物理メモリ総量
# MemFree:        12345678 kB   ← 未使用の物理メモリ
# MemAvailable:   22345678 kB   ← 実質的に利用可能なメモリ(キャッシュ含む)
# Buffers:          245760 kB   ← ブロックデバイスのバッファキャッシュ
# Cached:          8765432 kB   ← ページキャッシュ(ファイルデータ)
# SwapCached:        12345 kB   ← スワップに書き出し済みだがメモリにも残るページ
# Active:         12345678 kB   ← 最近アクセスされたページ(回収されにくい)
# Inactive:        9876543 kB   ← 最近アクセスされていないページ(回収候補)
# Active(anon):    6543210 kB   ← アクティブな匿名ページ
# Inactive(anon):  2345678 kB   ← 非アクティブな匿名ページ
# Active(file):    5802468 kB   ← アクティブなファイルキャッシュ
# Inactive(file):  7530865 kB   ← 非アクティブなファイルキャッシュ
# Unevictable:        1234 kB   ← 回収不可能なページ(mlock等)
# Mlocked:             1234 kB  ← mlock() でロックされたページ
# SwapTotal:       4194304 kB   ← スワップ総量
# SwapFree:        4181959 kB   ← スワップ空き
# Dirty:             56789 kB   ← ダーティページ(ディスクに書き戻し必要)
# Writeback:             0 kB   ← 現在書き戻し中のページ
# AnonPages:       8765432 kB   ← 匿名ページ(ファイルに紐付かないメモリ)
# Mapped:          1234567 kB   ← メモリマップドファイルのサイズ
# Shmem:            234567 kB   ← 共有メモリ + tmpfs
# KReclaimable:     567890 kB   ← 回収可能なカーネルメモリ
# Slab:             890123 kB   ← スラブアロケータの使用量
# SReclaimable:     567890 kB   ← 回収可能なスラブ
# SUnreclaim:       322233 kB   ← 回収不可能なスラブ
# KernelStack:       23456 kB   ← カーネルスタック
# PageTables:       123456 kB   ← ページテーブル使用量
# NFS_Unstable:          0 kB   ← NFSの未安定ページ
# Bounce:                0 kB   ← バウンスバッファ
# WritebackTmp:          0 kB   ← FUSE書き戻しバッファ
# CommitLimit:    20578304 kB   ← コミット可能上限
# Committed_AS:   12345678 kB   ← コミット済みメモリ
# VmallocTotal:   34359738367 kB ← vmalloc 空間の総量
# VmallocUsed:      345678 kB   ← vmalloc 使用量
# VmallocChunk:          0 kB   ← vmalloc の最大連続空きブロック
# Percpu:            12345 kB   ← per-CPU データ
# AnonHugePages:    524288 kB   ← 匿名THP
# ShmemHugePages:        0 kB   ← 共有メモリTHP
# ShmemPmdMapped:        0 kB   ← PMDマッピングされた共有メモリ
# FileHugePages:         0 kB   ← ファイルバックTHP
# FilePmdMapped:         0 kB   ← PMDマッピングされたファイル
# HugePages_Total:       0      ← 明示的HugePages総数
# HugePages_Free:        0      ← 明示的HugePages空き
# HugePages_Rsvd:        0      ← 予約済みHugePages
# HugePages_Surp:        0      ← 余剰HugePages
# Hugepagesize:       2048 kB   ← HugePageサイズ
# Hugetlb:               0 kB   ← HugeTLB使用量
# DirectMap4k:      234567 kB   ← 4KB直接マッピング
# DirectMap2M:    28765432 kB   ← 2MB直接マッピング
# DirectMap1G:     4194304 kB   ← 1GB直接マッピング

17.2 /proc/vmstat の重要な指標

# ページ割り当て/解放
grep -E "pgalloc|pgfree" /proc/vmstat

# ページ回収
grep -E "pgsteal|pgscan|pgreclaim" /proc/vmstat

# ページフォルト
grep -E "pgfault|pgmajfault" /proc/vmstat

# ワークングセット
grep workingset /proc/vmstat
# workingset_nodes 12345
# workingset_refault_anon 1234
# workingset_refault_file 56789
# workingset_activate_anon 234
# workingset_activate_file 5678
# workingset_restore_anon 12
# workingset_restore_file 345
# workingset_nodereclaim 67

# NUMA統計
grep numa /proc/vmstat

# THP統計
grep thp /proc/vmstat

# コンパクション統計
grep compact /proc/vmstat

# ksm 統計
grep ksm /proc/vmstat

17.3 /proc/buddyinfo

cat /proc/buddyinfo
# 各列: order 0(4KB) 〜 order 10(4MB) の空きブロック数

# 断片化の視覚化スクリプト
awk '
{
    printf "%-8s %-8s: ", $1" "$2, $3$4;
    for (i=5; i<=NF; i++) {
        blocks = $i;
        printf "%6d ", blocks;
    }
    printf "\n";
}
' /proc/buddyinfo

17.4 /proc/pagetypeinfo

cat /proc/pagetypeinfo
# Page block order: 9
# Pages per block:  512
#
# Free pages count per migrate type at order   0   1   2   3   4   5 ...
# Node    0, zone      DMA, type    Unmovable   1   0   0   1   0   0 ...
# Node    0, zone      DMA, type      Movable   0   0   0   0   2   1 ...
# Node    0, zone      DMA, type  Reclaimable   0   0   0   0   0   0 ...

18. メモリ監視ツール

18.1 free コマンド

free -h
#                total    used    free  shared  buff/cache   available
# Mem:            31Gi    8.2Gi   12Gi   256Mi       11Gi       22Gi
# Swap:           4.0Gi   256Mi   3.8Gi

# total     = 物理メモリ総量
# used      = 使用中のメモリ (total - free - buffers - cache)
# free      = 未使用のメモリ
# shared    = 共有メモリ (tmpfs含む)
# buff/cache = バッファ + キャッシュ
# available = 新しいアプリケーションが使用可能な推定メモリ量
#             (free + 回収可能なキャッシュ/バッファ)

# 定期的な監視
free -h -s 5  # 5秒ごとに表示

# ワンライナーでの変化率計算
watch -d -n 1 free -h

18.2 vmstat コマンド

vmstat 1 10  # 1秒間隔で10回
# procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
#  r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
#  1  0  51200 1234567 45672 8765432   0    0    12    45  234  567  5  2 93  0  0
#  0  0  51200 1234123 45680 8765876   0    0     0    64  189  423  3  1 96  0  0

# r:  実行可能プロセス数
# b:  ブロック中プロセス数
# swpd: スワップ使用量
# free: 空きメモリ
# buff: バッファ
# cache: キャッシュ
# si: スワップイン (KB/s)
# so: スワップアウト (KB/s)
# bi: ブロックイン (blocks/s)
# bo: ブロックアウト (blocks/s)
# in: 割り込み数
# cs: コンテキストスイッチ数

# 詳細モード
vmstat -s
#      32768000 K total memory
#       8765432 K used memory
#      12345678 K active memory
#       9876543 K inactive memory
#      12345678 K free memory
#        245760 K buffer memory
#       8765432 K swap cache
#       4194304 K total swap
#         51200 K used swap
#       4143104 K free swap
#     123456789 non-nice user cpu ticks
#        123456 nice user cpu ticks
#      23456789 system cpu ticks
#    9876543210 idle cpu ticks
#       1234567 IO-wait cpu ticks

18.3 slabtop コマンド

# スラブキャッシュの使用状況
slabtop -o -s c | head -30
# オプション:
# -o: ワンショット(非インタラクティブ)
# -s c: キャッシュサイズでソート
# -s a: アクティブオブジェクト数でソート
# -s n: 名前でソート

# slabinfo からのカスタム分析
awk 'NR>2 {printf "%-30s %10d %10d %10d\n", $1, $2, $3, $4}' /proc/slabinfo | sort -k4 -rn | head -20

18.4 numastat コマンド

# NUMA 統計
numastat
numastat -c  # コンパクト表示
numastat -m  # /proc/meminfo 形式
numastat -p <pid>  # プロセス毎

# メモリ使用量をMB単位で表示
numastat -m
# Per-node system memory usage (in MBs):
#                    Node 0    Node 1    Total
# MemTotal          65536.00  65536.00 131072.00
# MemFree           32768.00  30720.00  63488.00
# MemUsed           32768.00  34816.00  67584.00
# Active            12345.00  11234.00  23579.00
# ...

18.5 smem コマンド

# smem: プロセスのメモリ使用量を詳細表示
smem -r -s rss | head -20
#   PID User     Command                         Swap      USS      PSS      RSS
# 12345 mysql    /usr/sbin/mysqld                   0  4567890  4578901  4612345
#  6789 root     /usr/bin/java -server ...          0  2345678  2356789  2390123
#  2345 www-data /usr/sbin/apache2                  0   123456   145678   234567

# USS: Unique Set Size (プロセス固有のメモリ)
# PSS: Proportional Set Size (共有メモリを按分)
# RSS: Resident Set Size (物理メモリ上のサイズ)

# システム全体のサマリ
smem -w
# Area                           Used      Cache   Noncache
# firmware/hardware                  0          0          0
# kernel image                       0          0          0
# kernel dynamic memory        1234567     567890     666677
# userspace memory             8765432    4321098    4444334
# free memory                 12345678   12345678          0

# パイチャート出力
smem --pie name -s rss

18.6 perf によるメモリイベント分析

# メモリ関連のパフォーマンスカウンタ
perf stat -e cache-references,cache-misses,\
dTLB-loads,dTLB-load-misses,\
dTLB-stores,dTLB-store-misses,\
page-faults,major-faults,minor-faults \
-- ./my_application

# メモリアクセスのサンプリング
perf record -e mem_load_retired.l3_miss -- ./my_application
perf report

# NUMA関連イベント
perf stat -e node-loads,node-load-misses,\
node-stores,node-store-misses \
-- ./my_application

18.7 /proc/pressure/memory (PSI)

# PSI (Pressure Stall Information) でメモリ圧力を監視
cat /proc/pressure/memory
# some avg10=0.00 avg60=0.12 avg300=0.34 total=1234567
# full avg10=0.00 avg60=0.01 avg300=0.02 total=123456

# some: 少なくとも1つのタスクがメモリ不足でストールしている時間の割合
# full: すべてのタスクがメモリ不足でストールしている時間の割合
# avg10/60/300: 直近10秒/60秒/300秒の平均値 (%)

# PSIベースのメモリ監視スクリプト
while true; do
    some=$(awk '/^some/ {print $2}' /proc/pressure/memory | cut -d= -f2)
    if (( $(echo "$some > 10.0" | bc -l) )); then
        echo "WARNING: Memory pressure detected: some avg10=$some%"
        logger "Memory pressure: some avg10=$some%"
    fi
    sleep 5
done

19. トラブルシューティング実践

19.1 メモリリークの調査

# 方法1: /proc/<pid>/smaps で RSS の変化を監視
while true; do
    echo "$(date): $(grep -E 'Rss:' /proc/<pid>/smaps | awk '{sum+=$2} END {print sum " kB"}')"
    sleep 60
done

# 方法2: pmap コマンド
pmap -x <pid> | tail -1
#  total kB         1234567    567890    345678

# 方法3: valgrind (開発環境)
valgrind --leak-check=full --show-leak-kinds=all ./my_application

# 方法4: /proc/<pid>/smaps の詳細分析
cat /proc/<pid>/smaps | awk '
/^[0-9a-f]/ { region=$0 }
/^Rss:/ { rss+=$2 }
/^Private_Dirty:/ { pdirty+=$2 }
END { print "RSS:", rss, "kB, Private Dirty:", pdirty, "kB" }
'

19.2 OOM の調査と対策

# OOM の発生確認
dmesg | grep -i "out of memory" -A 20

# OOM 発生時のメモリ状態
dmesg | grep -E "oom|kill|Mem-Info|Normal free" -A 5

# OOM の予防的監視
cat /proc/pressure/memory
# some avg10 が高い値を示し始めたらメモリ圧迫の兆候

# 対策1: メモリ上限の適切な設定
sysctl -w vm.min_free_kbytes=131072

# 対策2: OOM スコアの調整
for pid in $(pgrep -f "重要なプロセス"); do
    echo -500 > /proc/$pid/oom_score_adj
done

# 対策3: earlyoom の導入(OOM Killer より早く対処)
# earlyoom -m 5 -s 10 --prefer "^(my_app)$" --avoid "^(sshd|systemd)$"

19.3 メモリの断片化調査

# /proc/buddyinfo で断片化レベルを確認
cat /proc/buddyinfo

# 高 order の空きブロックが少ない = 断片化が進行
# order 10 (4MB) のブロック数が 0 に近い場合は要注意

# コンパクション実行
echo 1 > /proc/sys/vm/compact_memory

# 断片化を改善するための設定
sysctl -w vm.compaction_proactiveness=40
sysctl -w vm.extfrag_threshold=300

# THP の割り当て失敗率を確認
grep -E "thp_fault_alloc|thp_fault_fallback" /proc/vmstat

19.4 NUMA 不均衡の調査

# ノード間のメモリ使用量比較
numastat -m

# リモートアクセスの割合確認
numastat | awk '/other_node/ {sum+=$2+$3} /local_node/ {local+=$2+$3}
    END {printf "Remote access ratio: %.2f%%\n", sum/(sum+local)*100}'

# プロセスのNUMA分布確認
numastat -p $(pidof my_application)

# 対策: プロセスのNUMA配置を最適化
numactl --cpunodebind=0 --membind=0 ./my_application

20. まとめとベストプラクティス

20.1 メモリ設定のチェックリスト

#!/bin/bash
# メモリ設定確認スクリプト

echo "=== メモリ基本情報 ==="
free -h
echo ""

echo "=== スワップ設定 ==="
swapon --show
echo "swappiness: $(cat /proc/sys/vm/swappiness)"
echo ""

echo "=== THP 設定 ==="
echo "enabled: $(cat /sys/kernel/mm/transparent_hugepage/enabled)"
echo "defrag: $(cat /sys/kernel/mm/transparent_hugepage/defrag)"
echo ""

echo "=== HugePages ==="
grep -i huge /proc/meminfo
echo ""

echo "=== オーバーコミット ==="
echo "overcommit_memory: $(cat /proc/sys/vm/overcommit_memory)"
echo "overcommit_ratio: $(cat /proc/sys/vm/overcommit_ratio)"
grep -E "CommitLimit|Committed_AS" /proc/meminfo
echo ""

echo "=== ウォーターマーク ==="
echo "min_free_kbytes: $(cat /proc/sys/vm/min_free_kbytes)"
echo ""

echo "=== NUMA ==="
numactl --hardware 2>/dev/null || echo "numactl not available"
echo ""

echo "=== ダーティページ設定 ==="
echo "dirty_ratio: $(cat /proc/sys/vm/dirty_ratio)"
echo "dirty_background_ratio: $(cat /proc/sys/vm/dirty_background_ratio)"
echo "dirty_expire_centisecs: $(cat /proc/sys/vm/dirty_expire_centisecs)"
echo ""

echo "=== メモリ圧力 (PSI) ==="
cat /proc/pressure/memory 2>/dev/null || echo "PSI not available"
echo ""

echo "=== 断片化状況 ==="
cat /proc/buddyinfo

20.2 ワークロード別推奨設定

Web サーバ(Nginx / Apache):

sysctl -w vm.swappiness=30
sysctl -w vm.dirty_ratio=10
sysctl -w vm.dirty_background_ratio=5
echo madvise > /sys/kernel/mm/transparent_hugepage/enabled

データベースサーバ(MySQL / PostgreSQL):

sysctl -w vm.swappiness=1
sysctl -w vm.dirty_ratio=5
sysctl -w vm.dirty_background_ratio=2
echo never > /sys/kernel/mm/transparent_hugepage/enabled
# HugePages を shared_buffers に合わせて設定
sysctl -w vm.nr_hugepages=4096

Redis / Memcached:

sysctl -w vm.swappiness=0
sysctl -w vm.overcommit_memory=1
echo never > /sys/kernel/mm/transparent_hugepage/enabled
echo never > /sys/kernel/mm/transparent_hugepage/defrag

仮想化ホスト(KVM):

sysctl -w vm.swappiness=30
echo 1 > /sys/kernel/mm/ksm/run
echo always > /sys/kernel/mm/transparent_hugepage/enabled
# VM ごとに HugePages を計算して設定

科学計算 / HPC:

sysctl -w vm.swappiness=10
sysctl -w vm.overcommit_memory=1
echo always > /sys/kernel/mm/transparent_hugepage/enabled
echo always > /sys/kernel/mm/transparent_hugepage/defrag
# NUMA インターリーブを検討

20.3 監視ダッシュボードのための主要メトリクス

メトリクス取得元閾値目安
MemAvailable/proc/meminfo< 10% で警告
SwapUsed/proc/meminfo> 0 で注意
si/so (swap I/O)vmstat> 0 で注意
pgmajfault/proc/vmstat増加傾向で注意
PSI some avg60/proc/pressure/memory> 5% で警告
PSI full avg60/proc/pressure/memory> 1% で警告
OOM kill countdmesg / memory.events> 0 で緊急
Slab unreclaimable/proc/meminfo増加傾向で注意
PageTables/proc/meminfo急増で注意
buddyinfo order 3+/proc/buddyinfo減少で断片化

20.4 参考資料

  • Linux Kernel Documentation: https://www.kernel.org/doc/html/latest/admin-guide/mm/
  • Understanding the Linux Virtual Memory Manager (Mel Gorman)
  • Linux カーネルソースコード: mm/ ディレクトリ
  • /proc ファイルシステムのドキュメント: Documentation/filesystems/proc.rst
  • cgroup v2 ドキュメント: Documentation/admin-guide/cgroup-v2.rst

著者注: 本ドキュメントは Linux カーネル 5.x / 6.x 系を基に記述しています。カーネルバージョンによってパラメータの名前やデフォルト値が異なる場合があります。本番環境への適用前に、必ず対象カーネルのドキュメントを確認してください。