TL

スラブアロケータの内部(SLUB)詳解

kmallocがロックなしで一瞬に返る仕組みが、per-CPUキャッシュとフリーリストの構造から腑に落ちます。partial/full slabの管理、フリーポインタ難読化、SLAB/SLOBとの設計差までキャッシュ効率の観点で解剖します。

応用SLUBスラブアロケータLinuxカーネルメモリアロケータキャッシュ効率最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.SLUBはCPUごとに「現用slab(kmem_cache_cpu)」を持ち、その先頭オブジェクトをロックなしのcmpxchgで付け替えるだけで確保・解放を済ませる。これが高速パスの正体。
  • 2.slabはfull/partial/freeの状態でリスト管理され、未使用スロットを残すpartial slabをper-CPU/per-nodeで再利用してメモリ密度とキャッシュ局所性を稼ぐ。
  • 3.空きスロットの連結はオブジェクト内に埋めたフリーリストポインタで表現し、近年はそのポインタをアドレスと乱数でXOR難読化してヒープ攻撃を緩和する。

SLUBが解く問題──小オブジェクトを速く密に配る

カーネルは task_structinodedentry のような 固定サイズの小さな構造体 を秒間に何百万回も作っては壊します。これを物理ページ単位のアロケータ(buddy system)で扱うと、数百バイトのために 4KB を丸ごと消費し、確保のたびにグローバルなロックを取ることになって、メニーコアでまるでスケールしません。階層全体の地図は メモリアロケータの内部(buddy systemとslab) を参照してください。

スラブアロケータは、buddy から取った数ページを slab とし、そこへ 同じ型・同じサイズのオブジェクトをびっしり敷き詰めて 再利用します。Linux には歴史的に SLAB・SLUB・SLOB の3実装がありましたが、現在の標準は SLUB です。SLUB の設計目標は明快で、高速パスをロックなしにする・メタデータを薄くする・断片化を抑える の3点に尽きます。

オブジェクトを連ねるフリーリスト──slabの内部レイアウト

1つの slab は、kmem_cache が定めたサイズのスロットで区切られています。空きスロットの管理に SLUB が使うのは オブジェクト内埋め込み型のフリーリスト です。空きオブジェクトの中(既定ではオブジェクト先頭)に「次の空きオブジェクトへのポインタ」を直接書き込み、空きスロットを単方向リストとして連ねます。

slab(buddy から取った連続ページ)
┌─────────┬─────────┬─────────┬─────────┐
│ obj #0  │ obj #1  │ obj #2  │ obj #3  │
│ (使用中)│ next→#3 │ (使用中)│ next→NULL│
└─────────┴─────────┴─────────┴─────────┘
            freelist 先頭 = #1 → #3 → NULL

この設計が効くのは、空きスロットの管理に別途ビットマップや外部配列を要らない からです。空き領域そのものがリンク情報を兼ねるため、メタデータがほぼゼロで済みます。slab 全体のメタ情報(使用中オブジェクト数 inuse、フリーリスト先頭など)は専用構造体ではなく struct slab(かつての struct page の一部)側に寄せられており、これが「SLUB はメタデータが薄い」と言われる理由です。

確保=リスト先頭を外す、解放=リスト先頭に挿す

オブジェクト確保は freelist の先頭ポインタを1つ進めて先頭スロットを払い出すだけ、解放は返ってきたスロットを freelist 先頭につなぎ直すだけです。どちらも本質は 単方向リストの push/pop であり、コストはポインタ付け替え数命令に収まります。slab を型で固定したからこそ、サイズ管理を一切持たずにここまで単純化できます。

per-CPUキャッシュ──ロックなし高速パスの正体

