---
title: Slab と kmalloc
url: https://doc.liz6.com/ja/linux-kernel/02-memory-management/04-slab-and-kmalloc
locale: ja
area: linux-kernel
tags:
- linux-kernel
- memory-management
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: SLUB アロケータ → kmem_cache → kmalloc/kfree → スラブマージ → デバッグ (KASAN/SLUB_DEBUG) → SLOB の廃止 (6.4) カーネルバージョン: 2.6 ~ 6.x'
---

# Slab と kmalloc

> カバー範囲: SLUB アロケータ → kmem_cache → kmalloc/kfree → スラブマージ → デバッグ (KASAN/SLUB_DEBUG) → SLOB の廃止 (6.4)
> カーネルバージョン: 2.6 ~ 6.x

## 概要

Buddy アロケータの最小割り当て単位は 1ページ（4KB）です。しかし、カーネル内のほとんどの割り当ては 4KB よりもはるかに小さく、`task_struct`（約 4KB）、`inode`（約 600B）、`dentry`（約 200B）などです。もし小さなオブジェクトごとに 1ページ全体を占有させると、その無駄は極めて深刻なものになります。

**SLUB アロケータ**はこの問題を解決します：1ページ（または連続する数ページ）を同じサイズの小さなオブジェクトに分割し、freelist（空きリスト）を使って空きオブジェクトと割り当て済みオブジェクトを管理します。SLUB は現在のデフォルトのスラブ実装であり（6.4 で古い SLOB が削除されました）、SLAB (Solaris → Linux 2.2) から派生し、SLUB (Linux 2.6.23, C. Lameter) として実装されました。

---

## SLUB アーキテクチャ

### kmem_cache

```c
// include/linux/slub_def.h
struct kmem_cache {
    unsigned int        size;           // 各オブジェクトの実際のサイズ (アライメント含む)
    unsigned int        object_size;    // 元のオブジェクトサイズ (メタデータを除く)
    unsigned int        offset;         // 次の空きオブジェクトへのポインタオフセット
    unsigned int        cpu_partial;    // per-CPU 部分的スラブの数

    const char          *name;          // /proc/slabinfo で表示される名前

    // コンストラクタ (あまり使われない。Linux ではゼロ初期化 + 手動初期化を好む)
    void (*ctor)(void *);

    struct kmem_cache_node *node[MAX_NUMNODES];  // per-NUMA ノード
    struct kmem_cache_cpu __percpu *cpu_slab;    // per-CPU ホットスラブ (ロックレス)
};
```

### Per-CPU ホットスラブ

```mermaid
flowchart TD
    START["SLUB 割り当て/解放リクエスト"]

    START --> OP{"操作タイプ?"}

    OP -->|"割り当て"| ALLOC["freelist ヘッドからオブジェクトを取得<br/>(アトミック: this_cpu_cmpxchg)"]
    ALLOC --> FULL{"現在のスラブ<br/>がいっぱい?"}
    FULL -->|"いいえ"| FAST_ALLOC["✅ オブジェクトを返す<br/>(ロックレス、極めて高速)"]
    FULL -->|"はい"| PART{"部分的な<br/>スラブがある?"}
    PART -->|"はい"| SWITCH["次の部分的スラブに切り替え"]
    SWITCH --> FAST_ALLOC
    PART -->|"いいえ"| BUDDY_ALLOC["buddy から新しいスラブを取得"]
    BUDDY_ALLOC --> FAST_ALLOC

    OP -->|"解放"| FREE["オブジェクトを freelist ヘッドに戻す"]
    FREE --> EMPTY{"スラブが完全に空になった?"}
    EMPTY -->|"はい"| RETURN["buddy に返還される可能性あり"]
    EMPTY -->|"いいえ"| FAST_FREE["✅ 解放完了<br/>(ロックレス、極めて高速)"]

    classDef fast fill:#e8f5e9,stroke:#2e7d32
    classDef slow fill:#ffebee,stroke:#c62828
    classDef decision fill:#fff3e0,stroke:#ef6c00
    class START,ALLOC,FREE fast
    class FULL,PART,EMPTY decision
    class SWITCH,BUDDY_ALLOC,RETURN slow
    class FAST_ALLOC,FAST_FREE fast
```

### 割り当てと解放パス

```c
// mm/slub.c

// 割り当て (高速パス → 低速パス):
kmem_cache_alloc(s, flags)
  → slab_alloc() → slab_alloc_node()
    ├─ fast path: cpu_slab の freelist から取得
    │     object = this_cpu_read(s->cpu_slab->freelist);
    │     if (likely(object))
    │         // freelist ポインタを更新 (cmpxchg)
    │         return object;
    │
    └─ slow path: __slab_alloc()
        ├─ CPU partial リスト内のスラブを試す
        ├─ ノード partial リスト内のスラブを試す
        ├─ どちらも失敗 → new_slab() → allocate_slab()
        │     → alloc_pages(GFP_KERNEL, order) → buddy アロケータ
        │     → freelist を構築
        └─ 最初のオブジェクトを返す

// 解放 (高速パス):
kmem_cache_free(s, object)
  → slab_free()
    ├─ fast path: オブジェクトを cpu_slab の freelist に戻す
    │     this_cpu_cmpxchg(s->cpu_slab->freelist, old, object);
    │
    └─ slow path: __slab_free()
        ├─ スラブが完全に空き状態になったか? → per-node partial が少ない場合 → partial に変換
        │                → そうでない場合 free_slab() → buddy アロケータに解放
        └─ ノード統計を更新
```

