5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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カーネル開発を解説した:

  1. Part1: カーネルビルドの基本
  2. Part2: カーネルモジュールの作り方
  3. Part3: PCIデバイスドライバ
  4. Part4: カスタムシステムコール
  5. Part5: DTraceでカーネルを観察

FreeBSDのカーネルは読みやすく、改造しやすい

OSの内部に興味があるなら、FreeBSDで遊んでみることをオススメする。

このシリーズが役に立ったら、いいね・ストックしてもらえると嬉しいです!

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?