Linuxでは、通常のファイルだけでなく、ディレクトリ、デバイス、パイプ、ソケットなども、統一されたファイルインターフェースを通じて扱います。プログラムがファイルを操作するとき、カーネルとの境界で重要な役割を果たすのがファイルディスクリプタです。
1. ファイルを開いたときに起きること#
ユーザープログラムがopen()を呼び出すと、カーネルはおおむね次の処理を行います。
- パスを解決し、ディレクトリエントリーから対象のinodeを探す。
- inodeに保存されたファイル種別、権限、サイズ、データ位置などのメタデータを確認する。
- オープンされたファイルを表すカーネル内部の
struct fileを用意する。 - プロセスのファイルディスクリプタ表に整数の番号を割り当てる。
- 必要なデータをページキャッシュへ読み込み、後続の読み書きを効率化する。
大きなファイルを扱う場合、mmap()でファイルをプロセスの仮想アドレス空間へマッピングすることもあります。
2. ファイルディスクリプタとstruct file#
ファイルディスクリプタ(file descriptor、FD)は、プロセスがオープン中のファイルを参照するための非負整数です。新しいファイルを操作するたびに名前を検索する代わりに、プロセスはFDをシステムコールへ渡します。
通常、プロセスの標準FDは次のように予約されています。
| FD | 名前 | 用途 |
|---|---|---|
| 0 | stdin | 標準入力 |
| 1 | stdout | 標準出力 |
| 2 | stderr | 標準エラー出力 |
そのため、これらをすべて使っているプロセスが新しいファイルを開くと、通常はFD 3が割り当てられます。その後は4、5のように増えていきます。
簡略化すると、カーネル内部の関係は次のようになります。
プロセスのFD表
0 ─────┐
1 ─────┼→ struct file(オープンファイルの状態と現在位置)
2 ─────┘
↓
inode(ファイルのメタデータとデータ位置)実際には、Linuxは主に次の3種類の情報を分けて管理します。
- プロセスごとのファイルディスクリプタ表:FD番号とオープンファイルへの参照を対応付ける。
- システム全体のオープンファイル表:現在位置、オープンフラグ、参照カウントなどを保持する。
- ファイルシステムのinode:ファイルの種類、権限、所有者、サイズ、時刻、データブロックなどを保持する。
3. 複数のプロセスでファイルを共有する#
「同じパスを複数のプロセスが開く」ことと、「同じオープンファイル状態を共有する」ことは同じではありません。
3.1 別々にopen()する場合#
複数のプロセスが同じパスに対して個別にopen()を呼ぶと、それぞれのFDから同じinodeを参照します。しかし、現在位置(file offset)などのオープン状態は、通常それぞれ独立しています。
3.2 fork()やdup()で共有する場合#
fork()の後に親子プロセスが同じFDを使ったり、dup()でFDを複製したりすると、複数のFDが同じstruct fileを参照することがあります。この場合、ファイルオフセットなどの状態を共有します。片方が読み書きすると、もう片方から見える現在位置も変化します。
カーネルは参照カウントでこの共有状態を管理します。1つのFDをclose()しても、同じオープンファイルを参照するFDが残っていれば、カーネルは関連するリソースを解放しません。
4. ファイルロックと並行アクセス#
複数のプロセスが同じファイルへ書き込むと、データの競合が起きる可能性があります。Linuxでは、flock()やfcntl()を使って共有ロックや排他ロックを要求できます。
- 共有ロック(shared lock):複数のプロセスが同時に取得でき、読み取り用途に向く。
- 排他ロック(exclusive lock):同時に1つのプロセスだけが取得でき、書き込み用途に向く。
ロックは、アプリケーションが規約として守る協調的なロックです。すべてのプログラムや操作を強制的に停止させるものではないため、設計時には注意が必要です。
5. mmap()によるファイル共有#
複数のプロセスがファイルの同じ範囲へアクセスする場合、mmap()で同じファイル領域を仮想アドレスへマッピングできます。共有マッピングを使えば、プロセス間で共有メモリに近い形でデータを扱えます。
ただし、マッピングしただけで読み書きの順序が安全になるわけではありません。複数のプロセスが同じデータを変更する場合は、mutex、セマフォ、ファイルロックなどの同期機構が必要です。
6. inodeとは何か#
inodeは、ファイルシステムがファイルを管理するためのデータ構造です。ファイルの内容そのものや、人間が見るファイル名を直接保存するのではなく、主に次のメタデータを保持します。
- ファイル種別(通常ファイル、ディレクトリ、シンボリックリンク、デバイスなど)
- アクセス権限
- 所有者のUIDとグループのGID
- ファイルサイズ
ctime、mtime、atimeなどの時刻情報- ファイルデータを指すブロック情報
- ハードリンク数
6.1 ファイル名とinodeの関係#
ファイル名は、ディレクトリ内のディレクトリエントリーに保存されます。ディレクトリエントリーは、ファイル名とinode番号を対応付けます。そのため、ファイル名とinodeは別の概念です。
ディレクトリエントリー
"notes.txt" ─→ inode 12345 ─→ データブロックLinuxではディレクトリ自身も特殊なファイルであり、ディレクトリエントリーを内容として持ちます。
6.2 ハードリンクとシンボリックリンク#
- ハードリンクは、別のファイル名から同じinodeを参照するディレクトリエントリーです。ある名前を削除しても、ハードリンク数が0になるまでinodeとデータは解放されません。
- シンボリックリンクは、リンク先のパス文字列を保存する特殊なファイルです。通常はリンク先のinodeを直接共有しません。リンク先が削除されると、リンクは壊れた状態になります。
7. ファイルのライフサイクル#
- 作成:カーネルがinodeと必要なデータ領域を割り当て、ディレクトリーにファイル名とinode番号の対応を追加する。
- オープン:パスを解決し、オープンファイル状態を作成して、プロセスへFDを返す。
- 読み書き:FDを使ってページキャッシュやストレージへアクセスする。
- 削除:ディレクトリーから名前を削除し、ハードリンク数を減らす。
- 解放:ハードリンク数が0になり、かつオープン中のFDもなくなった時点で、inodeとデータ領域が解放される。
まとめ#
- FDは、プロセスがオープン中のファイルを参照する番号である。
struct fileは、現在位置やオープンフラグなどのオープン状態を表す。- inodeは、ファイルのメタデータとデータ位置を管理する。
- ファイル名はディレクトリエントリーからinode番号へ対応付けられる。
fork()やdup()では同じオープンファイル状態を共有することがある。- ハードリンクは同じinodeを指し、シンボリックリンクは別のパスを保存する。


