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

ライブラリ関数とシステムコールの違い

printf や fopen はC標準ライブラリ(libc)の関数で、システムコールそのものではありません。これらは内部でバッファリングなどの工夫をしつつ、最終的に write や open といったシステムコールを呼び出します。つまりライブラリ関数は「使いやすい上の層」、システムコールは「カーネルへの直接の入口」という関係です。両者の境目は strace(システムコールを表示)と ltrace(ライブラリ関数呼び出しを表示)を比べると体感できます。fwrite が即座に write を呼ばずバッファに溜めるなど、層が違うことで挙動も変わるため、性能やデバッグを考えるうえで区別が重要になります。

C言語でファイルを扱おうとすると、fopenとopen、fwriteとwrite、fprintfとwriteのように、似た目的の関数が二系統あることに気づく。

なぜ、わざわざ似たものが二つ用意されているのか。これは、システムコール(syscall)とライブラリ関数が、まったく別の層に属しているからだ。

💡
ポイントfopenとopen、fwriteとwriteが二系統あるのは、システムコールとライブラリ関数がまったく別の層に属しているからだ。

この二つの層の違いをきちんと理解すると、一段深い話がすっきり腑に落ちる。なぜprintfで出力したはずの文字がすぐに画面へ出ないことがあるのか、なぜ性能やデバッグの場面で両者を区別する必要があるのか、といった話だ。

低レイヤを扱ううえで避けて通れない、とても重要な区別なので、ここで丁寧に押さえておこう。

🪜 二つの層の上下関係

システムコールは、これまで見てきたとおり、カーネルへ直接仕事を頼むための窓口だ。

一方、printfやfopenといった関数は、C標準ライブラリ(libc、Linuxでは多くの場合glibc)が提供するライブラリ関数であって、システムコールそのものではない。

アプリのコードlibc のライブラリ関数(printf・fopen)システムコール(open・write)カーネル

両者はきれいな階層になっている。一番上にアプリのコードがあり、その下にlibcのライブラリ関数があり、さらにその下にシステムコールがあって、最下層にカーネルがいる。

つまりライブラリ関数は「アプリから使いやすいように整えられた上の層」、システムコールは「カーネルへの直接の入口」という役割分担だ。

🔗
たとえライブラリ関数は使いやすく整えた受付窓口、システムコールはカーネルへの直通口。普段はfopenやprintfが内部でopenやwriteを呼んでくれている。

fopenやprintfは、内部でさまざまな下準備をしたうえで、最終的にopenやwriteといったシステムコールを呼び出す。

私たちが普段書くコードの多くは、この使いやすい上の層を経由して、カーネルの機能を間接的に利用している。ライブラリ関数は、エラー処理やデータの整形といった面倒な部分を肩代わりしてくれるぶん、手軽に使える。

🎁 ラッパー関数が肩代わりしているもの

ここで一段細かい話をすると、C言語でwriteやopenを呼ぶときでさえ、実は本物のシステムコールを直接叩いているわけではない。

多くの場合、glibcが用意したラッパー関数を経由している。ラッパーとは、システムコールを呼びやすいように薄く包んだ関数のことだ。

つまずきC言語でwriteやopenを呼ぶときでさえ、実は本物のシステムコールを直接叩いてはいない。多くはglibcのラッパー関数を経由している。

具体的には、引数をカーネルが期待する形に整えてシステムコールを発行し、カーネルから戻ってきた結果を見て、失敗していればerrno(errno)に理由を入れて-1を返す、という後始末まで肩代わりしてくれる。

私たちが、戻り値とerrnoを見るだけで簡潔にエラー処理を書けるのは、このラッパーが裏で働いてくれているおかげだ。

コツソースに書くのは「glibcのラッパー(や上位のライブラリ関数)」、straceで見えるのは「カーネルへ発行された生のシステムコール」。この二段構えを覚えておく。

整理すると、ソースコードに書くのが「glibcのラッパー(あるいはより上位のライブラリ関数)」、straceで実際に見えるのが「カーネルへ発行された生のシステムコール」という二段構えになっている。

この対応関係を頭に入れておくと、ソースでは一回しか呼んでいないライブラリ関数が、straceでは内部で複数のシステムコールに分かれて見えても、慌てずに済む。

📤 printf した文字がすぐ画面に出ないわけ

二つの層の違いが最もはっきりと表に出るのが、バッファリングだ。

ライブラリ関数のfwriteやprintfは、書き込むデータをすぐにカーネルへ渡さず、いったんライブラリ内部のバッファ(一時的なため場所)に溜め込む。

