TL

tracepointとftrace・カーネルトレーシング機構

本番カーネルを止めず、再ビルドもせずに「何が・いつ・どれだけ」を覗ける。tracepointとftraceの仕掛けを、低オーバーヘッドの理由まで含めて原理から腑に落とせます。

応用ftracetracepointkprobeeBPFLinuxカーネル最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.tracepointはカーネルに静的に埋め込まれた計測点で、無効時はnop命令同然のためほぼゼロコスト。有効化するとjump labelでコードを差し替え、登録したプローブを呼ぶ。
  • 2.ftraceは関数の入口に仕込まれたmcount/__fentry__フックを使い、関数トレースや関数グラフトレース(入退出のネスト記録)を行う。記録先はCPUごとのロックレスなリングバッファ。
  • 3.kprobeは任意のカーネル命令に動的に割り込み、uprobeはユーザー空間命令に割り込む。これらの計測点にeBPFを載せると、収集と集計をカーネル内で完結できる。

動いているカーネルを「観測」するという課題

本番のカーネルで「どの関数が・何回・どれだけ時間がかかって呼ばれたか」を知りたい。だが printf を足して再ビルド するわけにはいかず、再起動も論外です。求められるのは、動いているカーネルに後付けで計測点を仕込み、必要なときだけ有効化し、無効時のコストを限りなくゼロに近づける仕組み。Linux ではこれを tracepointftracekprobe/uprobe という層で実現しています。本稿はその構造を下から積み上げます。

tracepoint:静的計測点とjump labelの妙

tracepoint は、カーネルのソースに開発者があらかじめ埋め込んだ名前付きの計測点です(例 sched_switchsys_enter)。重要なのは、無効時のオーバーヘッドがほぼゼロである点。素朴に書けば各計測点で if (有効?) probe() の分岐が要りますが、ホットパスにこの分岐が常駐するのは避けたい。

そこで使われるのが static key(jump label) です。tracepoint が無効な間、その場所には実質 nop(無操作)命令だけが置かれます。tracepoint を有効化すると、カーネルはコードを実行時に書き換え、その nop を「登録済みプローブ呼び出しへのジャンプ」に差し替えます。分岐予測ミスすら避けられ、無効時は本当に何もしていないに等しい——これが「常時埋め込んでも害がない」を成立させる核心です。

tracepointがABIとして安定する理由

tracepoint はカーネルが意図的に置いた計測点で、その名前と引数フィールドは安定が約束されます。だから長期運用の監視に向きます。対して後述の kprobe は任意の関数に動的に刺せる反面、関数名も引数もカーネル内部実装でありバージョン間で変わり得る——安定なら tracepoint、柔軟なら kprobe という使い分けの根拠がここにあります。

各 tracepoint には複数のプローブを登録でき、登録リストは RCU 的に管理されるため、計測点を通る側はロックを取らずにリストを辿れます。ftrace も perf も eBPF も、最終的にはこの tracepoint に自分のプローブを括り付けて発火を受け取ります。

ftrace:関数フックと関数グラフトレース

ftrace(function tracer) は、カーネル関数の呼び出しそのものを追う機構です。鍵は、コンパイル時に全カーネル関数の入口へ計測用フックを仕込んでおくこと。gcc -pg 系の仕組みで各関数の先頭に mcount(新しくは __fentry__)呼び出しが置かれます。

ただし全関数で常にフックを呼ぶと重すぎます。そこで ftrace も実行時コード書き換えを使い、起動時にこれらのフック呼び出しを nop に潰しておき、トレースを有効化した関数だけ nop をフック呼び出しへ戻します。動的に「どの関数を追うか」を set_ftrace_filter で絞れるのはこのためです。

ftrace の代表的なモードを整理します。

トレーサ記録する内容主な用途
function有効化した関数の入口を順に記録呼び出しの流れの俯瞰
function_graph関数の入口と出口をネスト表示し各関数の実行時間を算出遅延の内訳・コールツリー解析
irqsoff / preemptoff割り込み/プリエンプト禁止の最長区間を追跡レイテンシ要因の特定

特に強力なのが function_graph です。__fentry__ で入口を捉えるだけでなく、関数の戻りアドレスを退避して自前のトランポリンに差し替えることで出口も捕捉します。これにより呼び出しのネスト構造(どの関数がどれを呼んだか)と、各関数が消費した時間が C 言語のインデント風に得られます。

 3) 0.213 us  |  mutex_lock();
 3)           |  ext4_file_write_iter() {
 3) 1.024 us  |    ext4_buffered_write_iter();
 3) 2.115 us  |  }

行頭の 3 は記録した CPU、左の数値が実行時間です。出口の時刻から入口の時刻を引くだけで関数単位の所要時間が出る——これが遅延解析で重宝される理由です。

リングバッファ:低オーバーヘッド記録の心臓部

トレースは秒間に何十万イベントも生みます。これをロック付きの共有バッファに書けば、そのロック競合自体が観測対象を乱す(観測者効果)。ftrace の答えは CPU ごとに独立したリングバッファを持つことです。

