Linux Kernel System Calls Deep Dive
Linux Kernel System Calls Deep Dive - 包括的技術ガイド
著者: AI Generated Technical Document 対象カーネルバージョン: Linux 6.x 系 最終更新日: 2026-04-10 対象読者: 中級〜上級エンジニア、カーネル開発者、SRE
目次
- はじめに
- システムコールの基本概念
- x86_64 におけるシステムコール機構
- システムコールテーブル (sys_call_table)
- システムコールのエントリ/イグジットパス
- SYSCALL_DEFINE マクロ
- システムコールの引数渡し
- VDSO と vsyscall
- エラーハンドリング (errno)
- 新しいシステムコールの追加手順
- ファイル I/O 系システムコール
- プロセス管理系システムコール
- メモリ管理系システムコール
- ネットワーク系システムコール
- I/O 多重化系システムコール
- その他の重要なシステムコール
- システムコールのオーバーヘッドと最適化
- 互換性レイヤー (compat syscalls)
- システムコールの監査 (auditd)
- デバッグ・解析ツール
- まとめ
1. はじめに
1.1 本ドキュメントの目的
Linux カーネルにおけるシステムコール (system call, syscall) は、ユーザー空間プログラムがカーネルの機能を利用するための根本的なインターフェースである。本ドキュメントでは、x86_64 アーキテクチャを中心に、システムコールの内部機構から実践的な活用方法まで包括的に解説する。
1.2 前提知識
- C 言語プログラミングの基本
- Linux カーネルの基本的なアーキテクチャ理解
- アセンブリ言語 (x86_64) の基礎
- 特権レベル (Ring 0 / Ring 3) の概念
1.3 システムコールとは何か
システムコールは、ユーザー空間 (Ring 3) からカーネル空間 (Ring 0) への制御された遷移メカニズムである。プロセスがファイルの読み書き、ネットワーク通信、メモリ割り当てなどのカーネル機能を必要とする場合、システムコールを介してカーネルにリクエストを送信する。
┌─────────────────────────────────────────────┐
│ ユーザー空間 (Ring 3) │
│ │
│ ┌─────────┐ ┌────────────┐ │
│ │ アプリ │───>│ glibc/musl │ │
│ │ケーション │ │ ラッパー │ │
│ └─────────┘ └─────┬──────┘ │
│ │ │
│ │ SYSCALL 命令 │
├───────────────────────┼─────────────────────┤
│ ▼ │
│ カーネル空間 (Ring 0) │
│ │
│ ┌────────────────────────────────────┐ │
│ │ entry_SYSCALL_64 │ │
│ │ → sys_call_table[nr] │ │
│ │ → 実際のシステムコールハンドラ │ │
│ └────────────────────────────────────┘ │
└─────────────────────────────────────────────┘
1.4 システムコールの歴史的変遷
| 世代 | メカニズム | 命令 | 特徴 |
|---|---|---|---|
| 第1世代 | ソフトウェア割り込み | int 0x80 | 低速、IA-32 互換 |
| 第2世代 | 高速システムコール | sysenter/sysexit | Intel Pentium II 以降 |
| 第3世代 | 64bit 高速システムコール | SYSCALL/SYSRET | AMD64/x86_64 標準 |
| VDSO | 仮想 DSO | ユーザー空間実行 | カーネル遷移不要 |
2. システムコールの基本概念
2.1 特権レベルとモード遷移
x86_64 アーキテクチャでは、CPU は複数の特権レベル (Protection Ring) を提供する。Linux では主に Ring 0 (カーネルモード) と Ring 3 (ユーザーモード) の2つを使用する。
Ring 0 (最高特権) ─── カーネルモード
│ すべてのメモリ、I/O ポート、CPU 命令にアクセス可能
│
Ring 1 (未使用)
Ring 2 (未使用)
│
Ring 3 (最低特権) ─── ユーザーモード
制限されたメモリ、制限された命令セット
2.2 システムコールの実行フロー
システムコール実行の全体フローを以下に示す:
1. ユーザープログラムが C ライブラリ関数を呼び出す
例: write(fd, buf, count)
2. C ライブラリ (glibc) がシステムコール番号をレジスタにセット
RAX = __NR_write (= 1)
3. 引数をレジスタにセット
RDI = fd, RSI = buf, RDX = count
4. SYSCALL 命令を実行
→ CPU がカーネルモードに遷移
5. entry_SYSCALL_64 が呼ばれる
→ レジスタ保存、スタック切り替え
6. sys_call_table[RAX] からハンドラを取得
→ ksys_write() を呼び出し
7. カーネル内部処理を実行
→ VFS → ファイルシステム → デバイスドライバ
8. 戻り値を RAX にセット
9. SYSRET 命令でユーザーモードに復帰
10. C ライブラリが戻り値を処理
→ エラーの場合は errno をセット
2.3 システムコール番号
各システムコールには一意の番号が割り当てられている。x86_64 の場合、以下のヘッダファイルで定義される:
/* arch/x86/include/generated/uapi/asm/unistd_64.h */
#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4
#define __NR_fstat 5
#define __NR_lstat 6
#define __NR_poll 7
#define __NR_lseek 8
#define __NR_mmap 9
#define __NR_mprotect 10
#define __NR_munmap 11
#define __NR_brk 12
#define __NR_rt_sigaction 13
#define __NR_rt_sigprocmask 14
#define __NR_rt_sigreturn 15
#define __NR_ioctl 16
/* ... 以降多数 ... */
#define __NR_clone3 435
#define __NR_close_range 436
#define __NR_openat2 437
#define __NR_pidfd_getfd 438
#define __NR_faccessat2 439
#define __NR_process_madvise 440
/* Linux 6.x で追加されたもの */
#define __NR_futex_waitv 449
#define __NR_set_mempolicy_home_node 450
#define __NR_cachestat 451
#define __NR_fchmodat2 452
現在のシステムコール数を確認する方法:
# システムコール数の確認
$ grep -c '__NR_' /usr/include/asm/unistd_64.h
# または
$ ausyscall --dump | wc -l
# 特定のシステムコール番号の確認
$ ausyscall openat
257
$ python3 -c "import os; print(os.sysconf('SC_NPROCESSORS_ONLN'))"
3. x86_64 におけるシステムコール機構
3.1 SYSCALL/SYSRET メカニズム
x86_64 アーキテクチャでは、SYSCALL 命令が高速なシステムコール発行メカニズムとして使用される。この命令は Model Specific Register (MSR) を利用して、スタック切り替えとモード遷移を効率的に行う。
3.1.1 関連する MSR レジスタ
MSR_STAR (0xC0000081):
┌───────────────────┬───────────────────┐
│ SYSRET CS/SS │ SYSCALL CS/SS │
│ (bits 63:48) │ (bits 47:32) │
└───────────────────┴───────────────────┘
SYSRET: CS = MSR_STAR[63:48] SS = MSR_STAR[63:48] + 8
SYSCALL: CS = MSR_STAR[47:32] SS = MSR_STAR[47:32] + 8
MSR_LSTAR (0xC0000082):
SYSCALL のエントリポイントアドレス (RIP にロードされる)
→ entry_SYSCALL_64 のアドレス
MSR_CSTAR (0xC0000083):
互換モード (32-bit) の SYSCALL エントリポイント
MSR_SFMASK (0xC0000084):
SYSCALL 実行時に RFLAGS からクリアされるビットマスク
→ 通常 IF (割り込みフラグ) をクリアして割り込みを無効化
3.1.2 MSR 初期化コード
/* arch/x86/kernel/cpu/common.c */
void syscall_init(void)
{
wrmsr(MSR_STAR, 0, (__USER32_CS << 16) | __KERNEL_CS);
wrmsrl(MSR_LSTAR, (unsigned long)entry_SYSCALL_64);
#ifdef CONFIG_IA32_EMULATION
wrmsrl(MSR_CSTAR, (unsigned long)entry_SYSCALL_compat);
#else
wrmsrl(MSR_CSTAR, (unsigned long)ignore_sysret);
#endif
/* Flags to clear on syscall */
wrmsrl(MSR_SYSCALL_MASK,
X86_EFLAGS_TF | X86_EFLAGS_DF | X86_EFLAGS_IF |
X86_EFLAGS_IOPL | X86_EFLAGS_AC | X86_EFLAGS_NT);
}
3.1.3 SYSCALL 命令の動作 (ハードウェア)
SYSCALL 命令実行時:
1. RCX ← RIP (戻りアドレスを保存)
2. R11 ← RFLAGS (フラグを保存)
3. RFLAGS ← RFLAGS AND NOT(MSR_SFMASK) (フラグをマスク)
4. CS ← MSR_STAR[47:32] (カーネル CS をロード)
5. SS ← MSR_STAR[47:32] + 8 (カーネル SS をロード)
6. RIP ← MSR_LSTAR (entry_SYSCALL_64 にジャンプ)
7. CPL ← 0 (特権レベルをカーネルモードに)
SYSRET 命令実行時:
1. RIP ← RCX (戻りアドレスを復元)
2. RFLAGS ← R11 (フラグを復元)
3. CS ← MSR_STAR[63:48] + 16 (ユーザー CS をロード)
4. SS ← MSR_STAR[63:48] + 8 (ユーザー SS をロード)
5. CPL ← 3 (特権レベルをユーザーモードに)
3.2 レガシー int 0x80 メカニズム
32-bit 互換のために、int 0x80 によるシステムコール発行も引き続きサポートされている。ただし、これは大幅に低速であり、64-bit プログラムでは使用すべきではない。
; レガシー int 0x80 によるシステムコール (32-bit ABI)
; write(1, "Hello\n", 6)
section .data
msg db "Hello", 10 ; 10 = '\n'
section .text
global _start
_start:
mov eax, 4 ; __NR_write (32-bit 番号)
mov ebx, 1 ; fd = stdout
mov ecx, msg ; buf
mov edx, 6 ; count
int 0x80 ; ソフトウェア割り込み
mov eax, 1 ; __NR_exit (32-bit 番号)
xor ebx, ebx ; status = 0
int 0x80
; 64-bit SYSCALL によるシステムコール
; write(1, "Hello\n", 6)
section .data
msg db "Hello", 10
section .text
global _start
_start:
mov rax, 1 ; __NR_write (64-bit 番号)
mov rdi, 1 ; fd = stdout
mov rsi, msg ; buf
mov rdx, 6 ; count
syscall ; 高速システムコール
mov rax, 60 ; __NR_exit (64-bit 番号)
xor rdi, rdi ; status = 0
syscall
3.2.1 int 0x80 vs SYSCALL のパフォーマンス比較
# ベンチマーク: getpid() を100万回呼び出し
$ cat bench_syscall.c
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/syscall.h>
#define ITERATIONS 1000000
int main(void)
{
struct timespec start, end;
long i;
double elapsed;
/* SYSCALL 命令経由 */
clock_gettime(CLOCK_MONOTONIC, &start);
for (i = 0; i < ITERATIONS; i++) {
syscall(SYS_getpid);
}
clock_gettime(CLOCK_MONOTONIC, &end);
elapsed = (end.tv_sec - start.tv_sec) +
(end.tv_nsec - start.tv_nsec) / 1e9;
printf("syscall: %.3f sec (%.0f ns/call)\n",
elapsed, elapsed * 1e9 / ITERATIONS);
return 0;
}
$ gcc -O2 -o bench_syscall bench_syscall.c
$ ./bench_syscall
# 典型的な結果:
# syscall: 0.180 sec (180 ns/call)
# int 0x80 は約 2-3 倍遅い
3.3 Spectre/Meltdown 対策の影響
Spectre および Meltdown の脆弱性対策 (KPTI: Kernel Page Table Isolation) により、システムコールのオーバーヘッドが増加している。
# KPTI の状態確認
$ cat /sys/devices/system/cpu/vulnerabilities/meltdown
Mitigation: PTI
$ dmesg | grep "page table"
[ 0.000000] Kernel/User page tables isolation: enabled
# Spectre 対策の確認
$ cat /sys/devices/system/cpu/vulnerabilities/spectre_v2
Mitigation: Enhanced / Automatic IBRS; IBPB: conditional; ...
# RETPOLINE の確認
$ cat /sys/devices/system/cpu/vulnerabilities/spectre_v2
Mitigation: Retpolines, IBPB: conditional, IBRS_FW, ...
KPTI が有効な場合のシステムコールオーバーヘッド:
KPTI 無効時: ~100-150 ns/syscall (getpid)
KPTI 有効時: ~400-800 ns/syscall (getpid)
→ CR3 レジスタの切り替えによる TLB フラッシュが主な原因
PCID (Process Context ID) 対応 CPU の場合:
→ TLB フラッシュの影響が軽減され、~200-400 ns/syscall 程度
4. システムコールテーブル (sys_call_table)
4.1 テーブルの定義
sys_call_table は、システムコール番号から実際のハンドラ関数へのマッピングを行う関数ポインタの配列である。
/* arch/x86/entry/syscall_64.c */
#include <asm/unistd.h>
#include <asm/syscalls.h>
#define __SYSCALL(nr, sym) extern long __x64_##sym(const struct pt_regs *);
#include <asm/syscalls_64.h>
#undef __SYSCALL
#define __SYSCALL(nr, sym) __x64_##sym,
asmlinkage const sys_call_table_t sys_call_table[__NR_syscall_max+1] = {
[0 ... __NR_syscall_max] = __x64_sys_ni_syscall,
#include <asm/syscalls_64.h>
};
4.2 テーブルの構造
sys_call_table の構造:
インデックス │ 関数ポインタ
──────────┼──────────────────────
[0] │ __x64_sys_read
[1] │ __x64_sys_write
[2] │ __x64_sys_open
[3] │ __x64_sys_close
[4] │ __x64_sys_stat
[5] │ __x64_sys_fstat
... │ ...
[257] │ __x64_sys_openat
... │ ...
[435] │ __x64_sys_clone3
[436] │ __x64_sys_close_range
... │ ...
[N] │ __x64_sys_ni_syscall (未実装)
4.3 sys_ni_syscall
未実装のシステムコール番号に対しては、sys_ni_syscall が配置され、-ENOSYS を返す:
/* kernel/sys_ni.c */
SYSCALL_DEFINE0(ni_syscall)
{
return -ENOSYS;
}
4.4 テーブルの保護
sys_call_table はカーネルの .rodata セクション (読み取り専用) に配置されており、実行時の改ざんを防止する:
# テーブルのアドレスを確認
$ sudo cat /proc/kallsyms | grep sys_call_table
ffffffff82200300 R sys_call_table
ffffffff82201580 R ia32_sys_call_table
# 'R' は読み取り専用 (.rodata) を意味する
# テーブルのサイズを確認
$ sudo cat /proc/kallsyms | grep -A1 sys_call_table
4.5 syscall テーブルの調査
# 全システムコールの一覧を取得
$ ausyscall --dump
Using x86_64 syscall table:
0 read
1 write
2 open
3 close
4 stat
5 fstat
...
# 特定のシステムコールの番号を調べる
$ ausyscall openat
257
$ ausyscall sendmsg
46
# Python を使ったテーブル調査
$ python3 -c "
import ctypes
import ctypes.util
libc = ctypes.CDLL(ctypes.util.find_library('c'), use_errno=True)
# getpid のテスト
pid = libc.syscall(39) # __NR_getpid
print(f'PID: {pid}')
"
5. システムコールのエントリ/イグジットパス
5.1 entry_SYSCALL_64 の詳細
entry_SYSCALL_64 は、x86_64 でのシステムコールエントリポイントであり、アセンブリ言語で記述されている。
/* arch/x86/entry/entry_64.S */
SYM_CODE_START(entry_SYSCALL_64)
UNWIND_HINT_ENTRY
ENDBR
swapgs
/* tss.sp2 is scratch space. */
movq %rsp, PER_CPU_VAR(cpu_tss_rw + TSS_sp2)
SWITCH_TO_KERNEL_CR3 scratch_reg=%rsp
movq PER_CPU_VAR(pcpu_hot + X86_top_of_stack), %rsp
SYM_INNER_LABEL(entry_SYSCALL_64_safe_stack, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(cpu_tss_rw + TSS_sp2) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
SYM_INNER_LABEL(entry_SYSCALL_64_after_hwframe, SYM_L_GLOBAL)
ANNOTATE_NOENDBR
pushq %rax /* pt_regs->orig_ax */
PUSH_AND_CLEAR_REGS rax=$-ENOSYS
/* ... レジスタ保存 ... */
movq %rax, %rdi /* arg1: syscall number */
movq %rsp, %rsi /* arg2: pt_regs pointer */
call do_syscall_64 /* C 関数を呼び出し */
/* ... 復帰処理 ... */
SWITCH_TO_USER_CR3_STACK scratch_reg=%rdi
popq %rdi
popq %rsp
swapgs
sysretq
SYM_CODE_END(entry_SYSCALL_64)
5.2 do_syscall_64 関数
/* arch/x86/entry/common.c */
__visible noinstr void do_syscall_64(struct pt_regs *regs, int nr)
{
add_random_kstack_offset();
nr = syscall_enter_from_user_mode(regs, nr);
instrumentation_begin();
if (!do_syscall_x64(regs, nr) && !do_syscall_x32(regs, nr)) {
nr = -1;
regs->ax = __x64_sys_ni_syscall(regs);
}
instrumentation_end();
syscall_exit_to_user_mode(regs);
}
static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr)
{
if (likely(nr < NR_syscalls)) {
nr = array_index_nospec(nr, NR_syscalls);
regs->ax = sys_call_table[nr](regs);
return true;
}
return false;
}
5.3 pt_regs 構造体
/* arch/x86/include/asm/ptrace.h */
struct pt_regs {
/* C ABI のレジスタ引数 (システムコール引数を保持) */
unsigned long r15;
unsigned long r14;
unsigned long r13;
unsigned long r12;
unsigned long bp; /* RBP */
unsigned long bx; /* RBX */
/* システムコール引数: arg6 〜 arg1 */
unsigned long r11;
unsigned long r10; /* arg4 (RCX は SYSCALL で使用されるため) */
unsigned long r9; /* arg6 */
unsigned long r8; /* arg5 */
unsigned long ax; /* 戻り値 / syscall 番号 */
unsigned long cx; /* RCX (SYSCALL により RIP が保存される) */
unsigned long dx; /* arg3 */
unsigned long si; /* arg2 */
unsigned long di; /* arg1 */
unsigned long orig_ax; /* 元のシステムコール番号 */
/* IRET フレーム */
unsigned long ip; /* RIP */
unsigned long cs; /* CS */
unsigned long flags; /* RFLAGS */
unsigned long sp; /* RSP */
unsigned long ss; /* SS */
};
5.4 エントリ/イグジットの完全フロー
ユーザー空間:
1. SYSCALL 命令実行
└─ RCX = RIP, R11 = RFLAGS
└─ CS/SS をカーネルセレクタに変更
└─ RIP = MSR_LSTAR (entry_SYSCALL_64)
entry_SYSCALL_64:
2. swapgs
└─ GS ベースをカーネルの per-CPU データに切り替え
3. スタック切り替え
└─ ユーザー RSP を保存
└─ カーネルスタックに切り替え
4. SWITCH_TO_KERNEL_CR3 (KPTI 有効時)
└─ ページテーブルをカーネル用に切り替え
5. pt_regs 構造体を構築
└─ 全レジスタをスタック上に push
6. do_syscall_64() 呼び出し
└─ syscall_enter_from_user_mode()
└─ seccomp フィルタチェック
└─ ptrace チェック
└─ audit チェック
└─ sys_call_table[nr](regs) 実行
└─ syscall_exit_to_user_mode()
└─ シグナル配送チェック
└─ スケジューラ preemption チェック
└─ audit ログ記録
7. 復帰処理
└─ SWITCH_TO_USER_CR3 (KPTI 有効時)
└─ レジスタ復元
└─ swapgs
└─ SYSRET 命令でユーザー空間に復帰
5.5 カーネルスタックの使用
┌──────────────────────────┐ ← トップ (高アドレス)
│ thread_info │
├──────────────────────────┤
│ │
│ カーネルスタック領域 │
│ (通常 16KB / 4ページ) │
│ │
├──────────────────────────┤
│ pt_regs │ ← entry_SYSCALL_64 で構築
│ (レジスタ保存領域) │
├──────────────────────────┤
│ ローカル変数 │ ← カーネル関数のスタックフレーム
│ 関数呼び出しフレーム │
├──────────────────────────┤
│ ... │
└──────────────────────────┘ ← ボトム (低アドレス)
※ スタックオーバーフロー検出用
ガードページ付き
6. SYSCALL_DEFINE マクロ
6.1 マクロの定義
SYSCALL_DEFINE マクロは、システムコールハンドラ関数を定義するための標準的な方法である。
/* include/linux/syscalls.h */
/* 引数なしのシステムコール */
#define SYSCALL_DEFINE0(sname) \
SYSCALL_METADATA(_##sname, 0); \
asmlinkage long sys_##sname(void); \
ALLOW_ERROR_INJECTION(sys_##sname, ERRNO); \
asmlinkage long sys_##sname(void)
/* 引数ありのシステムコール (1〜6 引数) */
#define SYSCALL_DEFINE1(name, ...) SYSCALL_DEFINEx(1, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE2(name, ...) SYSCALL_DEFINEx(2, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE4(name, ...) SYSCALL_DEFINEx(4, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE5(name, ...) SYSCALL_DEFINEx(5, _##name, __VA_ARGS__)
#define SYSCALL_DEFINE6(name, ...) SYSCALL_DEFINEx(6, _##name, __VA_ARGS__)
6.2 マクロ展開の詳細
/* SYSCALL_DEFINEx の展開 */
#define SYSCALL_DEFINEx(x, sname, ...) \
SYSCALL_METADATA(sname, x, __VA_ARGS__) \
__SYSCALL_DEFINEx(x, sname, __VA_ARGS__)
#define __SYSCALL_DEFINEx(x, name, ...) \
__diag_push(); \
__diag_ignore_all("-Wattribute-alias", \
"Type aliasing is used to sanitize syscall args"); \
asmlinkage long sys##name(__MAP(x, __SC_DECL, __VA_ARGS__)) \
__attribute__((alias(__stringify(__se_sys##name)))); \
ALLOW_ERROR_INJECTION(sys##name, ERRNO); \
static inline long __do_sys##name(__MAP(x, __SC_DECL, __VA_ARGS__)); \
asmlinkage long __se_sys##name(__MAP(x, __SC_LONG, __VA_ARGS__)); \
asmlinkage long __se_sys##name(__MAP(x, __SC_LONG, __VA_ARGS__)) \
{ \
long ret = __do_sys##name(__MAP(x, __SC_CAST, __VA_ARGS__)); \
__MAP(x, __SC_TEST, __VA_ARGS__); \
__PROTECT(x, ret, __MAP(x, __SC_ARGS, __VA_ARGS__)); \
return ret; \
} \
__diag_pop(); \
static inline long __do_sys##name(__MAP(x, __SC_DECL, __VA_ARGS__))
6.3 実際の使用例
/* fs/read_write.c */
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
return ksys_read(fd, buf, count);
}
/* 上記は以下のように展開される */
asmlinkage long sys_read(unsigned int fd, char __user *buf, size_t count);
/* 実際のハンドラ */
static inline long __do_sys_read(unsigned int fd, char __user *buf, size_t count)
{
return ksys_read(fd, buf, count);
}
/* kernel/sys.c - getpid の例 */
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}
/* fs/open.c - openat の例 */
SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename,
int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(dfd, filename, flags, mode);
}
6.4 __user アノテーション
/* include/linux/compiler_types.h */
#ifdef __CHECKER__
# define __user __attribute__((noderef, address_space(__user)))
#else
# define __user
#endif
__user アノテーションは以下の目的で使用される:
- Sparse チェッカー: ユーザー空間ポインタの誤使用を検出
- コード可読性: ポインタがユーザー空間を指すことを明示
- 安全性:
copy_from_user()/copy_to_user()の使用を強制
/* ユーザー空間とのデータコピー */
/* 正しい使い方 */
char kernel_buf[256];
if (copy_from_user(kernel_buf, user_buf, count))
return -EFAULT;
/* 絶対にやってはいけない - ユーザー空間ポインタの直接参照 */
/* char c = *user_buf; <-- これは OOPS やセキュリティ脆弱性の原因 */
7. システムコールの引数渡し
7.1 x86_64 レジスタ規約
x86_64 のシステムコールでは、以下のレジスタ規約に従って引数が渡される:
┌──────────┬───────────────────────────────────┐
│ レジスタ │ 用途 │
├──────────┼───────────────────────────────────┤
│ RAX │ システムコール番号 │
│ RDI │ 第1引数 │
│ RSI │ 第2引数 │
│ RDX │ 第3引数 │
│ R10 │ 第4引数 (注: RCX ではなく R10) │
│ R8 │ 第5引数 │
│ R9 │ 第6引数 │
├──────────┼───────────────────────────────────┤
│ RAX │ 戻り値 │
│ RCX │ SYSCALL で破壊 (RIP 保存に使用) │
│ R11 │ SYSCALL で破壊 (RFLAGS 保存に使用) │
└──────────┴───────────────────────────────────┘
重要: 通常の C 関数呼び出し規約 (System V AMD64 ABI) では第4引数に RCX を使うが、SYSCALL 命令が RCX を破壊するため、システムコールでは代わりに R10 が使用される。
7.2 glibc のシステムコールラッパー
/* glibc の syscall() 関数のアセンブリ実装 (簡略版) */
/* sysdeps/unix/sysv/linux/x86_64/syscall.S */
ENTRY (syscall)
movq %rdi, %rax /* システムコール番号 */
movq %rsi, %rdi /* arg1 */
movq %rdx, %rsi /* arg2 */
movq %rcx, %rdx /* arg3 */
movq %r8, %r10 /* arg4 (RCX → R10 へ変換!) */
movq %r9, %r8 /* arg5 */
movq 8(%rsp), %r9 /* arg6 (スタックから) */
syscall /* システムコール実行 */
cmpq $-4095, %rax /* エラーチェック */
jae SYSCALL_ERROR_LABEL
ret
END (syscall)
7.3 インラインアセンブリでのシステムコール
/* C コードからインラインアセンブリでシステムコールを直接発行 */
#include <stdio.h>
static inline long my_syscall3(long nr, long a1, long a2, long a3)
{
long ret;
asm volatile (
"syscall"
: "=a" (ret) /* 出力: RAX = 戻り値 */
: "a" (nr), /* 入力: RAX = syscall 番号 */
"D" (a1), /* 入力: RDI = arg1 */
"S" (a2), /* 入力: RSI = arg2 */
"d" (a3) /* 入力: RDX = arg3 */
: "rcx", "r11", "memory" /* 破壊レジスタ */
);
return ret;
}
int main(void)
{
const char *msg = "Hello from inline syscall!\n";
/* write(1, msg, 27) */
long ret = my_syscall3(1, 1, (long)msg, 27);
printf("write returned: %ld\n", ret);
return 0;
}
7.4 6 引数のシステムコール例
/* mmap は 6 引数のシステムコール */
/* void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset) */
static inline void *my_mmap(void *addr, size_t length, int prot,
int flags, int fd, off_t offset)
{
void *ret;
register long r10 asm("r10") = (long)flags;
register long r8 asm("r8") = (long)fd;
register long r9 asm("r9") = (long)offset;
asm volatile (
"syscall"
: "=a" (ret)
: "a" ((long)__NR_mmap),
"D" (addr),
"S" (length),
"d" (prot),
"r" (r10),
"r" (r8),
"r" (r9)
: "rcx", "r11", "memory"
);
return ret;
}
7.5 32-bit 互換モードの引数渡し
32-bit 互換モード (int 0x80) のレジスタ規約:
┌──────────┬────────────────────┐
│ レジスタ │ 用途 │
├──────────┼────────────────────┤
│ EAX │ システムコール番号 │
│ EBX │ 第1引数 │
│ ECX │ 第2引数 │
│ EDX │ 第3引数 │
│ ESI │ 第4引数 │
│ EDI │ 第5引数 │
│ EBP │ 第6引数 │
├──────────┼────────────────────┤
│ EAX │ 戻り値 │
└──────────┴────────────────────┘
8. VDSO と vsyscall
8.1 VDSO (Virtual Dynamic Shared Object) とは
VDSO は、カーネルがユーザー空間にマッピングする特殊な共有ライブラリで、特定のシステムコールをカーネルモード遷移なしに実行できるメカニズムである。
┌────────────────────────────────────────────┐
│ プロセスのアドレス空間 │
│ │
│ 0x00000000_00000000 ┌──────────────┐ │
│ │ テキスト │ │
│ │ データ │ │
│ │ ヒープ │ │
│ │ ... │ │
│ │ │ │
│ │ スタック │ │
│ 0x00007fff_xxxxxxxx │ [vdso] │◄──── VDSO ページ
│ │ [vvar] │◄──── カーネルデータ
│ 0x00007fff_ffffffff └──────────────┘ │
│ │
│ 0xffffffff_ff600000 [vsyscall] (レガシー) │
└────────────────────────────────────────────┘
8.2 VDSO で高速化されるシステムコール
/* VDSO で提供される関数 */
__vdso_clock_gettime() /* clock_gettime() の高速版 */
__vdso_gettimeofday() /* gettimeofday() の高速版 */
__vdso_time() /* time() の高速版 */
__vdso_getcpu() /* getcpu() の高速版 */
__vdso_clock_getres() /* clock_getres() の高速版 */
8.3 VDSO の確認方法
# プロセスの VDSO マッピングを確認
$ cat /proc/self/maps | grep -E 'vdso|vvar|vsyscall'
7fff2d5f5000-7fff2d5f7000 r--p 00000000 00:00 0 [vvar]
7fff2d5f7000-7fff2d5f9000 r-xp 00000000 00:00 0 [vdso]
ffffffffff600000-ffffffffff601000 --xp 00000000 00:00 0 [vsyscall]
# VDSO の内容をダンプして解析
$ dd if=/proc/self/mem bs=1 skip=$((0x7fff2d5f7000)) count=8192 \
of=/tmp/vdso.so 2>/dev/null
$ file /tmp/vdso.so
/tmp/vdso.so: ELF 64-bit LSB shared object, x86-64
# VDSO のシンボルを確認
$ objdump -T /tmp/vdso.so
DYNAMIC SYMBOL TABLE:
0000000000000a20 g DF .text 0000000000000055 LINUX_2.6 __vdso_clock_gettime
0000000000000a80 g DF .text 0000000000000025 LINUX_2.6 __vdso_gettimeofday
0000000000000ab0 g DF .text 0000000000000010 LINUX_2.6 __vdso_time
0000000000000ac0 g DF .text 0000000000000015 LINUX_2.6 __vdso_getcpu
# ldd でも VDSO を確認可能
$ ldd /bin/ls
linux-vdso.so.1 (0x00007ffcf7dfb000)
...
8.4 VDSO の内部実装
/* arch/x86/entry/vdso/vclock_gettime.c (簡略版) */
notrace int __vdso_clock_gettime(clockid_t clock, struct __kernel_timespec *ts)
{
/* vvar ページからカーネルが更新したタイムスタンプデータを直接読む */
const struct vdso_data *vd = __arch_get_vdso_data();
switch (clock) {
case CLOCK_REALTIME:
return do_realtime(vd, ts);
case CLOCK_MONOTONIC:
return do_monotonic(vd, ts);
case CLOCK_REALTIME_COARSE:
return do_realtime_coarse(vd, ts);
case CLOCK_MONOTONIC_COARSE:
return do_monotonic_coarse(vd, ts);
default:
/* VDSO で処理できない場合は通常のシステムコールにフォールバック */
return clock_gettime_fallback(clock, ts);
}
}
8.5 vsyscall (レガシー)
vsyscall は VDSO の前身であり、固定アドレス (0xffffffffff600000) にマッピングされていた。セキュリティ上の理由から、現在は以下の3つのモードで動作する:
# vsyscall モードの確認
$ cat /proc/cmdline | grep -o 'vsyscall=[a-z]*'
# カーネルブートパラメータで設定
# vsyscall=native : 従来通りの実行 (非推奨)
# vsyscall=emulate : トラップして emulate (デフォルト)
# vsyscall=none : 完全無効化 (最も安全)
# dmesg で確認
$ dmesg | grep vsyscall
[ 0.000000] vsyscall: emulation enabled
8.6 VDSO パフォーマンスの測定
/* clock_gettime の VDSO vs 通常 syscall ベンチマーク */
#include <stdio.h>
#include <time.h>
#include <sys/syscall.h>
#include <unistd.h>
#define ITERATIONS 10000000
int main(void)
{
struct timespec ts, start, end;
double elapsed;
/* VDSO 経由 (glibc がVDSOを使用) */
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < ITERATIONS; i++) {
clock_gettime(CLOCK_MONOTONIC, &ts);
}
clock_gettime(CLOCK_MONOTONIC, &end);
elapsed = (end.tv_sec - start.tv_sec) +
(end.tv_nsec - start.tv_nsec) / 1e9;
printf("VDSO: %.3f sec (%.0f ns/call)\n",
elapsed, elapsed * 1e9 / ITERATIONS);
/* 強制的に syscall 経由 */
clock_gettime(CLOCK_MONOTONIC, &start);
for (int i = 0; i < ITERATIONS; i++) {
syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &ts);
}
clock_gettime(CLOCK_MONOTONIC, &end);
elapsed = (end.tv_sec - start.tv_sec) +
(end.tv_nsec - start.tv_nsec) / 1e9;
printf("syscall: %.3f sec (%.0f ns/call)\n",
elapsed, elapsed * 1e9 / ITERATIONS);
return 0;
}
/* 典型的な出力:
* VDSO: 0.250 sec (25 ns/call)
* syscall: 2.100 sec (210 ns/call)
* → VDSO は約8倍高速
*/
9. エラーハンドリング (errno)
9.1 カーネル内部でのエラー返却
カーネル内部では、システムコールのエラーは負の整数値として返される:
/* include/uapi/asm-generic/errno-base.h */
#define EPERM 1 /* Operation not permitted */
#define ENOENT 2 /* No such file or directory */
#define ESRCH 3 /* No such process */
#define EINTR 4 /* Interrupted system call */
#define EIO 5 /* I/O error */
#define ENXIO 6 /* No such device or address */
#define E2BIG 7 /* Argument list too long */
#define ENOEXEC 8 /* Exec format error */
#define EBADF 9 /* Bad file number */
#define ECHILD 10 /* No child processes */
#define EAGAIN 11 /* Try again (= EWOULDBLOCK) */
#define ENOMEM 12 /* Out of memory */
#define EACCES 13 /* Permission denied */
#define EFAULT 14 /* Bad address */
#define ENOTBLK 15 /* Block device required */
#define EBUSY 16 /* Device or resource busy */
#define EEXIST 17 /* File exists */
/* ... */
/* include/uapi/asm-generic/errno.h */
#define EDEADLK 35 /* Resource deadlock would occur */
#define ENAMETOOLONG 36 /* File name too long */
#define ENOLCK 37 /* No record locks available */
#define ENOSYS 38 /* Invalid system call number */
#define ENOTEMPTY 39 /* Directory not empty */
/* ... */
#define ENOTSUP 95 /* Operation not supported */
/* ... */
9.2 カーネルからユーザー空間へのエラー伝播
/* カーネル側: 負のエラーコードを返す */
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
/* エラー時: -EBADF, -EFAULT, -EINVAL 等を返す */
/* 成功時: 読み取ったバイト数 (非負) を返す */
return ksys_read(fd, buf, count);
}
/* glibc 側: 負の戻り値を errno に変換 */
/* glibc/sysdeps/unix/sysv/linux/x86_64/syscall.S */
/*
SYSCALL_ERROR_LABEL:
negq %rax ; RAX を正の値に変換
movq %rax, %fs:ERRNO ; errno にセット (TLS)
movq $-1, %rax ; 戻り値を -1 にする
ret
*/
9.3 errno の使い方
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
int main(void)
{
/* errno を 0 に初期化 */
errno = 0;
int fd = open("/nonexistent/file", O_RDONLY);
if (fd == -1) {
/* errno の値を確認 */
printf("errno = %d\n", errno); /* 2 */
printf("strerror: %s\n", strerror(errno)); /* No such file or directory */
perror("open failed"); /* open failed: No such file or directory */
}
/* errno は直近の失敗したシステムコールのみ有効 */
/* 成功したシステムコールは errno を変更しない */
fd = open("/dev/null", O_RDONLY);
if (fd >= 0) {
/* この時点で errno はまだ ENOENT の可能性がある */
/* 成功時は errno をチェックしてはいけない */
printf("Success! fd=%d (errno is still %d)\n", fd, errno);
close(fd);
}
return 0;
}
9.4 スレッドセーフな errno
/* errno は TLS (Thread-Local Storage) で実装されている */
/* glibc では以下のように定義 */
/* #define errno (*__errno_location()) */
#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
void *thread_func(void *arg)
{
int thread_id = *(int *)arg;
/* 意図的にエラーを発生させる */
open("/nonexistent", O_RDONLY);
printf("Thread %d: errno = %d (%s)\n",
thread_id, errno, strerror(errno));
return NULL;
}
int main(void)
{
pthread_t t1, t2;
int id1 = 1, id2 = 2;
pthread_create(&t1, NULL, thread_func, &id1);
pthread_create(&t2, NULL, thread_func, &id2);
pthread_join(t1, NULL);
pthread_join(t2, NULL);
/* 各スレッドの errno は独立 */
return 0;
}
9.5 errno の全一覧確認
# errno の全一覧
$ errno -l
EPERM 1 Operation not permitted
ENOENT 2 No such file or directory
ESRCH 3 No such process
EINTR 4 Interrupted system call
EIO 5 Input/output error
...
# Python で確認
$ python3 -c "
import errno
for name in sorted(dir(errno)):
if name.startswith('E'):
print(f'{name:20s} = {getattr(errno, name)}')"
# C プログラムで全エラーコード出力
$ cat > /tmp/errno_list.c << 'EOF'
#include <stdio.h>
#include <string.h>
int main(void) {
for (int i = 1; i <= 133; i++) {
const char *s = strerror(i);
if (s && strncmp(s, "Unknown", 7) != 0)
printf("%3d: %s\n", i, s);
}
return 0;
}
EOF
$ gcc -o /tmp/errno_list /tmp/errno_list.c && /tmp/errno_list
10. 新しいシステムコールの追加手順
10.1 概要
Linux カーネルに新しいシステムコールを追加する手順を、ステップバイステップで解説する。ここでは例として sys_hello という簡単なシステムコールを追加する。
10.2 ステップ 1: システムコール番号の割り当て
/* arch/x86/entry/syscalls/syscall_64.tbl に追加 */
/*
* 番号 ABI 名前 エントリポイント
*/
452 common hello sys_hello
10.3 ステップ 2: プロトタイプ宣言
/* include/linux/syscalls.h に追加 */
asmlinkage long sys_hello(const char __user *name, size_t len);
10.4 ステップ 3: システムコールの実装
/* kernel/hello.c (新規作成) */
#include <linux/kernel.h>
#include <linux/syscalls.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
/**
* sys_hello - サンプルシステムコール
* @name: ユーザー空間の名前文字列
* @len: 名前の長さ
*
* カーネルログに挨拶メッセージを出力する。
* 成功時は 0、エラー時は負のエラーコードを返す。
*/
SYSCALL_DEFINE2(hello, const char __user *, name, size_t, len)
{
char *kname;
int ret = 0;
/* 引数の検証 */
if (!name || len == 0 || len > PAGE_SIZE)
return -EINVAL;
/* カーネルメモリの確保 */
kname = kmalloc(len + 1, GFP_KERNEL);
if (!kname)
return -ENOMEM;
/* ユーザー空間からデータをコピー */
if (copy_from_user(kname, name, len)) {
ret = -EFAULT;
goto out;
}
kname[len] = '\0';
/* カーネルログに出力 */
pr_info("Hello, %s! (from sys_hello, pid=%d)\n",
kname, current->pid);
out:
kfree(kname);
return ret;
}
10.5 ステップ 4: Makefile の更新
# kernel/Makefile に追加
obj-y += hello.o
10.6 ステップ 5: UAPI ヘッダの更新
/* include/uapi/asm-generic/unistd.h に追加 */
#define __NR_hello 452
__SYSCALL(__NR_hello, sys_hello)
/* __NR_syscalls の更新 */
#undef __NR_syscalls
#define __NR_syscalls 453
10.7 ステップ 6: テストプログラムの作成
/* test_hello.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/syscall.h>
/* 新しいシステムコール番号 */
#ifndef __NR_hello
#define __NR_hello 452
#endif
int main(void)
{
const char *name = "Linux Kernel";
long ret;
ret = syscall(__NR_hello, name, strlen(name));
if (ret == 0) {
printf("sys_hello succeeded!\n");
} else {
perror("sys_hello failed");
}
/* エラーケースのテスト */
ret = syscall(__NR_hello, NULL, 0);
printf("NULL test: ret=%ld (expected -1, EINVAL)\n", ret);
return 0;
}
/*
$ gcc -o test_hello test_hello.c
$ ./test_hello
sys_hello succeeded!
NULL test: ret=-1 (expected -1, EINVAL)
$ dmesg | tail -1
[12345.678] Hello, Linux Kernel! (from sys_hello, pid=1234)
*/
10.8 ステップ 7: カーネルのビルドとテスト
# カーネルソースのディレクトリで
$ cd /usr/src/linux
# 設定の確認
$ make menuconfig # 必要に応じて
# カーネルのビルド
$ make -j$(nproc)
# モジュールのインストール
$ sudo make modules_install
# カーネルのインストール
$ sudo make install
# ブートローダーの更新
$ sudo grub-mkconfig -o /boot/grub/grub.cfg
# 再起動してテスト
$ sudo reboot
10.9 システムコール追加時の注意事項
- 後方互換性: 一度追加したシステムコールは削除できない
- ABI の安定性: 引数の型や意味を変更してはいけない
- 拡張性: 将来の拡張のために flags 引数を設けることを推奨
- セキュリティ: 必ずユーザー入力を検証する
- 32-bit 互換: compat ハンドラも必要に応じて実装する
/* 良い設計の例: flags 引数を持つ */
SYSCALL_DEFINE3(hello2, const char __user *, name, size_t, len,
unsigned int, flags)
{
/* 未知のフラグを拒否 (将来の拡張に対応) */
if (flags & ~(HELLO_FLAG_VERBOSE | HELLO_FLAG_LOG))
return -EINVAL;
/* ... */
}
11. ファイル I/O 系システムコール
11.1 openat システムコール
openat は open の拡張版で、ディレクトリファイルディスクリプタを基準としたパス解決を行う。
/* カーネル実装 */
/* fs/open.c */
SYSCALL_DEFINE4(openat, int, dfd, const char __user *, filename,
int, flags, umode_t, mode)
{
if (force_o_largefile())
flags |= O_LARGEFILE;
return do_sys_open(dfd, filename, flags, mode);
}
long do_sys_open(int dfd, const char __user *filename,
int flags, umode_t mode)
{
struct open_how how = build_open_how(flags, mode);
return do_sys_openat2(dfd, filename, &how);
}
/* ユーザー空間での使用例 */
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
/* 基本的な openat の使用 */
int dirfd = open("/etc", O_RDONLY | O_DIRECTORY);
if (dirfd < 0) {
perror("open directory");
return 1;
}
/* dirfd を基準として "hosts" を開く → /etc/hosts */
int fd = openat(dirfd, "hosts", O_RDONLY);
if (fd < 0) {
perror("openat");
close(dirfd);
return 1;
}
char buf[256];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
if (n > 0) {
buf[n] = '\0';
printf("First %zd bytes:\n%s\n", n, buf);
}
close(fd);
close(dirfd);
/* AT_FDCWD: カレントディレクトリ基準 (open と同等) */
fd = openat(AT_FDCWD, "/etc/hostname", O_RDONLY);
if (fd >= 0) {
n = read(fd, buf, sizeof(buf) - 1);
buf[n] = '\0';
printf("hostname: %s", buf);
close(fd);
}
/* openat2 (Linux 5.6+): より細かい制御 */
/* struct open_how how = {
* .flags = O_RDONLY | O_NOFOLLOW,
* .mode = 0,
* .resolve = RESOLVE_NO_SYMLINKS | RESOLVE_BENEATH
* };
* fd = syscall(SYS_openat2, dirfd, "filename", &how, sizeof(how));
*/
return 0;
}
11.2 read/write システムコール
/* カーネル実装: read */
/* fs/read_write.c */
SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
return ksys_read(fd, buf, count);
}
ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
{
struct fd f = fdget_pos(fd);
ssize_t ret = -EBADF;
if (f.file) {
loff_t pos, *ppos = file_ppos(f.file);
if (ppos) {
pos = *ppos;
ppos = &pos;
}
ret = vfs_read(f.file, buf, count, ppos);
if (ret >= 0 && ppos)
f.file->f_pos = pos;
fdput_pos(f);
}
return ret;
}
ssize_t vfs_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
ssize_t ret;
if (!(file->f_mode & FMODE_READ))
return -EBADF;
if (!(file->f_mode & FMODE_CAN_READ))
return -EINVAL;
if (unlikely(!access_ok(buf, count)))
return -EFAULT;
ret = rw_verify_area(READ, file, pos, count);
if (ret)
return ret;
if (count > MAX_RW_COUNT)
count = MAX_RW_COUNT;
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos);
else if (file->f_op->read_iter)
ret = new_sync_read(file, buf, count, pos);
else
ret = -EINVAL;
if (ret > 0) {
fsnotify_access(file);
add_rchar(current, ret);
}
inc_syscr(current);
return ret;
}
11.3 pread64 / pwrite64
ファイルオフセットを変更せずに指定位置から読み書きする:
/* 使用例 */
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
int fd = open("/tmp/testfile", O_RDWR | O_CREAT | O_TRUNC, 0644);
/* 通常の write */
write(fd, "Hello World!\n", 13);
/* pwrite: オフセット 6 から書き込み (f_pos は変更されない) */
pwrite(fd, "Linux", 5, 6);
/* ファイルの内容: "Hello Linux!\n" */
/* pread: オフセット 0 から読み取り (f_pos は変更されない) */
char buf[64];
ssize_t n = pread(fd, buf, sizeof(buf) - 1, 0);
buf[n] = '\0';
printf("Content: %s", buf); /* "Hello Linux!" */
/* pread/pwrite はマルチスレッドで安全 */
/* 各スレッドが独自のオフセットを指定できる */
close(fd);
return 0;
}
11.4 readv / writev (Scatter-Gather I/O)
/* ベクタ I/O: 複数のバッファを一度に読み書き */
#include <sys/uio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(void)
{
/* writev: 複数バッファを一度に書き込み */
struct iovec iov[3];
char header[] = "=== Header ===\n";
char body[] = "This is the body content.\n";
char footer[] = "=== Footer ===\n";
iov[0].iov_base = header;
iov[0].iov_len = strlen(header);
iov[1].iov_base = body;
iov[1].iov_len = strlen(body);
iov[2].iov_base = footer;
iov[2].iov_len = strlen(footer);
int fd = open("/tmp/scatter_test", O_WRONLY | O_CREAT | O_TRUNC, 0644);
ssize_t nwritten = writev(fd, iov, 3);
printf("writev: wrote %zd bytes\n", nwritten);
close(fd);
/* readv: 複数バッファに分散して読み取り */
char buf1[16], buf2[32], buf3[16];
struct iovec riov[3];
riov[0].iov_base = buf1;
riov[0].iov_len = sizeof(buf1);
riov[1].iov_base = buf2;
riov[1].iov_len = sizeof(buf2);
riov[2].iov_base = buf3;
riov[2].iov_len = sizeof(buf3);
fd = open("/tmp/scatter_test", O_RDONLY);
ssize_t nread = readv(fd, riov, 3);
printf("readv: read %zd bytes\n", nread);
printf("buf1: %.16s\n", buf1);
close(fd);
/* preadv2/pwritev2 (Linux 4.6+): フラグ付きオフセット指定 */
/* RWF_HIPRI: 高優先度 I/O (polling) */
/* RWF_DSYNC: データ同期書き込み */
/* RWF_SYNC: 完全同期書き込み */
/* RWF_NOWAIT: 非ブロッキング */
/* RWF_APPEND: アトミックな追記 */
return 0;
}
11.5 I/O パスの全体像
ユーザー空間:
write(fd, buf, count)
│
▼
glibc ラッパー:
SYSCALL 命令
│
▼
カーネル空間:
sys_write(fd, buf, count)
│
▼
ksys_write()
│
▼
vfs_write() ← VFS レイヤー
│
├─ セキュリティチェック (LSM)
├─ inotify 通知
│
▼
file->f_op->write_iter() ← ファイルシステムレイヤー
│
├─ ext4_file_write_iter()
├─ xfs_file_write_iter()
├─ btrfs_file_write_iter()
│
▼
ページキャッシュ / Direct I/O
│
▼
ブロックレイヤー (bio)
│
▼
I/O スケジューラ
│
▼
デバイスドライバ
│
▼
ハードウェア (HDD/SSD/NVMe)
12. プロセス管理系システムコール
12.1 clone3 システムコール
clone3 は clone の改良版で、構造体を介して多くのパラメータを渡せる。
/* カーネル実装 */
/* kernel/fork.c */
SYSCALL_DEFINE2(clone3, struct clone_args __user *, uargs, size_t, size)
{
int err;
struct kernel_clone_args kargs;
pid_t set_tid[MAX_PID_NS_LEVEL];
kargs.set_tid = set_tid;
err = copy_clone_args_from_user(&kargs, uargs, size);
if (err)
return err;
if (!clone3_args_valid(&kargs))
return -EINVAL;
return kernel_clone(&kargs);
}
/* ユーザー空間での使用例 */
#define _GNU_SOURCE
#include <linux/sched.h>
#include <sys/syscall.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#define STACK_SIZE (1024 * 1024) /* 1MB */
int main(void)
{
struct clone_args args;
char *stack;
pid_t pid;
/* スタックの確保 */
stack = mmap(NULL, STACK_SIZE,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK,
-1, 0);
memset(&args, 0, sizeof(args));
args.flags = CLONE_VM | CLONE_FS | CLONE_FILES;
args.exit_signal = SIGCHLD;
args.stack = (unsigned long)stack;
args.stack_size = STACK_SIZE;
/* pidfd を取得 (Linux 5.2+) */
args.flags |= CLONE_PIDFD;
int pidfd;
args.pidfd = (unsigned long)&pidfd;
pid = syscall(SYS_clone3, &args, sizeof(args));
if (pid == 0) {
/* 子プロセス */
printf("Child: PID=%d, PPID=%d\n", getpid(), getppid());
_exit(42);
} else if (pid > 0) {
/* 親プロセス */
printf("Parent: child PID=%d, pidfd=%d\n", pid, pidfd);
int status;
waitpid(pid, &status, 0);
if (WIFEXITED(status))
printf("Child exited with status %d\n", WEXITSTATUS(status));
close(pidfd);
} else {
perror("clone3");
}
return 0;
}
12.2 execveat システムコール
/* ファイルディスクリプタを基準とした exec */
/* fs/exec.c */
SYSCALL_DEFINE5(execveat, int, fd, const char __user *, filename,
const char __user *const __user *, argv,
const char __user *const __user *, envp,
int, flags)
{
return do_execveat(fd, getname_uflags(filename, flags),
argv, envp, flags);
}
/* 使用例: fexecve の実装に使用 */
#define _GNU_SOURCE
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/syscall.h>
#include <linux/fcntl.h>
int main(void)
{
/* memfd_create でメモリ上のファイルを作成 */
int memfd = syscall(SYS_memfd_create, "exec_test", 0);
if (memfd < 0) {
perror("memfd_create");
return 1;
}
/* シェルスクリプトを書き込み */
const char script[] = "#!/bin/sh\necho 'Hello from memfd exec!'\n";
write(memfd, script, sizeof(script) - 1);
/* 実行権限を設定 */
fchmod(memfd, 0755);
/* execveat でメモリ上のファイルを実行 */
char *const argv[] = { "script", NULL };
char *const envp[] = { NULL };
/* AT_EMPTY_PATH: fd 自体を実行対象とする */
syscall(SYS_execveat, memfd, "", argv, envp, AT_EMPTY_PATH);
perror("execveat"); /* 成功したらここには来ない */
return 1;
}
12.3 waitid システムコール
/* waitid: 柔軟なプロセス待機 */
#include <sys/wait.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main(void)
{
pid_t pid = fork();
if (pid == 0) {
/* 子プロセス */
sleep(1);
exit(42);
}
siginfo_t info;
int ret;
/* waitid: siginfo_t で詳細情報を取得 */
ret = waitid(P_PID, pid, &info, WEXITED);
if (ret == 0) {
printf("Child PID: %d\n", info.si_pid);
printf("Exit status: %d\n", info.si_status);
printf("Exit code: %d\n", info.si_code);
printf(" CLD_EXITED=%d, CLD_KILLED=%d, CLD_DUMPED=%d\n",
CLD_EXITED, CLD_KILLED, CLD_DUMPED);
printf(" si_code=%d → %s\n", info.si_code,
info.si_code == CLD_EXITED ? "正常終了" :
info.si_code == CLD_KILLED ? "シグナルで終了" :
info.si_code == CLD_DUMPED ? "コアダンプ" : "不明");
printf("User time: %ld.%06ld sec\n",
info.si_utime / 1000000, info.si_utime % 1000000);
printf("System time: %ld.%06ld sec\n",
info.si_stime / 1000000, info.si_stime % 1000000);
}
/* WNOHANG: 非ブロッキング待機 */
/* WNOWAIT: 子プロセスの状態を消費しない (再度waitid可能) */
/* WSTOPPED: 停止した子プロセスを報告 */
/* WCONTINUED: 再開した子プロセスを報告 */
return 0;
}
13. メモリ管理系システムコール
13.1 mmap システムコール
/* カーネル実装 */
/* arch/x86/kernel/sys_x86_64.c */
SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,
unsigned long, prot, unsigned long, flags,
unsigned long, fd, unsigned long, off)
{
if (off & ~PAGE_MASK)
return -EINVAL;
return ksys_mmap_pgoff(addr, len, prot, flags, fd, off >> PAGE_SHIFT);
}
/* 包括的な mmap 使用例 */
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main(void)
{
/* 1. 匿名マッピング (ヒープ代替) */
void *anon = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
if (anon == MAP_FAILED) {
perror("mmap anonymous");
return 1;
}
strcpy(anon, "Anonymous mapping works!");
printf("1. %s\n", (char *)anon);
/* 2. ファイルマッピング (読み取り) */
int fd = open("/etc/hostname", O_RDONLY);
off_t size = lseek(fd, 0, SEEK_END);
void *file_map = mmap(NULL, size, PROT_READ,
MAP_PRIVATE, fd, 0);
close(fd);
printf("2. hostname: %.*s", (int)size, (char *)file_map);
munmap(file_map, size);
/* 3. 共有匿名マッピング (プロセス間通信) */
void *shared = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1, 0);
pid_t pid = fork();
if (pid == 0) {
strcpy(shared, "Message from child!");
_exit(0);
}
waitpid(pid, NULL, 0);
printf("3. Parent received: %s\n", (char *)shared);
munmap(shared, 4096);
/* 4. 大きなページ (Huge Pages) */
void *huge = mmap(NULL, 2 * 1024 * 1024, /* 2MB */
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB,
-1, 0);
if (huge != MAP_FAILED) {
printf("4. Huge page mapping succeeded\n");
munmap(huge, 2 * 1024 * 1024);
} else {
printf("4. Huge page not available (need to configure)\n");
}
/* 5. 固定アドレスマッピング */
void *fixed = mmap((void *)0x10000000, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED_NOREPLACE,
-1, 0);
if (fixed != MAP_FAILED) {
printf("5. Fixed mapping at %p\n", fixed);
munmap(fixed, 4096);
}
munmap(anon, 4096);
return 0;
}
13.2 mprotect システムコール
/* メモリ保護属性の変更 */
#include <sys/mman.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <setjmp.h>
static sigjmp_buf jmpbuf;
void segfault_handler(int sig)
{
siglongjmp(jmpbuf, 1);
}
int main(void)
{
/* 読み書き可能なマッピングを作成 */
void *page = mmap(NULL, 4096,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
strcpy(page, "Initial data");
printf("Before mprotect: %s\n", (char *)page);
/* 読み取り専用に変更 */
if (mprotect(page, 4096, PROT_READ) != 0) {
perror("mprotect");
return 1;
}
/* シグナルハンドラを設定 */
signal(SIGSEGV, segfault_handler);
/* 読み取り専用ページへの書き込みを試行 */
if (sigsetjmp(jmpbuf, 1) == 0) {
strcpy(page, "Try to write"); /* SIGSEGV! */
printf("Should not reach here\n");
} else {
printf("Caught SIGSEGV: write to read-only page\n");
}
/* 再度書き込み可能にする */
mprotect(page, 4096, PROT_READ | PROT_WRITE);
strcpy(page, "Writable again!");
printf("After re-mprotect: %s\n", (char *)page);
/* 実行可能に変更 (JIT コンパイルなど) */
/* mprotect(page, 4096, PROT_READ | PROT_EXEC); */
/* PROT_NONE: アクセス不可 (ガードページ) */
mprotect(page, 4096, PROT_NONE);
munmap(page, 4096);
return 0;
}
13.3 madvise / process_madvise
/* madvise: カーネルにメモリ使用パターンを通知 */
#include <sys/mman.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
size_t size = 100 * 1024 * 1024; /* 100MB */
void *mem = mmap(NULL, size,
PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS,
-1, 0);
/* MADV_SEQUENTIAL: シーケンシャルアクセス (先読みを積極的に) */
madvise(mem, size, MADV_SEQUENTIAL);
/* MADV_RANDOM: ランダムアクセス (先読みを抑制) */
madvise(mem, size, MADV_RANDOM);
/* MADV_WILLNEED: 近い将来アクセスする (ページインを事前実行) */
madvise(mem, 4096 * 10, MADV_WILLNEED);
/* MADV_DONTNEED: 不要になった (ページを解放) */
memset(mem, 0x42, size);
madvise(mem, size, MADV_DONTNEED);
/* mem にアクセスするとゼロページが返る */
/* MADV_FREE (Linux 4.5+): 遅延解放 (メモリ圧迫時に解放) */
memset(mem, 0x42, size);
madvise(mem, size, MADV_FREE);
/* メモリ圧迫がなければデータは残る */
/* MADV_HUGEPAGE: THP (Transparent Huge Page) を推奨 */
madvise(mem, size, MADV_HUGEPAGE);
/* MADV_COLD (Linux 5.4+): メモリが cold であることを通知 */
madvise(mem, size, MADV_COLD);
/* MADV_PAGEOUT (Linux 5.4+): ページをスワップアウト */
madvise(mem, size, MADV_PAGEOUT);
munmap(mem, size);
return 0;
}
/* process_madvise (Linux 5.10+): 他プロセスのメモリにアドバイス */
#include <sys/syscall.h>
#include <sys/uio.h>
#include <unistd.h>
#include <stdio.h>
int main(void)
{
pid_t target_pid = 1234; /* 対象プロセスの PID */
/* pidfd 経由でアクセス */
int pidfd = syscall(SYS_pidfd_open, target_pid, 0);
if (pidfd < 0) {
perror("pidfd_open");
return 1;
}
struct iovec iov = {
.iov_base = (void *)0x7f0000000000,
.iov_len = 4096 * 256,
};
/* 他プロセスのメモリに MADV_COLD をアドバイス */
long ret = syscall(SYS_process_madvise, pidfd, &iov, 1,
MADV_COLD, 0);
printf("process_madvise: %ld\n", ret);
close(pidfd);
return 0;
}
14. ネットワーク系システムコール
14.1 socket システムコール
/* カーネル実装 */
/* net/socket.c */
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
return __sys_socket(family, type, protocol);
}
int __sys_socket(int family, int type, int protocol)
{
struct socket *sock;
int flags;
flags = type & ~SOCK_TYPE_MASK;
if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
return -EINVAL;
type &= SOCK_TYPE_MASK;
int retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
return retval;
return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
}
14.2 sendmsg / recvmsg
/* 高度なソケット通信例: sendmsg/recvmsg */
#include <sys/socket.h>
#include <sys/un.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
/* sendmsg: 補助データ (cmsg) 付きメッセージ送信 */
int send_fd(int sock, int fd_to_send)
{
struct msghdr msg = {0};
struct iovec iov;
char buf[] = "fd";
char cmsgbuf[CMSG_SPACE(sizeof(int))];
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(sizeof(int));
memcpy(CMSG_DATA(cmsg), &fd_to_send, sizeof(int));
return sendmsg(sock, &msg, 0);
}
/* recvmsg: 補助データ付きメッセージ受信 */
int recv_fd(int sock)
{
struct msghdr msg = {0};
struct iovec iov;
char buf[16];
char cmsgbuf[CMSG_SPACE(sizeof(int))];
int fd = -1;
iov.iov_base = buf;
iov.iov_len = sizeof(buf);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
if (recvmsg(sock, &msg, 0) < 0)
return -1;
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
if (cmsg && cmsg->cmsg_level == SOL_SOCKET &&
cmsg->cmsg_type == SCM_RIGHTS) {
memcpy(&fd, CMSG_DATA(cmsg), sizeof(int));
}
return fd;
}
int main(void)
{
int sv[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
pid_t pid = fork();
if (pid == 0) {
/* 子プロセス: /etc/hostname の fd を親に送信 */
close(sv[0]);
int fd = open("/etc/hostname", 0);
send_fd(sv[1], fd);
close(fd);
close(sv[1]);
_exit(0);
}
/* 親プロセス: fd を受信 */
close(sv[1]);
int received_fd = recv_fd(sv[0]);
if (received_fd >= 0) {
char buf[256];
ssize_t n = read(received_fd, buf, sizeof(buf) - 1);
buf[n] = '\0';
printf("Received fd=%d, content: %s", received_fd, buf);
close(received_fd);
}
close(sv[0]);
waitpid(pid, NULL, 0);
return 0;
}
15. I/O 多重化系システムコール
15.1 epoll
/* epoll: 高性能 I/O イベント通知 */
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#define MAX_EVENTS 64
#define PORT 8080
/* ソケットを非ブロッキングに設定 */
int set_nonblocking(int fd)
{
int flags = fcntl(fd, F_GETFL, 0);
return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}
int main(void)
{
/* サーバーソケットの作成 */
int server_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
int opt = 1;
setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
struct sockaddr_in addr = {
.sin_family = AF_INET,
.sin_addr.s_addr = INADDR_ANY,
.sin_port = htons(PORT),
};
bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));
listen(server_fd, SOMAXCONN);
/* epoll インスタンスの作成 */
int epfd = epoll_create1(EPOLL_CLOEXEC);
/* サーバーソケットを epoll に登録 */
struct epoll_event ev = {
.events = EPOLLIN | EPOLLET, /* Edge Triggered */
.data.fd = server_fd,
};
epoll_ctl(epfd, EPOLL_CTL_ADD, server_fd, &ev);
struct epoll_event events[MAX_EVENTS];
printf("Server listening on port %d\n", PORT);
/* イベントループ */
while (1) {
int nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (nfds < 0) {
if (errno == EINTR)
continue;
perror("epoll_wait");
break;
}
for (int i = 0; i < nfds; i++) {
if (events[i].data.fd == server_fd) {
/* 新しい接続を受け付け */
while (1) {
struct sockaddr_in client;
socklen_t len = sizeof(client);
int cfd = accept4(server_fd,
(struct sockaddr *)&client,
&len,
SOCK_NONBLOCK | SOCK_CLOEXEC);
if (cfd < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
break;
perror("accept");
break;
}
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
printf("New connection: fd=%d\n", cfd);
}
} else {
/* データ受信 */
char buf[1024];
ssize_t n = read(events[i].data.fd, buf, sizeof(buf));
if (n <= 0) {
epoll_ctl(epfd, EPOLL_CTL_DEL,
events[i].data.fd, NULL);
close(events[i].data.fd);
printf("Connection closed: fd=%d\n",
events[i].data.fd);
} else {
write(events[i].data.fd, buf, n); /* エコー */
}
}
}
}
close(epfd);
close(server_fd);
return 0;
}
15.2 io_uring (Linux 5.1+)
/* io_uring: 高性能非同期 I/O フレームワーク */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <linux/io_uring.h>
#include <sys/mman.h>
#include <stdatomic.h>
/* io_uring のセットアップ (低レベル syscall 版) */
struct io_uring_context {
int ring_fd;
struct io_uring_sqe *sqes;
unsigned *sq_array;
struct io_uring_cqe *cqes;
unsigned sq_mask;
unsigned cq_mask;
unsigned *sq_tail;
unsigned *sq_head;
unsigned *cq_head;
unsigned *cq_tail;
};
int io_uring_setup(unsigned entries, struct io_uring_params *p)
{
return syscall(SYS_io_uring_setup, entries, p);
}
int io_uring_enter(int fd, unsigned to_submit, unsigned min_complete,
unsigned flags, sigset_t *sig)
{
return syscall(SYS_io_uring_enter, fd, to_submit,
min_complete, flags, sig, 0);
}
int main(void)
{
struct io_uring_params params;
memset(¶ms, 0, sizeof(params));
/* io_uring のセットアップ */
int ring_fd = io_uring_setup(32, ¶ms);
if (ring_fd < 0) {
perror("io_uring_setup");
return 1;
}
printf("io_uring setup successful:\n");
printf(" SQ entries: %u\n", params.sq_entries);
printf(" CQ entries: %u\n", params.cq_entries);
printf(" Features: 0x%x\n", params.features);
/* SQ (Submission Queue) のマッピング */
size_t sq_ring_sz = params.sq_off.array +
params.sq_entries * sizeof(unsigned);
void *sq_ring = mmap(NULL, sq_ring_sz,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE,
ring_fd, IORING_OFF_SQ_RING);
/* SQE (Submission Queue Entry) のマッピング */
size_t sqe_sz = params.sq_entries * sizeof(struct io_uring_sqe);
struct io_uring_sqe *sqes = mmap(NULL, sqe_sz,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE,
ring_fd, IORING_OFF_SQES);
/* CQ (Completion Queue) のマッピング */
size_t cq_ring_sz = params.cq_off.cqes +
params.cq_entries * sizeof(struct io_uring_cqe);
void *cq_ring = mmap(NULL, cq_ring_sz,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_POPULATE,
ring_fd, IORING_OFF_CQ_RING);
printf("io_uring rings mapped successfully\n");
/* ファイル読み取りのサブミッション */
int fd = open("/etc/hostname", O_RDONLY);
char buf[256] = {0};
unsigned *sq_tail = sq_ring + params.sq_off.tail;
unsigned *sq_array = sq_ring + params.sq_off.array;
unsigned tail = *sq_tail;
unsigned index = tail & (params.sq_entries - 1);
struct io_uring_sqe *sqe = &sqes[index];
memset(sqe, 0, sizeof(*sqe));
sqe->opcode = IORING_OP_READ;
sqe->fd = fd;
sqe->addr = (unsigned long)buf;
sqe->len = sizeof(buf) - 1;
sqe->off = 0;
sqe->user_data = 42;
sq_array[index] = index;
atomic_store_explicit((_Atomic unsigned *)sq_tail,
tail + 1, memory_order_release);
/* サブミット */
int ret = io_uring_enter(ring_fd, 1, 1, IORING_ENTER_GETEVENTS, NULL);
if (ret < 0) {
perror("io_uring_enter");
} else {
/* CQE を読み取り */
unsigned *cq_head_ptr = cq_ring + params.cq_off.head;
unsigned *cq_tail_ptr = cq_ring + params.cq_off.tail;
struct io_uring_cqe *cqes_base =
cq_ring + params.cq_off.cqes;
unsigned head = atomic_load_explicit(
(_Atomic unsigned *)cq_head_ptr, memory_order_acquire);
unsigned cq_tail_val = atomic_load_explicit(
(_Atomic unsigned *)cq_tail_ptr, memory_order_acquire);
if (head != cq_tail_val) {
struct io_uring_cqe *cqe =
&cqes_base[head & (params.cq_entries - 1)];
printf("CQE result: %d, user_data: %lld\n",
cqe->res, cqe->user_data);
if (cqe->res > 0)
printf("Data: %s", buf);
atomic_store_explicit((_Atomic unsigned *)cq_head_ptr,
head + 1, memory_order_release);
}
}
close(fd);
close(ring_fd);
return 0;
}
/* liburing を使った簡単な例 (推奨) */
/*
#include <liburing.h>
#include <fcntl.h>
#include <stdio.h>
int main(void)
{
struct io_uring ring;
io_uring_queue_init(32, &ring, 0);
int fd = open("/etc/hostname", O_RDONLY);
char buf[256] = {0};
struct io_uring_sqe *sqe = io_uring_get_sqe(&ring);
io_uring_prep_read(sqe, fd, buf, sizeof(buf) - 1, 0);
io_uring_sqe_set_data(sqe, (void *)42);
io_uring_submit(&ring);
struct io_uring_cqe *cqe;
io_uring_wait_cqe(&ring, &cqe);
printf("Result: %d bytes, data: %s", cqe->res, buf);
io_uring_cqe_seen(&ring, cqe);
close(fd);
io_uring_queue_exit(&ring);
return 0;
}
*/
16. その他の重要なシステムコール
16.1 ioctl
/* ioctl: デバイス固有の操作 */
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <stdio.h>
#include <linux/fs.h>
#include <fcntl.h>
int main(void)
{
/* 1. ターミナルサイズの取得 */
struct winsize ws;
if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0) {
printf("Terminal size: %d rows x %d cols\n",
ws.ws_row, ws.ws_col);
}
/* 2. ブロックデバイスのサイズ取得 */
int fd = open("/dev/sda", O_RDONLY);
if (fd >= 0) {
unsigned long long size;
if (ioctl(fd, BLKGETSIZE64, &size) == 0) {
printf("Disk size: %llu bytes (%.1f GB)\n",
size, size / 1e9);
}
close(fd);
}
/* 3. ファイルシステムの freeze/thaw (要 root) */
/*
int dirfd = open("/mnt/data", O_RDONLY);
ioctl(dirfd, FIFREEZE, 0); // freeze
// ... スナップショット取得 ...
ioctl(dirfd, FITHAW, 0); // thaw
close(dirfd);
*/
return 0;
}
16.2 prctl
/* prctl: プロセス制御操作 */
#include <sys/prctl.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
int main(void)
{
char name[16];
/* 1. プロセス名の設定/取得 */
prctl(PR_SET_NAME, "my_daemon");
prctl(PR_GET_NAME, name);
printf("Process name: %s\n", name);
/* 2. 親プロセス死亡時のシグナル設定 */
prctl(PR_SET_PDEATHSIG, SIGHUP);
/* 3. dumpable フラグ */
prctl(PR_SET_DUMPABLE, 0); /* コアダンプを無効化 */
/* 4. No New Privileges */
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
/* → setuid/setgid, ファイルケーパビリティ等を無効化 */
/* 5. Seccomp モード設定 */
/* prctl(PR_SET_SECCOMP, SECCOMP_MODE_STRICT); */
/* 6. タイマースラック */
prctl(PR_SET_TIMERSLACK, 50000); /* 50us */
unsigned int slack;
prctl(PR_GET_TIMERSLACK, &slack);
printf("Timer slack: %u ns\n", slack);
/* 7. 子プロセスのsubreaper設定 */
prctl(PR_SET_CHILD_SUBREAPER, 1);
/* → 孤児プロセスの親が init ではなく自分になる */
return 0;
}
16.3 seccomp
/* seccomp: システムコールフィルタリング */
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/prctl.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <stdio.h>
#include <stddef.h>
int main(void)
{
/* No New Privileges が必要 */
prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
/* BPF フィルタの定義 */
struct sock_filter filter[] = {
/* アーキテクチャの確認 */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
offsetof(struct seccomp_data, arch)),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,
AUDIT_ARCH_X86_64, 1, 0),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
/* システムコール番号の取得 */
BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
offsetof(struct seccomp_data, nr)),
/* 許可するシステムコール */
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_write, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit_group, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_exit, 0, 1),
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
/* それ以外は拒否 */
BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL),
};
struct sock_fprog prog = {
.len = sizeof(filter) / sizeof(filter[0]),
.filter = filter,
};
/* seccomp フィルタを適用 */
if (syscall(SYS_seccomp, SECCOMP_SET_MODE_FILTER, 0, &prog)) {
perror("seccomp");
return 1;
}
printf("seccomp filter installed!\n");
/* write は許可されているので動作する */
const char *msg = "write works under seccomp!\n";
write(STDOUT_FILENO, msg, strlen(msg));
/* open は許可されていないので SIGKILL される */
/* open("/etc/passwd", O_RDONLY); <-- プロセスが kill される */
return 0;
}
# seccomp のデバッグ
$ strace -e trace=seccomp ./seccomp_test
seccomp(SECCOMP_SET_MODE_FILTER, 0, {len=12, filter=0x7fff...}) = 0
# seccomp プロファイルの確認
$ cat /proc/<pid>/status | grep Seccomp
Seccomp: 2
Seccomp_filters: 1
# 0: disabled, 1: strict, 2: filter
17. システムコールのオーバーヘッドと最適化
17.1 オーバーヘッドの内訳
システムコールのオーバーヘッド内訳 (概算):
┌────────────────────────────────────────┬─────────┐
│ ステップ │ 時間 │
├────────────────────────────────────────┼─────────┤
│ SYSCALL 命令 (モード遷移) │ ~20 ns │
│ swapgs + スタック切り替え │ ~10 ns │
│ KPTI: CR3 切り替え (入口) │ ~50 ns │
│ レジスタ保存 (pt_regs 構築) │ ~10 ns │
│ seccomp チェック │ ~5 ns │
│ audit チェック │ ~5 ns │
│ 実際のシステムコール処理 │ 可変 │
│ レジスタ復元 │ ~10 ns │
│ KPTI: CR3 切り替え (出口) │ ~50 ns │
│ SYSRET 命令 (モード復帰) │ ~20 ns │
├────────────────────────────────────────┼─────────┤
│ 合計 (カーネル処理除く) │ ~180 ns │
│ Spectre 対策込み │ ~300 ns │
└────────────────────────────────────────┴─────────┘
17.2 最適化テクニック
/* 1. システムコールのバッチ処理 (readv/writev) */
/* 複数の小さな read/write を一つにまとめる */
struct iovec iov[10];
for (int i = 0; i < 10; i++) {
iov[i].iov_base = buffers[i];
iov[i].iov_len = sizes[i];
}
writev(fd, iov, 10); /* 1回の syscall で10個のバッファを書き込み */
/* 2. VDSO 対応の関数を使う */
/* clock_gettime() は VDSO 経由で高速 */
/* gettimeofday() も VDSO 経由 */
/* 3. io_uring で非同期バッチ処理 */
/* 複数の I/O 操作を一度にサブミット */
/* 4. mmap で read/write を回避 */
/* 頻繁にアクセスするファイルは mmap が効率的 */
/* 5. sendfile/splice でカーネル内コピーを回避 */
#include <sys/sendfile.h>
sendfile(out_fd, in_fd, &offset, count);
/* ユーザー空間へのコピーなしにファイル間転送 */
/* 6. copy_file_range (Linux 4.5+) */
/* サーバーサイドコピー対応 */
#include <unistd.h>
copy_file_range(fd_in, &off_in, fd_out, &off_out, len, 0);
17.3 システムコール回数の削減
# strace でシステムコール回数を計測
$ strace -c ls /tmp
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
25.00 0.000050 5 10 mmap
15.00 0.000030 4 8 mprotect
10.00 0.000020 3 7 openat
10.00 0.000020 3 7 close
10.00 0.000020 3 7 fstat
8.00 0.000016 3 5 read
5.00 0.000010 3 3 brk
...
------ ----------- ----------- --------- --------- ----------------
100.00 0.000200 67 2 total
17.4 ベンチマーク
/* システムコールのレイテンシ測定 */
#include <stdio.h>
#include <time.h>
#include <unistd.h>
#include <sys/syscall.h>
#define ITERATIONS 1000000
#define CLOCK CLOCK_MONOTONIC
static inline unsigned long long rdtsc(void)
{
unsigned int lo, hi;
asm volatile ("rdtsc" : "=a" (lo), "=d" (hi));
return ((unsigned long long)hi << 32) | lo;
}
int main(void)
{
struct timespec ts;
unsigned long long start, end, total;
/* getpid (最も軽量なシステムコール) */
total = 0;
for (int i = 0; i < ITERATIONS; i++) {
start = rdtsc();
syscall(SYS_getpid);
end = rdtsc();
total += (end - start);
}
printf("getpid: %llu cycles/call (~%.0f ns @ 3GHz)\n",
total / ITERATIONS, (double)total / ITERATIONS / 3.0);
/* clock_gettime via VDSO */
total = 0;
for (int i = 0; i < ITERATIONS; i++) {
start = rdtsc();
clock_gettime(CLOCK, &ts);
end = rdtsc();
total += (end - start);
}
printf("clock_gettime: %llu cycles/call (~%.0f ns @ 3GHz) [VDSO]\n",
total / ITERATIONS, (double)total / ITERATIONS / 3.0);
/* clock_gettime via syscall */
total = 0;
for (int i = 0; i < ITERATIONS; i++) {
start = rdtsc();
syscall(SYS_clock_gettime, CLOCK, &ts);
end = rdtsc();
total += (end - start);
}
printf("clock_gettime: %llu cycles/call (~%.0f ns @ 3GHz) [syscall]\n",
total / ITERATIONS, (double)total / ITERATIONS / 3.0);
return 0;
}
18. 互換性レイヤー (compat syscalls)
18.1 32-bit 互換の仕組み
x86_64 Linux では、32-bit アプリケーションの実行をサポートするために互換レイヤーが提供される。
/* arch/x86/entry/syscall_32.c */
/* 32-bit 互換テーブル */
asmlinkage const sys_call_table_t ia32_sys_call_table[__NR_ia32_syscall_max+1] = {
[0 ... __NR_ia32_syscall_max] = __ia32_sys_ni_syscall,
#include <asm/syscalls_32.h>
};
18.2 compat ハンドラの実装例
/* 構造体サイズの違いを吸収する compat ハンドラ */
/* fs/stat.c */
COMPAT_SYSCALL_DEFINE2(stat, const char __user *, filename,
struct compat_stat __user *, statbuf)
{
struct kstat stat;
int error;
error = vfs_stat(filename, &stat);
if (error)
return error;
return cp_compat_stat(&stat, statbuf);
}
/* compat_stat: 32-bit 用のサイズに変換 */
struct compat_stat {
compat_dev_t st_dev; /* 32-bit デバイス番号 */
compat_ino_t st_ino; /* 32-bit inode */
compat_mode_t st_mode;
compat_nlink_t st_nlink;
/* ... 他のフィールドも 32-bit サイズ ... */
};
18.3 32-bit バイナリの実行確認
# 32-bit サポートの確認
$ file /lib32/libc.so.6
/lib32/libc.so.6: ELF 32-bit LSB shared object, Intel 80386
# CONFIG_IA32_EMULATION の確認
$ zcat /proc/config.gz | grep IA32_EMULATION
CONFIG_IA32_EMULATION=y
# 32-bit バイナリの strace
$ strace -f /usr/bin/linux32 /bin/echo hello 2>&1 | head
# → ia32_sys_call_table 経由で処理される
18.4 x32 ABI
/* x32: 32-bit ポインタ + 64-bit レジスタの ABI */
/* システムコール番号に __X32_SYSCALL_BIT (0x40000000) を OR する */
#define __X32_SYSCALL_BIT 0x40000000
/* x32 のシステムコール番号 */
/* __NR_read = 0x40000000 | 0 = 0x40000000 */
/* 確認方法 */
/* $ gcc -mx32 -o test test.c */
/* ※ x32 ABI は多くのディストリビューションで無効化されている */
19. システムコールの監査 (auditd)
19.1 Audit フレームワークの概要
┌─────────────┐ ┌──────────────┐
│ auditd │ │ auditctl │
│ (デーモン) │ │ (設定ツール) │
└──────┬──────┘ └──────┬───────┘
│ │
│ Netlink │ Netlink
│ │
┌──────┴──────────────────┴───────┐
│ カーネル Audit サブシステム │
│ │
│ ┌──────────────────────────┐ │
│ │ syscall_trace_enter() │ │
│ │ → audit_syscall_entry() │ │
│ └──────────────────────────┘ │
│ ┌──────────────────────────┐ │
│ │ syscall_trace_exit() │ │
│ │ → audit_syscall_exit() │ │
│ └──────────────────────────┘ │
└──────────────────────────────────┘
19.2 Audit ルールの設定
# auditd のインストールと起動
$ sudo apt install auditd audispd-plugins # Debian/Ubuntu
$ sudo yum install audit # RHEL/CentOS
$ sudo systemctl enable --now auditd
# ルールの追加: ファイルアクセス監視
$ sudo auditctl -w /etc/passwd -p rwa -k password_changes
# -w: 監視対象ファイル
# -p: パーミッション (r:read, w:write, a:attr, x:exec)
# -k: 検索用キー
# ルールの追加: システムコール監視
$ sudo auditctl -a always,exit -F arch=b64 -S execve -k exec_log
# -a: アクション (always,exit / never,exit)
# -F: フィルタ条件
# -S: システムコール名
# 特定ユーザーの openat を監視
$ sudo auditctl -a always,exit -F arch=b64 -S openat \
-F auid=1000 -F key=user_file_access
# ネットワーク関連のシステムコール監視
$ sudo auditctl -a always,exit -F arch=b64 \
-S socket -S connect -S accept -S sendto -S recvfrom \
-k network_activity
# 現在のルール一覧
$ sudo auditctl -l
-w /etc/passwd -p rwa -k password_changes
-a always,exit -F arch=b64 -S execve -F key=exec_log
...
# ルールの削除
$ sudo auditctl -D # 全ルール削除
19.3 監査ログの検索
# ausearch: 監査ログの検索
$ sudo ausearch -k password_changes
----
time->Fri Apr 10 10:30:15 2026
type=SYSCALL msg=audit(1744274415.123:456): arch=c000003e syscall=257
success=yes exit=3 a0=ffffff9c a1=7ffd1234 a2=241 a3=1b6
items=2 ppid=1000 pid=1234 auid=1000 uid=0 gid=0 euid=0
suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=pts0 ses=1
comm="vim" exe="/usr/bin/vim" key="password_changes"
# キーで検索
$ sudo ausearch -k exec_log --start today
# プログラム名で検索
$ sudo ausearch -c "ssh"
# システムコール番号で検索
$ sudo ausearch --syscall openat
# aureport: 監査レポート生成
$ sudo aureport --syscall --start today
Syscall Report
=======================================
# date time syscall pid comm auid event
1. 04/10/2026 10:30:15 257 1234 vim 1000 456
2. 04/10/2026 10:31:22 59 5678 bash 1000 457
...
# サマリーレポート
$ sudo aureport --summary
Summary Report
======================
Range of time in logs: 04/01/2026 - 04/10/2026
Number of changes in configuration: 5
Number of failed logins: 3
Number of successful logins: 42
...
19.4 永続的なルール設定
# /etc/audit/rules.d/ にルールファイルを配置
$ sudo cat > /etc/audit/rules.d/syscall-monitor.rules << 'EOF'
# バッファサイズ
-b 8192
# 既存ルールの削除
-D
# 自分自身の設定変更を監視
-w /etc/audit/ -p wa -k audit_config
# ファイルシステムの重要ファイル
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/sudoers -p wa -k sudo_config
# プロセス実行の監視
-a always,exit -F arch=b64 -S execve -F key=exec_log
-a always,exit -F arch=b32 -S execve -F key=exec_log
# ネットワーク接続の監視
-a always,exit -F arch=b64 -S connect -F key=network
-a always,exit -F arch=b64 -S accept -F key=network
# カーネルモジュールのロード
-a always,exit -F arch=b64 -S init_module -S finit_module -k modules
-a always,exit -F arch=b64 -S delete_module -k modules
# マウント操作
-a always,exit -F arch=b64 -S mount -S umount2 -k mounts
# 設定を不変にする (再起動するまでルール変更不可)
-e 2
EOF
# ルールの再読み込み
$ sudo augenrules --load
20. デバッグ・解析ツール
20.1 strace
# 基本的な使い方
$ strace ls /tmp
execve("/usr/bin/ls", ["ls", "/tmp"], 0x7ffc... /* 50 vars */) = 0
...
openat(AT_FDCWD, "/tmp", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
...
# 特定のシステムコールのみ追跡
$ strace -e trace=openat,read,write cat /etc/hostname
# ネットワーク関連のみ
$ strace -e trace=network curl -s http://example.com > /dev/null
# ファイル操作関連のみ
$ strace -e trace=file ls /tmp
# メモリ関連のみ
$ strace -e trace=memory ./my_program
# プロセス関連のみ
$ strace -e trace=process bash -c 'ls'
# 統計情報
$ strace -c -S time ls /tmp 2>&1 | tail -20
% time seconds usecs/call calls errors syscall
------ ----------- ----------- --------- --------- ----------------
33.33 0.000020 2 10 mmap
16.67 0.000010 1 7 close
16.67 0.000010 1 8 mprotect
...
# タイムスタンプ付き
$ strace -tt -T ls /tmp 2>&1 | head
10:30:15.123456 execve("/usr/bin/ls", ...) = 0 <0.000456>
10:30:15.124000 brk(NULL) = 0x561234 <0.000003>
# -tt: マイクロ秒精度のタイムスタンプ
# -T: システムコールの実行時間
# 実行中のプロセスにアタッチ
$ strace -p <PID>
# 子プロセスも追跡
$ strace -f -e trace=process bash -c 'ls | wc'
# ファイルディスクリプタのパスを表示
$ strace -y -e trace=read,write cat /etc/hostname
read(3</etc/hostname>, "myhost\n", 131072) = 7
write(1</dev/pts/0>, "myhost\n", 7) = 7
# 文字列の最大長を変更
$ strace -s 1024 -e trace=write echo "long string..."
# 出力をファイルに保存
$ strace -o /tmp/trace.log ls /tmp
20.2 ltrace
# ltrace: ライブラリ関数呼び出しのトレース
$ ltrace ls /tmp 2>&1 | head
__libc_start_main(0x555... , 2, 0x7ffc..., ...)
setlocale(LC_ALL, "")
bindtextdomain("coreutils", "/usr/share/locale")
textdomain("coreutils")
isatty(1) = 1
getenv("QUOTING_STYLE") = nil
...
opendir("/tmp") = 0x555...
readdir(0x555...) = {...}
...
# ライブラリとシステムコールの両方
$ ltrace -S ls /tmp 2>&1 | head
# 特定のライブラリ関数のみ
$ ltrace -e malloc+free ls /tmp
20.3 ausyscall
# ausyscall: システムコール番号の変換
$ ausyscall --dump
Using x86_64 syscall table:
0 read
1 write
2 open
3 close
...
# 名前から番号
$ ausyscall openat
257
# 番号から名前
$ ausyscall 257
openat
# 32-bit テーブル
$ ausyscall i386 openat
295
# 全アーキテクチャ
$ ausyscall --dump | wc -l
20.4 perf trace
# perf trace: 高速なシステムコールトレーサー
$ sudo perf trace ls /tmp
0.000 ( 0.010 ms): ls/12345 execve(filename: /usr/bin/ls) = 0
0.100 ( 0.003 ms): ls/12345 brk(brk: 0) = 0x563a
0.200 ( 0.005 ms): ls/12345 openat(dfd: CWD, filename: /etc/ld.so.cache) = 3
...
# 統計モード
$ sudo perf trace -s ls /tmp 2>&1 | tail -20
ls (12345), 50 events, 100.0%
syscall calls errors total min avg max
--------------- -------- ------- -------- --------- --------- ---------
openat 7 0 0.050ms 0.003ms 0.007ms 0.015ms
close 7 0 0.021ms 0.002ms 0.003ms 0.005ms
read 5 0 0.015ms 0.002ms 0.003ms 0.005ms
mmap 10 0 0.040ms 0.003ms 0.004ms 0.008ms
...
# 特定のシステムコールのみ
$ sudo perf trace -e openat,read,write ls /tmp
# 特定のイベントカテゴリ
$ sudo perf trace -e 'syscalls:sys_enter_openat' ls /tmp
# プロセス指定
$ sudo perf trace -p <PID>
# perf stat: パフォーマンスカウンタ
$ sudo perf stat -e 'syscalls:sys_enter_*' ls /tmp 2>&1 | tail -20
20.5 bpftrace
# bpftrace: eBPF ベースの高度なトレーシング
# 全 openat システムコールをトレース
$ sudo bpftrace -e '
tracepoint:syscalls:sys_enter_openat {
printf("%s(%d) openat: %s\n",
comm, pid, str(args->filename));
}'
# システムコール回数のヒストグラム
$ sudo bpftrace -e '
tracepoint:raw_syscalls:sys_enter {
@syscalls[args->id] = count();
}
END { print(@syscalls); }'
# システムコールレイテンシの測定
$ sudo bpftrace -e '
tracepoint:raw_syscalls:sys_enter {
@start[tid] = nsecs;
}
tracepoint:raw_syscalls:sys_exit /@start[tid]/ {
@ns = hist(nsecs - @start[tid]);
delete(@start[tid]);
}'
# 特定プロセスの read サイズ分布
$ sudo bpftrace -e '
tracepoint:syscalls:sys_exit_read /comm == "nginx"/ {
@bytes = hist(args->ret);
}'
20.6 /proc ファイルシステムによる調査
# プロセスのシステムコール情報
$ cat /proc/<PID>/syscall
0 0x3 0x7f1234 0x1000 0x0 0x0 0x0 0x7ffd1234 0x7f5678
# フォーマット: syscall_nr arg1 arg2 arg3 arg4 arg5 arg6 sp ip
# 現在実行中のシステムコール番号
$ awk '{print $1}' /proc/<PID>/syscall | ausyscall $(cat -)
# ファイルディスクリプタの一覧
$ ls -la /proc/<PID>/fd/
# メモリマッピング
$ cat /proc/<PID>/maps
# システムコールのフィルタ状態 (seccomp)
$ grep Seccomp /proc/<PID>/status
Seccomp: 2
Seccomp_filters: 1
21. まとめ
21.1 重要なポイントの整理
-
SYSCALL/SYSRET: x86_64 での標準的なシステムコールメカニズム。MSR レジスタを使用して高速なモード遷移を実現。
-
sys_call_table: システムコール番号から実際のハンドラ関数へのマッピング。読み取り専用で保護されている。
-
entry_SYSCALL_64: すべての 64-bit システムコールのエントリポイント。レジスタ保存、スタック切り替え、KPTI 対応を行う。
-
SYSCALL_DEFINE マクロ: 型安全なシステムコールハンドラの定義方法。引数のサニタイズも自動的に行われる。
-
VDSO: clock_gettime 等の高頻度システムコールをカーネル遷移なしに実行する仕組み。約8倍の高速化。
-
errno: カーネルの負の戻り値をユーザー空間の TLS errno に変換する仕組み。
-
セキュリティ: seccomp、audit、KPTI 等の多層的なセキュリティメカニズム。
-
最適化: VDSO の活用、io_uring によるバッチ処理、sendfile/splice による零コピー転送。
21.2 参考資料
| リソース | URL/パス |
|---|---|
| カーネルソースコード | https://elixir.bootlin.com/linux/latest/source |
| システムコール man ページ | man 2 <syscall_name> |
| syscall(2) マニュアル | man 2 syscall |
| Linux カーネルドキュメント | Documentation/process/adding-syscalls.rst |
| Anatomy of a system call | https://lwn.net/Articles/604287/ |
| x86_64 ABI 仕様 | System V Application Binary Interface AMD64 |
21.3 カーネルバージョンごとの主な追加システムコール
| バージョン | システムコール | 用途 |
|---|---|---|
| 5.1 | io_uring_setup/enter/register | 高性能非同期 I/O |
| 5.2 | clone3 | 改良版プロセス作成 |
| 5.3 | pidfd_open | PID ファイルディスクリプタ |
| 5.6 | openat2 | 改良版ファイルオープン |
| 5.9 | close_range | 範囲指定 close |
| 5.10 | process_madvise | 他プロセスのメモリアドバイス |
| 5.12 | mount_setattr | マウント属性設定 |
| 5.13 | landlock_create_ruleset | Landlock セキュリティ |
| 5.14 | memfd_secret | 秘密メモリ領域 |
| 6.1 | futex_waitv | 複数 futex 待機 |
| 6.5 | cachestat | ページキャッシュ統計 |
| 6.6 | fchmodat2 | フラグ付き chmod |
注意事項: 本ドキュメントは Linux 6.x 系カーネルを対象としているが、カーネルバージョンによって実装の詳細やシステムコール番号が異なる場合がある。最新の情報は公式のカーネルソースコードおよびドキュメントを参照すること。
Document generated: 2026-04-10 Kernel version reference: Linux 6.x Architecture focus: x86_64 (AMD64)