printf(ライブラリ関数)printfバッファに溜める満杯・改行・終了でまとめて writewrite(システムコール)write を直接呼ぶその場で即座にカーネルへ

そして、バッファがいっぱいになったときや、画面出力で改行が来たとき、あるいはプログラムが正常終了するときなどに、まとめて下層のwriteシステムコールを一度だけ呼んで吐き出す。

これは前に触れたとおり、ユーザ空間(userspace)とカーネルの境界をまたぐ回数を減らして性能を稼ぐための工夫だ。

一方、システムコールのwriteを直接呼べば、データはその場で即座にカーネルへ渡る。

つまずきprintfで出した文字が、クラッシュした瞬間にはまだバッファに残っていて画面に出ていない、という現象が起こりえる。出力順が直感と合わないのも多くはこれが原因。

この違いがあるために、printfで出力したはずの文字が、プログラムが途中でクラッシュした瞬間にはまだバッファの中に残っていて、画面に出ていない、という一見不可解な現象が起こりえる。

複数の出力の順序が直感と合わないときも、このバッファリングが原因であることが少なくない。

コツデバッグで確実に出力を見たいときは、バッファを強制的に吐き出すfflushを使う。

デバッグで確実に出力を見たいときは、バッファを強制的に吐き出すfflushを使う、という対処も覚えておくと役に立つ。

📦 FILE 構造体と fd の関係

二つの層は、扱う「対象の表し方」も違う。

高水準のライブラリ関数fopenが返すのは、FILE構造体へのポインタという、バッファなどの情報を内部に抱えた箱だ。

一方、低水準のopenが返すのは、これまで見てきたファイルディスクリプタ(fd)という、ただの小さな整数だった。

FILE 構造体は fd を包んだものFILE 構造体(fopen が返す箱)バッファ等の情報fd(小さな整数)open が返すものfileno で FILE→fd、fdopen で fd→FILE と行き来できる
🔗
たとえFILE構造体はfdを包んだ箱。箱(FILE)の内側に背番号(fd)が収まっている。fileno でFILEからfdを取り出し、fdopen でfdをFILEに包み直せる。

両者はまったく別物のようでいて、実は前者は後者を包んだものになっている。FILE構造体の内側にはfdが収まっているのだ。

必要なときはfileno関数でFILEからfdを取り出せるし、逆にfdをfdopen関数でFILEに包み直すこともできる。この対応を知っておくと、二系統の関数を行き来する場面でも混乱しない。

🔬 strace と ltrace を並べると層の違いが見える

二つの層の違いは、観察ツールを使い分けると体感として理解できる。

straceはカーネルへのシステムコールを表示し、ltraceはライブラリ関数の呼び出しを表示する。同じプログラムをそれぞれで追ってみると、層の違いが目に見える形で現れる。

printfを一度だけ呼ぶプログラムを、ltraceで見るとprintfが現れ、straceで見ると内部でまとめられた一回のwriteとして見える。

たとえばprintfを一度だけ呼ぶプログラムをltraceで見るとputsやprintfの呼び出しが現れるが、同じものをstraceで見ると、その出力は内部でまとめられて一回のwriteとして発行されている、といった具合だ。

fwriteを何度も繰り返し呼んでいるのに、straceにはwriteが数回しか出てこない、という観察も、バッファリングが確かに働いている証拠になる。

💡
ポイント手軽さと性能なら高水準のライブラリ関数(fopen系)、出力の即時性やデバイスファイルの細かい制御なら低水準のシステムコール(open系)を直接使う。

では実務では、どちらを選べばいいのか。勘所はこうだ。手軽さと性能を取りたいなら、バッファリングが効く高水準のライブラリ関数(fopen系)を使う。出力の即時性が必要だったり、デバイスファイルを細かく制御したかったりするなら、低水準のシステムコール(open系)を直接使う。

そして「なぜこういう動きになるのか」が分からなくなったら、straceとltraceで層をまたいだ実際の呼び出しを覗いてみる。この使い分けが、二つの層の区別を実践に活かすための確かな道筋になる。

この項目に出てくる用語

システムコールしすてむこーる
アプリがカーネルに処理を依頼する公式の窓口。
ファイルディスクリプタふぁいるでぃすくりぷた
開いたファイルを指す小さな整数(0以上)。
ユーザ空間ゆーざくうかん
アプリが動く、権限を制限された領域。

関連コマンド

straceltraceman 3 printf

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