各 CPU は自分専用のバッファに、他 CPU とのロックなしで書き込みます。書き込みは「現在の書き込み位置を進めて確保し、そこへ詰める」だけで、確保はアトミックな compare-and-swap(CAS 系操作)で行うため割り込みやプリエンプトに対しても安全です。バッファは固定サイズのページを環状に連ね、満杯になると最古のイベントを上書き(オーバーライトモード)するか、新規を捨てます。これにより、記録のために動的メモリ確保もグローバルロックも要らず、ホットパスでも数十ナノ秒級で済みます。

トレースは「軽い」が「無料」ではない

リングバッファは極めて軽量ですが、function トレーサで全関数を追えば、フック呼び出しと記録のコストが積み上がり、計測対象の挙動(タイミングや競合)を変え得ます。広く浅く当たりを付け、set_ftrace_filter で関数を絞り、function_graph で深掘りする——観測者効果を抑える定石です。

読み出しは tracefs(通常 /sys/kernel/tracing/)経由で、trace(整形済みテキスト)や trace_pipe(消費型ストリーム)から取り出します。CPU ごとのバッファは読み出し時に時刻順へマージされます。

kprobe・uprobe:動的に刺す計測点

tracepoint と ftrace フックは事前に用意された地点でしたが、任意の命令アドレスに後から割り込みたいことがあります。これを担うのが kprobe です。

kprobe は、対象命令の先頭バイトをブレークポイント命令(x86 なら int3/0xCC)に書き換えます。CPU がそこに来ると例外でカーネルへ落ち、kprobe のハンドラが呼ばれます。ハンドラ実行後は、退避しておいた元命令を単一ステップ実行して何事もなかったように復帰します。この「ブレークポイント命令への置換と単一ステップ」という土台は、ptrace によるデバッガのブレークポイント と原理的に同じものです。関数の戻りを捕らえる kretprobe は、入口で戻りアドレスを横取りしてトランポリンに差し替える点で function_graph と発想を共有します。

uprobe はこれをユーザー空間に広げたもので、ユーザープロセスの命令にブレークポイントを仕掛けます。対象ページはコピーオンライトで分離され、プロセスがその命令に達するとカーネルへ落ちてハンドラが走ります。アプリのライブラリ関数を、再コンパイルもアタッチ型デバッガもなしに観測できます。

kprobeを置けない場所

kprobe は割り込み禁止区間や例外処理の最深部など、安全に割り込めない箇所には置けません(ブラックリスト管理)。また命令境界を誤って書き換えれば破滅的なので、kprobe は命令デコードを行い境界を保証します。「任意の命令に刺せる」自由には、こうした安全装置が裏で働いています。

eBPF との連携:収集と集計をカーネル内で完結する

ここまでの計測点(tracepoint・kprobe・uprobe・ftrace フック)は、いずれも「発火したらハンドラを呼ぶ」仕組みでした。そのハンドラとして eBPF プログラムを載せられるのが、現代トレーシングの転換点です。

従来は「生イベントをリングバッファへ吐き、ユーザー空間で集計」が基本でした。eBPF を挟むと、発火地点でその場で集計できます。例えば「システムコールごとの遅延ヒストグラム」を、各 システムコール の入口/出口の tracepoint に eBPF を付けて差分を取り、eBPF マップ上のヒストグラムを直接更新する——ユーザー空間へ流すのは生イベントの洪水ではなく、集計済みの結果だけになります。bpftrace や bcc のツール群はこの形で実装されています。

試験・面接での要点整理

「なぜ tracepoint は常時埋め込んでも軽いのか」に static key/jump label による nop 差し替えで即答できるかが分かれ目。tracepoint=静的・ABI安定、kprobe/uprobe=動的・任意アドレス(int3 置換+単一ステップ)ftrace=mcount/fentry フック、function_graph で入退出と実行時間記録は per-CPU ロックレスリングバッファeBPF を載せるとカーネル内で集計、という対応関係を押さえれば一通り答えられます。

まとめ

まとめ

Linux のカーネルトレーシングは層構造です。tracepoint は静的計測点で、static key により無効時は nop 同然・有効時にコードを書き換えてプローブを呼びます。ftracemcount/__fentry__ フックで関数を追い、function_graph が入退出と実行時間を与えます。記録は CPU ごとのロックレスなリングバッファが担い、観測者効果を最小化。kprobe/uprobeブレークポイント命令への置換と単一ステップ で任意のカーネル/ユーザー命令へ動的に割り込みます。そしてこれらの計測点に eBPF を載せれば、収集から集計までをカーネル内で完結でき、本番を止めずに深く観測できる——再ビルドも再起動も要らない可観測性の土台が、この機構です。

OS Article

tracepointとftrace・カーネルトレーシング機構を実務で読む

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

解決すること

ftrace

比較で見る軸

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

導入後に効く点

ftraceは関数の入口に仕込まれたmcount/__fentry__フックを使い、関数トレースや関数グラフトレース(入退出のネスト記録)を行う。記録先はCPUごとのロックレスなリングバッファ。

先に潰すリスク

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

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

判断チェックリスト

  • 自社の用途が「ftrace / tracepoint」に近いか確認する。
  • 強みである「tracepointはカーネルに静的に埋め込まれた計測点で、無効時はnop命令同然のためほぼゼロコスト。有効化するとjump labelでコードを差し替え、登録したプローブを呼ぶ。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

ftracetracepointkprobeeBPFLinuxftracetracepointkprobe
参考: 公式情報