FreeBSDカーネル開発シリーズ
| Part1 ビルド | Part2 モジュール | Part3 ドライバ | Part4 システムコール | Part5 DTrace |
|---|---|---|---|---|
| ✅ Done | ✅ Done | ✅ Done | ✅ Done | 👈 Now |
はじめに
「カーネル内部で今何が起きてるか知りたい」
普通はprintfデバッグか、カーネルデバッガ(DDB)を使う。
でもDTraceを使えば、本番環境でもリアルタイムでカーネルを観察できる。
2005年にSunが開発した最強のトレースツール。FreeBSDは2008年から対応してる。
DTraceとは
┌─────────────────────────────────────────────────────────────┐
│ DTrace の仕組み │
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ D スクリプト │ │
│ │ syscall::open:entry { printf("%s", execname); } │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ コンパイル │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ DTrace VM (カーネル内) │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ probe1 │ │ probe2 │ │ probe3 │ ... │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ └───────────────────────────────────────────────────────┘ │
│ ↓ イベント発火 │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ カーネル / ユーザー空間 │ │
│ └───────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────┘
特徴:
- 本番環境で使える(オーバーヘッドが小さい)
- 安全(カーネルをクラッシュさせない)
- 動的(再起動不要)
- プローブが大量にある(数十万個)
セットアップ
カーネルオプション確認
# DTraceが有効か確認
sysctl kern.dtrace
# kern.dtrace.dof_maxsize: 262144
# ...
有効になってなければ、カーネル設定に追加:
options KDTRACE_HOOKS
options DDB_CTF
モジュールのロード
kldload dtraceall
# または個別に
kldload dtrace
kldload profile
kldload systrace
自動ロード設定:
echo 'dtraceall_load="YES"' >> /boot/loader.conf
基本的な使い方
プローブ一覧
# 全プローブ数
dtrace -l | wc -l
# 約 300,000 個
# syscallプローブ
dtrace -l -P syscall | head
# ID PROVIDER MODULE FUNCTION NAME
# 123 syscall read entry
# 124 syscall read return
# 125 syscall write entry
# 126 syscall write return
Hello DTrace
dtrace -n 'BEGIN { printf("Hello, DTrace!\n"); exit(0); }'
dtrace: description 'BEGIN ' matched 1 probe
CPU ID FUNCTION:NAME
0 1 :BEGIN Hello, DTrace!
システムコールのトレース
全てのopenを監視
dtrace -n 'syscall::open*:entry { printf("%s opened %s", execname, copyinstr(arg0)); }'
CPU ID FUNCTION:NAME
0 245 open:entry vim opened /etc/termcap
1 245 open:entry nginx opened /var/log/nginx/access.log
0 247 openat:entry cat opened /etc/passwd
特定プロセスだけ
# PID指定
dtrace -n 'syscall::*:entry /pid == 1234/ { printf("%s", probefunc); }'
# プロセス名指定
dtrace -n 'syscall::*:entry /execname == "nginx"/ { printf("%s", probefunc); }'
システムコールの戻り値
dtrace -n 'syscall::open*:return /arg1 == -1/ { printf("%s failed to open, errno=%d", execname, errno); }'
ファイルI/Oの分析
readのサイズ分布
dtrace -n 'syscall::read:entry { @["read size"] = quantize(arg2); }'
Ctrl+Cで終了すると:
read size
value ------------- Distribution ------------- count
16 | 0
32 |@@@@@@@@@ 158
64 |@@@@ 72
128 |@@@@@@@@@@@@@@ 254
256 |@@@@@@@ 125
512 |@@@ 48
1024 |@@ 35
2048 |@ 12
4096 | 3
ファイルごとの書き込み量
dtrace -n 'syscall::write:entry { @[fds[arg0].fi_pathname] = sum(arg2); }'
プロセスのトレース
fork/execを監視
dtrace -n 'proc:::exec-success { printf("%s -> %s", execname, curpsinfo->pr_psargs); }'
CPU ID FUNCTION:NAME
0 789 exec_common:exec-success sh -> /bin/sh -c ls
0 789 exec_common:exec-success ls -> ls --color=auto
プロセス終了
dtrace -n 'proc:::exit { printf("%s (pid=%d) exited with %d", execname, pid, arg0); }'
ネットワークのトレース
TCP接続
dtrace -n 'fbt::tcp_connect:entry { printf("connect from %s", execname); }'
パケット送受信
dtrace -n 'ip:::send { printf("send %d bytes to %s", args[2]->ip_plength, args[2]->ip_daddr); }'
dtrace -n 'ip:::receive { printf("recv %d bytes from %s", args[2]->ip_plength, args[2]->ip_saddr); }'
カーネル関数のトレース(fbt)
関数呼び出しをトレース
# VFS層の関数
dtrace -n 'fbt::vn_*:entry { printf("%s called by %s", probefunc, execname); }'
関数の実行時間
dtrace -n '
fbt::biodone:entry { self->start = timestamp; }
fbt::biodone:return /self->start/ {
@["biodone latency (ns)"] = quantize(timestamp - self->start);
self->start = 0;
}'
スタックトレース
カーネルスタック
dtrace -n 'syscall::read:entry { @[stack()] = count(); }'
kernel`sys_read+0x1a
kernel`amd64_syscall+0x3c0
kernel`fast_syscall_common+0x101
23
kernel`sys_read+0x1a
kernel`amd64_syscall+0x3c0
kernel`fast_syscall_common+0x101
nginx
42
ユーザースタック
dtrace -n 'syscall::malloc:entry { @[ustack()] = count(); }'
Dスクリプト
複雑なトレースはスクリプトファイルに書く。
vi trace_io.d
#!/usr/sbin/dtrace -s
#pragma D option quiet
syscall::read:entry
{
self->fd = arg0;
self->start = timestamp;
}
syscall::read:return
/self->start/
{
this->elapsed = timestamp - self->start;
printf("%Y %s[%d] read(%d) = %d, %d ns\n",
walltimestamp,
execname,
pid,
self->fd,
arg1,
this->elapsed);
@latency[execname] = avg(this->elapsed);
self->start = 0;
self->fd = 0;
}
END
{
printf("\n=== Average Read Latency ===\n");
printa("%s: %@d ns\n", @latency);
}
chmod +x trace_io.d
./trace_io.d
2024 Jan 01 12:00:00 nginx[1234] read(5) = 1024, 15234 ns
2024 Jan 01 12:00:00 mysql[5678] read(12) = 4096, 234567 ns
...
^C
=== Average Read Latency ===
nginx: 18234 ns
mysql: 245678 ns
プロファイリング
CPU使用率の内訳
dtrace -n 'profile-997 /arg0/ { @[stack()] = count(); }'
997Hzでサンプリングして、カーネルスタックごとにカウント。
ホットスポット検出
dtrace -n 'profile-997 { @[func(arg0)] = count(); }'
実用スクリプト集
ディスクI/Oレイテンシ
dtrace -n '
io:::start { start[arg0] = timestamp; }
io:::done /start[arg0]/ {
@["I/O latency (us)"] = quantize((timestamp - start[arg0]) / 1000);
start[arg0] = 0;
}'
ロック競合
dtrace -n '
lockstat:::adaptive-block { @[stack()] = sum(arg1); }
tick-5s { trunc(@, 10); printa(@); clear(@); }'
メモリアロケーション
dtrace -n '
fbt::malloc:entry { @[execname] = sum(arg1); }
tick-5s { trunc(@, 10); printa(@); clear(@); }'
DTraceの制限
安全性
DTraceは以下を禁止している:
- ループ(無限ループ防止)
- 浮動小数点演算
- カーネルメモリの書き換え
オーバーヘッド
- プローブを有効にしない限りオーバーヘッドはほぼゼロ
- 大量のプローブを有効にすると影響あり
- 本番環境でも使えるレベル
DTrace vs 他のツール
| ツール | 用途 | プラットフォーム |
|---|---|---|
| DTrace | 汎用トレース | FreeBSD, Solaris, macOS |
| eBPF/BCC | Linuxのトレース | Linux |
| SystemTap | Linuxのトレース | Linux |
| perf | パフォーマンス分析 | Linux |
| ktrace | システムコールトレース | FreeBSD |
DTraceの優位点:
- 構文がシンプル
- 安全性が高い
- 長年の実績
まとめ
DTraceは:
- カーネルの中身をリアルタイムで可視化
- 本番環境でも使える低オーバーヘッド
- 数十万のプローブで何でも追跡
- Dスクリプトで複雑な分析も可能
FreeBSD/Solarisユーザーの特権。Linuxには(BPFができるまで)なかった。
シリーズ完結
5回に渡ってFreeBSDカーネル開発を解説した:
- Part1: カーネルビルドの基本
- Part2: カーネルモジュールの作り方
- Part3: PCIデバイスドライバ
- Part4: カスタムシステムコール
- Part5: DTraceでカーネルを観察
FreeBSDのカーネルは読みやすく、改造しやすい。
OSの内部に興味があるなら、FreeBSDで遊んでみることをオススメする。
このシリーズが役に立ったら、いいね・ストックしてもらえると嬉しいです!