このページの目次
glibc システムコールラッパーの内部構造
カバー範囲: glibc システムコールラッパー → INLINE_SYSCALL → vsyscall/vDSO → gettimeofday 高速パス → システムコールレイテンシ → 自作 syscall ラッパー 対象: glibc 2.x、musl との比較、主に x86-64
概要
ユーザー空間のコードは syscall 命令を直接記述しません。glibc は各システムコールをラップする層を設けており、この層で以下の処理を行っています:引数の渡し方(レジスタ割り当て)、戻り値から errno への変換、取消ポイント(cancellation point)、および vDSO 高速パス。この層を理解することで、「なぜ clock_gettime が getpid よりも高速なのか」や「なぜ自作の syscall ラッパーが時として高速になるのか」といった疑問に答えられます。
glibc: INLINE_SYSCALL マクロ
// sysdeps/unix/sysv/linux/x86_64/sysdep.h
// glibc システムコールのラップ階層:
// 最下層: アセンブリマクロ
// アプリケーション層: errno への変換
重要な詳細:
asm volatile: コンパイラが syscall を跨いでメモリアクセスを並べ替えるのを防ぎます("memory"クラバー)"cc"クラバー: syscall はフラグレジスタを変更する可能性があります"r11", "cx"クラバー: syscall 命令はこれらのレジスタを破壊します- 戻り値のチェック:
__glibc_unlikelyを使用し、errno 処理パスが頻繁に通らないことをコンパイラに指示します
gettimeofday / clock_gettime: vDSO 高速パス
// glibc の clock_gettime 実装 (簡略化):
int
// vDSO バージョン (arch/x86/entry/vdso/vclock_gettime.c):
// 1. TSC を読む (rdtsc、約10サイクル)
// 2. timekeeper 変換を適用 (乗算 + シフト、約20サイクル)
// 3. seqlock をチェック (read_seqbegin/read_seqretry、約5サイクル)
// → 合計約35サイクル、対 syscall は約70サイクル → 約2倍高速
glibc と musl: システムコールラッパーの違い
| glibc | musl | |
|---|---|---|
| ラップの複雑さ | 複数階層のマクロ (INLINE_SYSCALL → INTERNAL_SYSCALL) | 単一階層のインラインアセンブリ |
| errno の処理 | TLS 関数呼び出し | インラインで直接 __errno_location() に書き込み |
| 取消ポイント (cancellation) | read/write などの「低速」syscall 前にチェック | より単純なモデル |
| vDSO | 起動時に初期化 + キャッシュ | 同上、ただしより軽量 |
| コードサイズ | システムコールあたり約50行(すべてのマクロ展開を含む) | システムコールあたり約5行 |
システムコールレイテンシの分析
# システムコールレイテンシの測定 (最小限の引数0のsyscall):
# getpid() → syscall 39 (x86-64, getpid)
# PID がキャッシュされているため → 直接返却 → PCB を実際に読みに行かない
# システムコールレイテンシ測定ツール: libMicro, lmbench
典型的なレイテンシ (3.5GHz x86-64):
getpid (キャッシュ済み): ~70ns (~250サイクル)
getppid: ~90ns
gettimeofday (vDSO): ~15ns (~50サイクル) ← 極めて高速!
gettimeofday (syscall フォールバック): ~80ns
write(1 byte to /dev/null): ~200ns
read(1 byte from /dev/zero): ~200ns
futex(FUTEX_WAIT, uncontended): ~150ns
futex(FUTEX_WAIT, contended): ~5-20μs (コンテキストスイッチを含む)
レイテンシの内訳:
syscall 命令: ~20サイクル
カーネルエントリ/アウト (swapgs + スタック切替): ~20サイクル
実際の syscall ロジック: ~30サイクル以上
Spectre/Meltdown 対策: ~20サイクル (影響を受けるCPUではKPTI有効)
手書き syscall: より高速なラッパー
// glibc の read() には一定のオーバーヘッド (errno 変換など) がある
// 熱いパスにいて、自分でエラー処理ができる場合 → 手動で syscall を書く:
static inline long
// どの場合に価値があるか?
// - io_uring の熱いパス → すでにラップされているため、不要
// - カスタムアロケータ → mmap ラッパーが必要になる場合がある
// - パフォーマンスベンチマーク → glibc のオーバーヘッドを除外するため
参考文献
- ソースコード: glibc
sysdeps/unix/sysv/linux/x86_64/sysdep.h,sysdeps/unix/sysv/linux/clock_gettime.c, muslarch/x86_64/syscall_arch.h - LWN: "vDSO and clock_gettime", "System call overhead"
- ツール:
man syscalls, strace -c (syscall count/time)
キーワード: INLINE_SYSCALL, syscall wrapper, vDSO, clock_gettime, errno, cancellation point, glibc, musl, syscall latency