### kmalloc: 汎用割り当てインターフェース

```c
// include/linux/slab.h
// kmalloc は実際には事前に定義された kmem_cache のラッパー

// カーネル内で事前に定義された汎用キャッシュ:
// kmalloc-8, kmalloc-16, kmalloc-32, kmalloc-64,
// kmalloc-96, kmalloc-128, kmalloc-192, kmalloc-256,
// kmalloc-512, kmalloc-1k, kmalloc-2k, kmalloc-4k, kmalloc-8k

// kmalloc(size, flags):
//   1. size に応じて対応する kmem_cache を検索 (kmalloc_caches テーブル経由)
//   2. kmem_cache_alloc(s, flags) を呼び出す
//   3. オブジェクトポインタを返す

// kfree(ptr):
//   1. オブジェクトのメタデータから、それがどの kmem_cache に属しているかを見つける
//      → page->slab_cache (struct page 内のフィールド) を介して
//   2. kmem_cache_free(cache, ptr) を呼び出す
```

### スラブマージ

```c
// mm/slab_common.c
// メモリを節約するために、サイズが同じまたは近い kmem_cache はマージできる:
//   "dentry" (256B) と "inode_cache" (592B)
//   → どちらも kmalloc-512 から割り当てられる? → マージしない: サイズが異なる
//   → しかし、2つのキャッシュの object_size が近い場合 (どちらも 256B に近い)
//      → 基盤となる同じスラブを共有する可能性がある
//
// /sys/kernel/slab/ 以下で、どのキャッシュがマージされているかを確認できる
```

---

## SLOB の廃止 (6.4)

```
歴史的に Linux には 3 つのスラブ実装があった:
  SLAB: 初期実装 (Solaris 移植 → Linux 2.2)、最も複雑
  SLUB: 簡略化された実装 (2.6.23)、現在のデフォルト
  SLOB: 極小実装、組み込みシステム向け (< 16MB RAM)

6.4 で SLOB が削除された:
  - SLUB は長年の最適化により、コードサイズが十分に小さくなった
  - 組み込みシステムでは SLUB を使用可能 (CONFIG_SLUB_TINY)
  - 3つのアロケータを維持する保守負荷が大きすぎた

SLOB の設計思想は注目すべき点がある:
  - per-CPU スラブがない
  - すべての割り当ては単一の空きリストから行われる
  - first-fit 割り当て (十分な大きさの最初のブロックを見つける)
  - 極めてシンプルだが、断片化とパフォーマンスの面で劣る
```

---

## kmemleak: メモリリーク検出

```c
// mm/kmemleak.c
// ユーザー空間の valgrind の memcheck に類似
// 定期的に割り当て済みのすべてのカーネルオブジェクトをスキャンし、参照のないものを見つけてリークを報告

// 有効化:
//   CONFIG_DEBUG_KMEMLEAK=y
//   ブート引数: kmemleak=on

// 手動トリガー:
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak  # リークレポート
```

---

## KASAN: バッファオーバーラン/UAF 検出

```c
// mm/kasan/
// Kernel Address Sanitizer — コンパイル時のインストルメンテーション
// スラブの境界外アクセス、use-after-free、ダブルフリーを検出

// 3つのモード:
//   Generic KASAN: シャドウメモリ (8バイトごとに 1バイトでマーキング) → メモリオーバーヘッド 1/8
//   Software Tag-Based KASAN: ARM64 で TBI (Top Byte Ignore) を使用 → 低オーバーヘッド
//   Hardware Tag-Based KASAN: ARM64 MTE → ハードウェアチェック → 本番環境向け
```

---

## デバッグと観測

```bash
# スラブの使用状況
cat /proc/slabinfo
# 列: name, active_objs, num_objs, objsize, objperslab, pagesperslab

# より人間 readable なバージョン
slabtop -o       # アクティブオブジェクト数でソート
slabtop -s c     # キャッシュサイズでソート

# 特定キャッシュの詳細情報
cat /sys/kernel/slab/dentry/order       # スラブのオーダ
cat /sys/kernel/slab/dentry/object_size # オブジェクトサイズ
cat /sys/kernel/slab/dentry/objects     # 割り当て済みオブジェクト数
cat /sys/kernel/slab/dentry/slab_size   # 各スラブのサイズ

# 最も多くのスラブを使用しているものを確認 (kmem trace)
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 | head -100

# メモリリーク検出
echo scan > /sys/kernel/debug/kmemleak
cat /sys/kernel/debug/kmemleak | head -50
```

---

## 参考と拡張

- **カーネルドキュメント**: `Documentation/mm/slub.rst`, `Documentation/dev-tools/kmemleak.rst`, `Documentation/dev-tools/kasan.rst`
- **LWN**: 
  - "The SLUB allocator" (lwn.net/Articles/229984/)
  - "Removing SLOB" (lwn.net/Articles/967178/)
- **ソースファイル**:
  - `mm/slub.c` — SLUB 実装
  - `mm/slab_common.c` — スラブマージ、sysfs インターフェース
  - `include/linux/slub_def.h` — struct kmem_cache
  - `mm/kmemleak.c` — リーク検出
  - `mm/kasan/` — KASAN

---

*キーワード: SLUB, kmem_cache, kmalloc, per-CPU スラブ, freelist, スラブマージ, SLOB 廃止, kmemleak, KASAN*
