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

主要なシステムコール(open/read/write/close)

ファイル操作の土台は open・read・write・close の4つのシステムコールです。open はファイルを開き、成功すると「ファイルディスクリプタ」と呼ばれる小さな整数(0以上)を返します。以後の read や write はこの番号を使って対象を指定し、最後に close で後始末をします。標準入力(0)・標準出力(1)・標準エラー出力(2)はプログラム開始時から開かれている特別なディスクリプタです。read と write は実際に処理したバイト数を返し、要求した分より少ないこともある点が重要です。失敗時は -1 を返し、原因は errno に格納されます。

Linuxでファイルを扱うプログラムを書くとき、覚えるべきシステムコールはたった四つでいい。open・read・write・closeという四つのシステムコール(syscall)だ。

日本語にすれば「開く・読む・書く・閉じる」で、紙のノートを開いて読み書きし、最後に閉じる、という日常の動作にそのまま対応する。

🔗
たとえopen・read・write・closeは、紙のノートを「開く・読む・書く・閉じる」とそのまま同じ。日常の動作に対応するので迷わない。

面白いのは、Linuxではこの四つが通常のファイルだけでなく、デバイスやパイプ、ソケットなど「ファイルとして見えるもの」全般に共通して使える点だ。

つまりこの四つを理解することは、Linuxの入出力という仕組み全体の土台を理解することに直結する。

ここでは、これらがどんな約束ごとで動くのかを順に見ていく。仕様を正確に確認したいときは、man 2 openのようにマニュアルのセクション2(システムコール)を引くのが最も確実だ。

開く → 読む・書く → 閉じる必要な回数だけ、何度でもopen()fd を受け取るread(fd,…)中身を読むwrite(fd,…)中身を書くclose(fd)fd を手放すこの4つは通常のファイルだけでなく、デバイス・パイプ・ソケットにも同じように使える

🔢 開いたファイルに付く「背番号」

openは、対象のファイルを開いて読み書きの準備を整えるシステムコールだ。成功すると、そのファイルを指し示すための小さな非負整数を返す。

これがファイルディスクリプタ(fd)で、いわば「いま開いているファイルにつけられた背番号」だ。以後のreadやwriteは、長いファイル名ではなく、この手短な番号を使って対象を指定する。

番号は、使われていない一番小さいものから順に割り当てられる。そのため最初に開いたファイルが多くの場合3番になる。

💡
ポイントfdは0番(標準入力)・1番(標準出力)・2番(標準エラー出力)が予約済み。だから自分で開いた最初のファイルは多くの場合3番になる。
プロセスのファイルディスクリプタ表0標準入力stdin1標準出力stdout2標準エラー出力stderr3open で開いた最初のファイル0/1/2 は予約済み / 空いている一番小さい番号から割り当て

なぜ3番からなのか。プロセスが開始した時点で0番(標準入力)・1番(標準出力)・2番(標準エラー出力)があらかじめ開かれて予約されているためだ。

openの第一引数にはパス名、第二引数には開き方を表すフラグを渡す。

フラグはO_RDONLY(読み込み専用)・O_WRONLY(書き込み専用)・O_RDWR(読み書き両用)のいずれか一つを必ず指定し、必要に応じてO_CREAT(なければ新規作成)やO_TRUNC(中身を空にする)、O_APPEND(末尾に追記)などを縦棒で組み合わせる。

open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0644) ……ログがなければ作り、あれば末尾に追記する形で開く。新規作成時だけ第三引数に権限(0644)を渡す。

