メインコンテンツまでスキップ

コラム: 「すべてはファイル」の哲学

読了時間: 5分


Unix の設計原則

1969年、Ken Thompson と Dennis Ritchie が Unix を開発したとき、彼らは一つの革新的な設計原則を採用しました。

"Everything is a file"(すべてはファイルである)

この原則は、50年以上経った今でも Linux/Unix システムの根幹を成しています。


ファイルとは何か

Unix における「ファイル」は、私たちが普段イメージするものより遥かに広い概念です。

┌─────────────────────────────────────────────────────────────────────┐
│ Unix における「ファイル」 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 通常ファイル /home/user/document.txt │
│ ディレクトリ /home/user/ │
│ デバイス /dev/sda, /dev/null, /dev/random │
│ プロセス情報 /proc/1234/status │
│ ソケット /var/run/docker.sock │
│ パイプ |(シェルのパイプ) │
│ シンボリックリンク /usr/bin/python -> python3 │
│ │
│ → すべて同じインターフェース(open/read/write/close)で操作 │
│ │
└─────────────────────────────────────────────────────────────────────┘

なぜこの設計が強力なのか

1. 統一されたインターフェース

すべてが「ファイル」なので、同じ操作で扱えます。

// 通常ファイルを読む
int fd = open("/etc/passwd", O_RDONLY);
read(fd, buffer, size);
close(fd);

// デバイスから読む(まったく同じAPI)
int fd = open("/dev/random", O_RDONLY);
read(fd, buffer, size);
close(fd);

// プロセス情報を読む(まったく同じAPI)
int fd = open("/proc/self/status", O_RDONLY);
read(fd, buffer, size);
close(fd);

新しいプログラミング言語を学ぶ必要も、新しいAPIを覚える必要もありません。

2. ツールの再利用

Unix のコマンドは「ファイル」を操作するように作られているため、あらゆるものに使えます。

# CPUの情報を見る
$ cat /proc/cpuinfo

# メモリの情報を見る
$ cat /proc/meminfo

# ランダムな文字列を生成
$ head -c 32 /dev/urandom | base64

# ディスクに直接書き込む(危険!)
$ dd if=image.iso of=/dev/sdb

# プロセスの環境変数を見る
$ cat /proc/1234/environ | tr '\0' '\n'

cat, head, dd は「ファイル」を操作するツールですが、デバイスやプロセス情報にもそのまま使えます。

3. リダイレクトの威力

シェルのリダイレクト(>, <, |)が万能になります。

# プログラムの出力を捨てる
$ command > /dev/null

# プログラムにゼロを入力し続ける
$ command < /dev/zero

# パイプでプログラムをつなぐ
$ cat /var/log/syslog | grep error | wc -l

代表的な仮想ファイルシステム

/dev - デバイスファイル

/dev/
├── null # 書き込むと消える、読むと即EOF
├── zero # 読むと無限にゼロを返す
├── random # 暗号学的に安全な乱数
├── urandom # 高速な乱数
├── sda # 1台目のディスク
├── sda1 # 1台目のディスクの1番目のパーティション
├── tty # 現在の端末
├── stdin # 標準入力(fd 0)
├── stdout # 標準出力(fd 1)
└── stderr # 標準エラー(fd 2)
# /dev/null の活用例
$ find / -name "*.log" 2>/dev/null # エラーを捨てる

# /dev/random の活用例
$ head -c 16 /dev/random | xxd # 16バイトの乱数を16進数で表示

/proc - プロセス情報

/proc/
├── 1/ # PID 1 のプロセス(init/systemd)
│ ├── cmdline # 起動コマンド
│ ├── environ # 環境変数
│ ├── fd/ # 開いているファイル
│ ├── maps # メモリマップ
│ └── status # 状態情報
├── cpuinfo # CPU情報
├── meminfo # メモリ情報
├── loadavg # 負荷平均
└── self/ # 現在のプロセス(自分自身)
# 自分のプロセスのメモリ使用量
$ cat /proc/self/status | grep VmRSS
VmRSS: 12340 kB

# 全プロセスのコマンドライン
$ for pid in /proc/[0-9]*; do cat $pid/cmdline 2>/dev/null; echo; done

/sys - カーネル情報

/sys/
├── block/ # ブロックデバイス
├── class/ # デバイスクラス
│ ├── net/ # ネットワークインターフェース
│ └── thermal/ # 温度センサー
└── devices/ # デバイスツリー
# CPU温度を取得(存在する場合)
$ cat /sys/class/thermal/thermal_zone0/temp
45000 # = 45.0°C

# ネットワークインターフェースの状態
$ cat /sys/class/net/eth0/operstate
up

この設計の限界

「すべてはファイル」は強力ですが、限界もあります。

┌─────────────────────────────────────────────────────────────────────┐
│ ファイル抽象化の限界 │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ 1. ネットワーク接続 │
│ └── connect(), bind() など専用システムコールが必要 │
│ │
│ 2. グラフィックス │
│ └── フレームバッファはファイルだが、実用的にはGPU APIを使う │
│ │
│ 3. 複雑な操作 │
│ └── ioctl() という「なんでも屋」システムコールに頼る │
│ │
│ 4. パフォーマンス │
│ └── 高性能I/Oは io_uring など専用インターフェースを使う │
│ │
└─────────────────────────────────────────────────────────────────────┘

それでも、この抽象化がもたらすシンプルさと一貫性は、50年以上にわたって Unix/Linux の成功を支えてきました。


Plan 9: さらなる徹底

Unix の後継として Bell Labs で開発された Plan 9 は、この哲学をさらに徹底しました。

Plan 9 では:
├── ネットワーク接続も /net/tcp/clone で作る
├── ウィンドウシステムも /dev/draw で操作
├── 他のマシンのファイルシステムも透過的にマウント
└── 文字通り「すべてがファイル」

Plan 9 は商業的には成功しませんでしたが、その思想は今も影響を与え続けています。


まとめ

「すべてはファイル」は、Unix が生んだ最も重要な抽象化の一つです。

┌─────────────────────────────────────────────────────────────────────┐
│ │
│ ファイル = バイトストリームを読み書きできるもの │
│ │
│ ├── 統一されたインターフェース(open/read/write/close) │
│ ├── 既存ツールの再利用が可能 │
│ ├── シェルのリダイレクトが万能 │
│ └── シンプルで理解しやすい │
│ │
└─────────────────────────────────────────────────────────────────────┘

この設計原則を知っていると、Linux システムの振る舞いの多くが「なるほど」と理解できるようになります。

/dev/null に書き込むと消えるのも、/proc でプロセス情報が見えるのも、すべてこの哲学の表れなのです。


試してみよう

# 自分のプロセスが開いているファイルを見る
ls -la /proc/self/fd

# /dev/null の正体
file /dev/null

# ランダムなパスワードを生成
head -c 12 /dev/urandom | base64

# 現在のプロセスのメモリマップを見る
cat /proc/self/maps | head -20