Linux Kernel Memory Management
Linux Kernel メモリ管理 - 包括的技術ガイド
対象読者: Linux カーネルのメモリ管理サブシステムを深く理解したいシステムエンジニア、SRE、カーネル開発者
カーネルバージョン: 主に Linux 5.x / 6.x 系を対象
最終更新: 2026年4月
目次
- はじめに
- 仮想メモリとページング
- ページテーブル(4段・5段ページテーブル)
- バディシステムアロケータ
- スラブアロケータ(SLUB / SLAB / SLOB)
- kmalloc / vmalloc / kzalloc
- ページキャッシュとバッファキャッシュ
- メモリゾーン(DMA / Normal / HighMem)
- NUMAメモリ管理
- Transparent Huge Pages (THP)
- HugePages(明示的)
- メモリオーバーコミットとOOM Killer
- スワップと zswap / zram
- メモリコンパクションとマイグレーション
- KSM(Kernel Same-page Merging)
- Memory cgroups(v1 / v2)
- /proc インターフェース
- メモリ監視ツール
- トラブルシューティング実践
- まとめとベストプラクティス
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]
│ │ │ │ │
│ │ │ │ ▼
│ │ │ │ 物理フレーム番号
│ │ │ │ + オフセット
│ │ │ │ = 物理アドレス
各レベルのカバー範囲:
| レベル | エントリ数 | カバー範囲 | 用途 |
|---|---|---|---|
| PGD | 512 | 512 GB | Page Global Directory |
| PUD | 512 | 1 GB | Page Upper Directory |
| PMD | 512 | 2 MB | Page Middle Directory |
| PTE | 512 | 4 KB | Page 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つのスラブアロケータ実装がある:
| 特性 | SLAB | SLUB | SLOB |
|---|---|---|---|
| 状態 | レガシー | デフォルト (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 の比較
| 特性 | kmalloc | vmalloc |
|---|---|---|
| 物理的連続性 | 連続 | 不連続(仮想的には連続) |
| 最大サイズ | 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 count | dmesg / 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 系を基に記述しています。カーネルバージョンによってパラメータの名前やデフォルト値が異なる場合があります。本番環境への適用前に、必ず対象カーネルのドキュメントを確認してください。