3秒でわかる
アプリが OS の中核機能を頼むための唯一の入口。ファイル操作や通信は、すべてこの窓口を通ってカーネルの側で実行されます。
もう少し詳しく
どういうものか
システムコールは、アプリケーションが OS のカーネルに処理を依頼するための公式な入口です。CPU にはユーザモードとカーネルモードという 2 つの動作モードがあり、アプリはユーザモードで動きます。ファイルの読み書き、ネットワーク通信、プロセスの生成といった、ハードウェアや他プロセスに影響する操作はカーネルモードでしか行えません。システムコールを呼ぶと、そこで CPU のモードが切り替わり、カーネル側の処理が走り、終わるとユーザモードへ戻ります。open、read、write、fork、execve、socket などが代表例です。
なぜ必要か
アプリが直接ディスクやメモリを操作できると、ひとつのバグが別のプロセスのデータを壊し、OS 自体も守れません。窓口を 1 つに絞ることで、権限の確認、資源の割り当て、プロセス間の調停を全部カーネルの側で行えます。パーミッションの検査が効くのも、ファイルを開く経路がここしか無いからです。
性能を考えるうえでも重要です。モードの切り替えには費用がかかるため、1 バイトずつ read を呼ぶと、実データより切り替えの時間のほうが長くなります。標準ライブラリがバッファリングするのは、この呼び出し回数を減らすためです。
具体例
高級言語の入出力は、最終的にシステムコールへ落ちます。
with open("data.txt") as f: # 内部で open(2) が呼ばれる
text = f.read() # 内部で read(2) が呼ばれる実際に何が呼ばれているかは strace で観察できます。
strace -c python3 app.py
# 呼び出し回数と所要時間が systemcall ごとに集計される
strace -e trace=openat,read ls似た用語との違い
| 用語 | 実行される場所 |
|---|---|
| ライブラリ関数 (printf など) | ユーザ空間。内部でシステムコールを呼ぶことがある |
| システムコール (write など) | カーネル空間 |
| 割り込み | ハードウェア側から発生する通知。方向が逆 |
printf は文字列を組み立てるまでがユーザ空間で、実際の出力で write を呼びます。呼ばない場合もあり、バッファに溜まったままプロセスが強制終了すると出力が消えます。
つまずきやすいところ
プロセスが遅いとき、CPU 使用率だけを見て「計算が重い」と判断すると外します。top で system の割合が高ければ、原因は計算ではなくシステムコールの回数です。ログを 1 行ごとに write して flush している、小さなファイルを何千回も開いている、といった構造がよくある原因です。
コンテナで動かすときは、seccomp によって使えるシステムコールが制限されている場合があります。ローカルで動くのにコンテナ内だけ落ちるときは、権限ではなくこの制限を疑う価値があります。