---
title: glibc システムコールラッパーの内部構造
url: https://doc.liz6.com/ja/systems-programming/02-system-call-abi/03-anatomy-of-glibc-syscall-wrappers
locale: ja
area: systems-programming
tags:
- systems-programming
- system-call-abi
date: 2026-06-30
modified: 2026-07-16
description: 'カバー範囲: glibc システムコールラッパー → INLINE_SYSCALL → vsyscall/vDSO → gettimeofday 高速パス → システムコールレイテンシ → 自作 syscall ラッパー 対象: glibc 2.x、musl との比較、主に x86-64'
---

# 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 マクロ

```c
// sysdeps/unix/sysv/linux/x86_64/sysdep.h
// glibc システムコールのラップ階層:

// 最下層: アセンブリマクロ
#define INTERNAL_SYSCALL(name, nr, args...)                \
({                                                          \
    unsigned long int resultvar;                            \
    LOAD_ARGS_##nr(args)          /* 引数をレジスタに配置 */    \
    LOAD_REGS_##nr                 /* rdi, rsi, rdx, ... をロード */ \
    register long int _a1 asm ("rdi");                      \
    asm volatile (                                          \
        "syscall\n\t"                                       \
        : "=a" (resultvar)                                  \
        : "0" (__NR_##name) ASM_ARGS_##nr                   \
        : "memory", "cc", "r11", "cx");                     \
    (long int) resultvar;                                   \
})

// アプリケーション層: errno への変換
#define INLINE_SYSCALL(name, nr, args...)                  \
({                                                          \
    long int sc_ret = INTERNAL_SYSCALL(name, nr, args);    \
    if (__glibc_unlikely(INTERNAL_SYSCALL_ERROR_P(sc_ret))) \
    {                                                       \
        __set_errno(INTERNAL_SYSCALL_ERRNO(sc_ret));       \
        sc_ret = -1;                                        \
    }                                                       \
    sc_ret;                                                 \
})
```

重要な詳細:
- `asm volatile`: コンパイラが syscall を跨いでメモリアクセスを並べ替えるのを防ぎます（`"memory"` クラバー）
- `"cc"` クラバー: syscall はフラグレジスタを変更する可能性があります
- `"r11", "cx"` クラバー: syscall 命令はこれらのレジスタを破壊します
- 戻り値のチェック: `__glibc_unlikely` を使用し、errno 処理パスが頻繁に通らないことをコンパイラに指示します

## gettimeofday / clock_gettime: vDSO 高速パス

```c
// glibc の clock_gettime 実装 (簡略化):
int __clock_gettime64(clockid_t clock_id, struct __timespec64 *tp)
{
    // vDSO 高速パスを試みる (ユーザー空間で実行、syscall なし)
    if (__glibc_likely(clock_id < CLOCK_THREAD_CPUTIME_ID))
    {
        __vdso_clock_gettime_func vdso_func =
            GLRO(dl_vdso_clock_gettime);  // 起動時に vDSO から取得

        if (vdso_func != NULL)
        {
            int ret = vdso_func(clock_id, tp);
            if (ret == 0) return 0;
        }
    }
    // vDSO が利用できない場合 → syscall にフォールバック
    return INLINE_SYSCALL(clock_gettime64, 2, clock_id, tp);
}

// 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行 |

## システムコールレイテンシの分析

```bash
# システムコールレイテンシの測定 (最小限の引数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: より高速なラッパー

```c
// glibc の read() には一定のオーバーヘッド (errno 変換など) がある
// 熱いパスにいて、自分でエラー処理ができる場合 → 手動で syscall を書く:

static inline long __read(int fd, void *buf, size_t count) {
    long ret;
    register long rdi asm("rdi") = fd;
    register long rsi asm("rsi") = (long)buf;
    register long rdx asm("rdx") = count;
    register long rax asm("rax") = __NR_read;
    asm volatile("syscall"
                 : "=a"(ret)
                 : "r"(rdi), "r"(rsi), "r"(rdx), "a"(rax)
                 : "rcx", "r11", "memory");
    return ret;  // 負数 = -errno
}

// どの場合に価値があるか?
//   - io_uring の熱いパス → すでにラップされているため、不要
//   - カスタムアロケータ → mmap ラッパーが必要になる場合がある
//   - パフォーマンスベンチマーク → glibc のオーバーヘッドを除外するため
```

## 参考文献

- **ソースコード**: glibc `sysdeps/unix/sysv/linux/x86_64/sysdep.h`, `sysdeps/unix/sysv/linux/clock_gettime.c`, musl `arch/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*
