🐧 Linux 総合学習プラットフォーム
システムコール ・ 上級

システムコールとは(ユーザ空間とカーネル空間の境界)

プログラムが画面表示・ファイル読み書き・通信などを行うとき、ハードウェアを直接触ることはできません。これらはカーネルだけが持つ特権で、アプリは「システムコール」という窓口を通じてカーネルに作業を依頼します。CPUにはユーザ空間(アプリが動く制限された世界)とカーネル空間(特権を持つOSの世界)の区別があり、システムコールはその境界を安全にまたぐ唯一の正式な入り口です。呼び出すとCPUは特権モードへ切り替わり、要求された処理を終えると元のアプリへ戻ります。この仕組みのおかげで、アプリのバグが直接ハードウェアやOS全体を壊すことを防げます。

プログラムが「画面に文字を出す」「ファイルを保存する」「ネットワークで通信する」といった仕事をするとき、その裏側では必ずカーネルの力を借りています。なぜなら、ディスクやネットワークカード、ディスプレイといったハードウェアを直接操作する権限は、OSの中核であるカーネルだけが握っているからです。もしアプリケーションが好き勝手にハードウェアを叩けてしまうと、一つのプログラムのバグや暴走が、ほかのプログラムやOS全体、さらには機器そのものを巻き込んで壊しかねません。そこでCPUとOSは協力して、アプリが動く世界とカーネルが動く世界をはっきり分け、両者の行き来を厳しく管理しています。この「アプリからカーネルへ仕事を頼むための、ただ一つの正式な窓口」がシステムコール(syscall)です。低レイヤを学ぶうえで、まずこの窓口の存在と意味を押さえることが、これから扱うファイル入出力やプロセス生成を理解するすべての出発点になります。

ユーザ空間とカーネル空間

CPUには「いまどこまでの操作を許すか」を切り替える特権レベルの仕組みが備わっています。これを使って、システムが扱うメモリ空間は大きく二つの領域に分けられます。一つはアプリやコマンド、サービス、シェルといった通常のプログラムが動くユーザ空間(userspace)です。ここでは権限が制限され、ハードウェアへの直接アクセスや、ほかのプロセスのメモリへの侵入はできないようになっています。もう一つは、カーネル本体やデバイスドライバが動くカーネル空間(kernelspace)で、こちらはすべての権限を持つ特権的な世界です。アプリのコードは普段、ユーザ空間で、CPUの「ユーザモード」という制限された状態で実行されています。この二つを分けておくことで、ユーザ空間で起きた問題がカーネル空間へ直接波及するのを防ぎ、システム全体の堅牢さと安全性を保っているのです。Linuxの構造を上から眺めると、アプリ・コマンド・サービス・シェルといったユーザランドが上の層にあり、その下にカーネルとその一部であるデバイスドライバ、さらに下に実際のハードウェアがあります。システムコールは、この上下の層を安全につなぐ橋として機能します。

境界をまたぐ瞬間に何が起きるか

アプリがシステムコールを発行すると、CPUは制限されたユーザモードから、全権を持つカーネルモードへと切り替わります。この切り替えをモード遷移と呼びます。具体的には、どの機能を呼びたいかを示す番号と引数を所定の場所に並べてカーネルへ制御を移し、カーネルが要求された処理(たとえばディスクからの読み出し)を肩代わりして実行します。処理が終わると、結果を持ってふたたびユーザモードへ戻り、アプリは中断した続きから実行を再開します。ここで決定的に重要なのは、この境界の出入りはアプリが自分の好きなアドレスへ飛び込めるわけではなく、カーネルがあらかじめ用意したただ一つの入口を必ず通る、という点です。だからこそアプリは「お願い」はできても「乗っ取り」はできず、カーネルは渡された引数を一つずつ検査したうえで安全に処理を進められます。この一方向の関所があるおかげで、信頼できないアプリと、信頼すべきカーネルとが、同じ機械の上で安全に同居できるわけです。

モード遷移とコンテキストスイッチの違い

よく似た用語にコンテキストスイッチ(contextswitch)があり、システムコールによるモード遷移と混同しがちなので、ここで区別しておきます。モード遷移は、同じプロセスのまま、CPUの権限レベルがユーザモードとカーネルモードのあいだで切り替わることを指します。実行しているプログラムそのものは変わりません。一方コンテキストスイッチは、CPUがいま動かしているプロセスを、いったん脇に置いて別のプロセスに切り替える動作を指します。レジスタの内容やメモリの対応づけといった「そのプロセスの実行状態(コンテキスト)」をまるごと保存し、次のプロセスのものを読み込み直すため、モード遷移よりも重い処理になります。システムコールは必ずモード遷移を伴いますが、必ずしもコンテキストスイッチを伴うわけではありません。たとえばディスク待ちのように時間のかかる処理では、待っているあいだにカーネルが別のプロセスへコンテキストスイッチして、CPUを遊ばせないようにする、という連携が起こります。

境界を越えるコストと、まとめる発想

ユーザモードとカーネルモードの行き来には、わずかとはいえ処理コストがかかります。レジスタの保存や引数の検査といった手続きが毎回挟まるためです。したがって、たとえば1バイトずつ何百万回も書き込みのシステムコールを呼ぶと、その小さなコストが塵も積もって全体が目に見えて遅くなります。C標準ライブラリのprintfなどが、出力をいったん内部にため込んでからまとめて書き出す(バッファリングする)のは、システムコールの呼び出し回数そのものを減らして、この境界越えのコストを節約するためでもあります。「カーネルに頼む仕事はできるだけまとめて、回数を減らす」という発想は、性能を意識した低レイヤプログラミングの基本的な勘所です。逆に言えば、性能が出ないプログラムを調べるときは、まず無駄に多くのシステムコールを呼んでいないかを疑うのが定石になります。

観察するための入口

システムコールは目に見えにくい裏方ですが、観察する道具はちゃんと用意されています。あるプログラムがどんなシステムコールをどんな引数で呼んでいるかを順番に表示するstraceと、各システムコールの正確な仕様を引くためのマニュアル、man 2です。「このアプリは結局カーネルに何を頼んでいるのか」をstraceで一度自分の目で確かめると、ユーザ空間とカーネル空間の境界が単なる抽象的な概念ではなく、実際に発行される一行一行の呼び出しとして体感できます。たとえば文字を一つ表示するだけのプログラムでも、その裏ではwriteというシステムコールが発行されていることが見て取れます。この境界を意識できるようになることが、この先に登場するopen・read・write・fork・execといった具体的なシステムコールを、丸暗記ではなく仕組みとして理解するための土台になります。

この項目に出てくる用語

システムコールしすてむこーる
アプリがカーネルに処理を依頼する公式の窓口。
ユーザ空間ゆーざくうかん
アプリが動く、権限を制限された領域。
カーネル空間かーねるくうかん
OSの中核が動く、特権を持つ領域。
コンテキストスイッチこんてきすとすいっち
CPUが実行する処理を別のものへ切り替える動作。

関連コマンド

man 2 openstrace

▶ 学習アプリでこの続きを学ぶ・演習する