SLUB 最大の武器が per-CPU キャッシュ(kmem_cache_cpu です。各 CPU は「いま自分が払い出しに使っている現用 slab」を1枚抱え、その freelist 先頭ポインタを CPU ローカルに保持します。確保要求が来たら、まずこの CPU 専用 freelist の先頭 を見ます。

/* 高速パスの概念図(実コードを簡略化) */
void *object = c->freelist;          /* 現用 slab の空き先頭 */
if (likely(object)) {
    c->freelist = get_freepointer(s, object);  /* 1つ進める */
    return object;                   /* ロックなしで即返却 */
}
/* freelist が空なら低速パスへ(partial slab 補充や buddy 要求) */

ここで重要なのは、この付け替えが 他 CPU と共有するロックを一切取らない ことです。CPU ローカルのデータだけを触り、整合性は this_cpu_cmpxchg(cmpxchg_double) という単一命令のアトミック操作で守ります。割り込みやプリエンプションで横入りされても、cmpxchg が失敗すればやり直すだけなので、スピンロックのようなコア間のキャッシュライン奪い合いが起きません。なぜアトミック操作の競合がスケールを殺すかは メニーコアとNUMAメモリ局所性キャッシュコヒーレンシ(MESIプロトコル) が背景になります。

ロックがないのではなく「共有しない」から速い

高速パスが速い理由は、ロックを巧妙に最適化したからではなく、触るデータがその CPU だけのもの だからです。共有しなければ競合は原理的に発生せず、各コアは自分の L1/L2 キャッシュに乗ったローカル freelist だけで確保・解放を完結できます。コアを足すほど性能が伸びるのはこのためです。

partial slabとfull slab──未使用スロットを使い切る

現用 slab を使い切ると(CPU freelist が空になると)、SLUB は次の slab を探します。ここで slab は使用状況によって3状態に分かれます。

状態意味リスト管理
full全スロットが使用中。空きがないどのリストにも積極的には載せない(追跡コスト削減)
partial一部使用中・一部空き。最も価値が高いper-CPU partial と per-node partial で再利用待ち
free(empty)全スロットが空き1枚はキャッシュ、余剰は buddy へ返却

肝は partial slab です。空きスロットが残っている slab を捨てずに取っておけば、次の確保で新しいページを buddy から取らずに済み、メモリ密度が上がります。SLUB はこの partial slab を二段で持ちます。まず per-CPU partial リスト(ロックなしで触れる近場の在庫)、それでも足りなければ per-node partial リスト(同じ NUMA ノードの共有在庫、こちらはノードロックを取る)から補充します。

確保の経路(速い順)
  ① CPU 現用 slab の freelist     ← ロックなし・最速
  ② per-CPU partial の slab を昇格  ← ロックなし
  ③ per-node partial から借りる     ← node ロック
  ④ buddy から新規 slab を確保      ← 最も高コスト

full slab を能動的にリスト管理しないのは、空きのない slab を追跡しても得がない からです。あるオブジェクトが解放されて full → partial に変わった瞬間に partial リストへ戻せばよく、full のまま抱えている間の管理コストをゼロにできます。NUMA ノードごとに partial を分ける理由は、別ノードのメモリを掴むとアクセスが遅くなるためで、ここでも局所性が設計を貫いています。

フリーリストポインタの難読化──ヒープ攻撃への防壁

フリーリストをオブジェクト内に埋め込む設計には弱点があります。解放済みチャンクの中に生ポインタが平文で置かれている ため、ヒープオーバーフローや use-after-free でこのポインタを書き換えられると、攻撃者が 任意アドレスを「次の空きスロット」に偽装 し、確保時にそこを払い出させてカーネル領域を乗っ取れてしまいます。

これに対し近年のカーネルは(CONFIG_SLAB_FREELIST_HARDENED)、フリーリストポインタを そのまま保存せず難読化 します。格納する値は概念的に次の形です。

保存値 = real_next_ptr  XOR  per_cache_random  XOR  &freeptr_location
  • per_cache_randomkmem_cache ごとにブート時に決まる秘密の乱数。
  • &freeptr_location:そのポインタを格納しているアドレス自身。

読み出すときは同じ3値で XOR を打ち消して本来のポインタを復元します。格納アドレス自身を混ぜるのが巧妙で、攻撃者が別の場所からコピーしてきた値はアドレスがずれて復元に失敗し、ポインタの「置き場所」まで一致しないと有効な next にならない ようになります。

難読化は暗号ではなく「コスト引き上げ」

この XOR 難読化は秘密の乱数を知らない攻撃者がフリーリストポインタを意図通り改竄するのを著しく困難にしますが、暗号学的な保証ではありません。乱数が漏れたり、別経路でリークが起きれば破られ得ます。あくまで 典型的なヒープエクスプロイトの難易度を大幅に上げる緩和策 であり、CONFIG_SLAB_FREELIST_RANDOM(slab 内のフリーリスト初期順序をランダム化し、確保アドレスの予測を崩す)と組み合わせて多層防御を成します。

SLAB・SLOBとの設計差──キャッシュ効率の観点で

同じ「型固定オブジェクトの再利用」を狙う3実装ですが、メタデータの置き方とキャッシュ効率で思想が分かれます。

観点SLAB(旧来)SLUB(現標準)SLOB(廃止済)
メタデータslab ごとに専用構造体と複数のキューstruct slab に寄せて最小化ほぼ持たない
キャッシュ構造per-CPU/共有/エイリアンの多段キューper-CPU 現用 slab + partial の二段単一のフリーリスト連結
並行性キューごとのロックで複雑高速パスはロックなし cmpxchgグローバルロック
狙いデバッグ情報と局所性を厚く簡素・スケーラブル・密最小フットプリント優先

SLAB は per-CPU キューに加えて NUMA 間の エイリアンキャッシュ まで持ち、局所性を厚く取りに行く代わりにメタデータが肥大し、コードもキューも複雑でした。SLUB はこのキュー多段を 「現用 slab 1枚+partial 在庫」 へ畳み、メタ情報を struct slab に寄せることで、フットプリントとキャッシュ汚染を同時に減らしました。秒間数百万回叩かれるホットパスでは、メタデータが小さいほどデータキャッシュに本来のオブジェクトが多く乗る ため、メタの薄さがそのままスループットに効きます。

SLOB は連結リスト1本の最小実装で、速度よりフットプリントを優先する組み込み向けでしたが、保守コストと性能の悪さからカーネルから削除され、現在の選択肢は実質 SLUB 一本です。ロックなし高速パスを支える同期プリミティブの全体像は カーネルのロックプリミティブ を、稼働中の slab 状況は /proc/slabinfo で観察できます。

試験・面接の要点

SLUB の核心は「per-CPU 現用 slab の freelist 先頭をロックなし cmpxchg で付け替えるのが高速パス。空になったら per-CPU partial → per-node partial → buddy の順に降りる」です。フリーリストはオブジェクト内埋め込みでメタデータを最小化し、難読化(アドレス+per-cache乱数とのXOR)で平文ポインタ書き換え攻撃を緩和する点も押さえましょう。SLAB との差は「キュー多段 vs 現用slab+partialの二段、メタデータの厚薄」で答えると整理できます。

まとめ

まとめ

SLUB は、型固定の小オブジェクトを buddy から取った slab に密に敷き詰め、空きスロットを オブジェクト内埋め込みのフリーリスト で連ねます。確保・解放は per-CPU 現用 slab の freelist 先頭をロックなし cmpxchg で付け替える だけの高速パスで済み、空になれば per-CPU partial → per-node partial → buddy の順で補充します。slab を full/partial/free の状態で管理し、未使用スロットを残す partial slab を NUMA ノード単位で再利用してメモリ密度と局所性を稼ぐのが密度の源です。フリーリストポインタは アドレスと per-cache 乱数との XOR で難読化され、平文ポインタ書き換えによるヒープ攻撃を緩和します。SLAB のキュー多段とメタデータ肥大を畳んで簡素・スケーラブルにしたのが SLUB であり、現在の Linux の標準です。階層全体は メモリアロケータの内部、ロックなしを支える土台は カーネルのロックプリミティブキャッシュコヒーレンシ(MESIプロトコル) も合わせてどうぞ。

OS Article

スラブアロケータの内部(SLUB)詳解を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

SLUB

比較で見る軸

難易度: advanced / カテゴリ: OS / タグ数: 5

導入後に効く点

slabはfull/partial/freeの状態でリスト管理され、未使用スロットを残すpartial slabをper-CPU/per-nodeで再利用してメモリ密度とキャッシュ局所性を稼ぐ。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
OS
タグ数
5

判断チェックリスト

  • 自社の用途が「SLUB / スラブアロケータ」に近いか確認する。
  • 強みである「SLUBはCPUごとに「現用slab(kmem_cache_cpu)」を持ち、その先頭オブジェクトをロックなしのcmpxchgで付け替えるだけで確保・解放を済ませる。これが高速パスの正体。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

SLUBスラブアロケータLinuxカーネルメモリアロケータキャッシュ効率SLUBスラブアロケータLinuxカーネル
参考: 公式情報