たとえばopen("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0644)と書けば、ログファイルがなければ作り、あれば末尾に追記する形で開ける。

新規作成を伴うときだけ、第三引数に作成するファイルの権限(この例なら0644)を渡す。

📏 read と write は頼んだ分だけ働くとは限らない

readはfdが指すファイルからデータを読み込み、writeは逆に書き込む。

どちらも操作対象のfd、データを置く(あるいは取り出す)バッファ、そして扱いたいバイト数を引数に取り、実際に処理できたバイト数を戻り値として返す。

man 2 readで宣言を引くと ssize_t read(int fd, void *buf, size_t count); という形が確認できる。

ここに、初学者がとてもつまずきやすい大切な性質がある。要求したバイト数より少ない数しか処理されないことがあるのだ。

つまずきwriteで1000バイト頼んでも戻り値が600なら、まだ400バイト書き残されている。要求した分だけ処理されるとは限らないので、戻り値を必ず見る。

たとえば1000バイトの書き込みを頼んでも、戻り値が600であれば、まだ400バイトが書き残されている。

write(fd, buf, 1000) を呼んだら…処理できた 600 バイト(戻り値=600)残り 400 バイト戻り値を見て処理済みを足し込み残りがゼロになるまで write を繰り返す頼んだ分を一度に処理するとは限らない

だから堅牢なプログラムでは、戻り値を見て処理済みのバイト数を足し込み、残りがゼロになるまでwriteを繰り返すのが定石だ。

readの戻り値が0のときは特別な意味を持つ。「これ以上読むデータがない」、つまりファイルの終端(EOF)に達したことを表す。

コツreadの戻り値0は「もう読むものがない」というEOFの合図。読み込みループはこの0を終わりの目印にして回す。

読み込みループは、この0を「もう終わり」の合図として回し、最後に読み込んだ内容の末尾へ終端文字を付けて文字列として扱う、という書き方がよく使われる。

📍 「次に読み書きする場所」を覚えているもの

openからcloseまでのあいだ、ファイルには「次にどこから読み書きするか」を示すオフセットという内部的な位置が保たれている。

最初はファイルの先頭(0)を指していて、readやwriteを呼ぶたびに、処理したバイト数だけ自動で前へ進む。

ファイルの中身とオフセット(次に読み書きする位置)0(先頭)末尾open直後は0 → read/writeで処理した分だけ前へ自動で進むlseek で好きな位置へ飛べる

だから何も特別なことをしなくても、繰り返し読めば先頭から順に最後まで読める。

この位置を好きなところへ動かしたいときは、lseekというシステムコールを使う。

lseek ……起点をSEEK_SET(先頭から)・SEEK_CUR(現在位置から)・SEEK_END(末尾から)から選び、オフセットを狙った位置へ動かす。

lseekには起点を選ぶ引数があり、SEEK_SET(先頭から)・SEEK_CUR(現在位置から)・SEEK_END(末尾から)を指定できる。

たとえばファイルの途中だけを読み出したい、ヘッダを読み飛ばして本体から処理したい、といった場合に、lseekで狙った位置へ飛んでからreadする。

この「順番に進むオフセット」という考え方を知っておくと、ファイルの一部分だけを扱うプログラムを書くときに迷わない。

🧹 閉じ忘れると静かに溜まっていくもの

使い終わったファイルはcloseで閉じる。closeはファイルディスクリプタを解放し、カーネル側で確保していた資源を返す後始末の役割を担う。

開いたまま閉じ忘れると、未使用のfdが少しずつ溜まっていき、やがて新たなopenが「開きすぎ」を意味するEMFILEエラーで失敗する。

つまずき閉じ忘れるとfdが溜まり、やがてopenがEMFILEで失敗する(ファイルディスクリプタリーク)。長く動くサーバで特に深刻。開いたら必ず閉じる。

これをファイルディスクリプタリークと呼び、長時間動き続けるサーバ系のプログラムでとりわけ深刻な問題になる。

一つのプロセスが同時に開けるfdの数には上限があり、シェルのulimit -nで確認できる。「開いたら必ず閉じる」を習慣にしてほしい。

なお、これら四つのシステムコールはいずれも、失敗すると戻り値として-1を返し、失敗の具体的な理由をグローバル変数errno(errno)に格納する。

💡
ポイント成功なら0以上、失敗なら-1、理由はerrnoを見る。たとえばファイルがなければENOENT、権限不足ならEACCES。これはシステムコール全般に共通する作法だ。

たとえば指定したファイルが存在しなければENOENT、権限が足りなければEACCESが入る。

「成功なら0以上、失敗なら-1、理由はerrnoを見る」という約束は、ファイル入出力に限らずシステムコール全般に共通する基本作法だ。

🔎 書いたコードと実際の呼び出しを照らし合わせる

open・read・write・closeは、設定ファイルの読み込み、ログの書き出し、デバイスファイル経由でのハードウェア制御など、低レイヤのあらゆる場面で顔を出す。

これらを直接使うコードを書いたら、gccでコンパイルして動かし、期待どおりに読み書きできているかをstraceで確かめると理解が一気に深まる。

strace の出力には openat("設定ファイル", O_RDONLY) = 3 や write(1, "...", 14) = 14 のように、呼び出しと戻り値が一行ずつ並ぶ。

straceの出力には、openat("設定ファイル", O_RDONLY) = 3 や write(1, "...", 14) = 14 のように、呼び出しと戻り値が一行ずつ並ぶ。

自分のプログラムが内部でどんな順序でこれらを呼んでいるかを目で追えば、書いたコードと、実際に発行されるシステムコールとの対応がはっきり結びつく。

とくに「読めているはずなのに中身が空」「書いたはずなのにファイルが変わらない」といった不具合のときに効く。

straceで戻り値とerrnoを確認すれば、原因がopenの失敗なのかreadの誤りなのかを切り分けられ、調査の強力な武器になる。

つまずいたら、まずstraceでその四つの呼び出しを一行ずつ追ってみる。

この項目に出てくる用語

システムコールしすてむこーる
アプリがカーネルに処理を依頼する公式の窓口。
ファイルディスクリプタふぁいるでぃすくりぷた
開いたファイルを指す小さな整数(0以上)。
errnoえらーなんばー
直近のシステムコール失敗の原因を表す番号。

関連コマンド

man 2 openstracegcc

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