第3章:ファイルシステムという抽象化の威力

3.1 はじめに:抽象化という魔法

私たちは日常的に「抽象化」を使っている。例えば「車を運転する」とき、エンジンの内部構造を理解する必要はない。アクセル、ブレーキ、ハンドルという「インターフェース」さえ理解していれば、どんな車でも運転できる。

Linuxの「すべてはファイル」という思想は、コンピュータの複雑な要素を「ファイル」という単純な概念に抽象化する、革命的なアイデアである。

3.2 「すべてはファイル」という革新的発想の起源

問題:多様なデバイスをどう扱うか

1960年代のコンピュータは、新しいデバイスが登場するたびに、専用のプログラムを書く必要があった:

プリンタA用プログラム → プリンタAを制御
プリンタB用プログラム → プリンタBを制御
テープドライブ用プログラム → テープを制御
ディスク用プログラム → ディスクを制御

これでは非効率である。

UNIXの解答:統一インターフェース

UNIXの開発者たちは画期的な解決策を思いついた:

あらゆるデバイス
    ↓ 「ファイル」として抽象化
統一的な操作(open、read、write、close)

つまり、プリンタも、ディスクも、キーボードも、すべて「ファイル」として扱えばよい、という発想である。

実例で理解する抽象化

通常のファイル操作:

# ファイルに書き込む
echo "Hello, World!" > message.txt

# ファイルを読む
cat message.txt

デバイスも同じように扱える:

# ターミナルでビープ音を鳴らす
echo -e "\a"
# 注: 現代のLinuxでは/dev/audioは非推奨。音声出力はALSAやPulseAudioを使用

# 現代的な音声出力の例(ALSAを使用)
# ビープ音を再生
# aplay -c 1 -r 8000 -f S16_LE -t raw /dev/zero -d 0.1
# または、PulseAudioを使用
# paplay /usr/share/sounds/freedesktop/stereo/bell.oga

# ランダムデータを読む
head -c 10 /dev/urandom

# 端末に直接書き込む
echo "緊急メッセージ" > /dev/pts/0

プログラマーから見れば、すべて同じ「ファイル操作」である。

3.3 デバイス、プロセス情報、設定のファイル化

Linuxファイルシステム階層

Linuxのファイルシステムは、以下のような階層構造で組織されています:

Linuxファイルシステム階層構造

/dev - デバイスファイルの世界

/devディレクトリには、ハードウェアデバイスが「ファイル」として存在する:

ls -la /dev/
# 結果の例:
# brw-rw---- 1 root disk    8,   0  3月 15 09:00 sda     # ハードディスク
# crw-rw-rw- 1 root tty     5,   0  3月 15 09:00 tty     # 端末
# crw-rw-rw- 1 root root    1,   3  3月 15 09:00 null    # nullデバイス
# crw-rw-rw- 1 root root    1,   5  3月 15 09:00 zero    # zeroデバイス

ファイルタイプの意味:

  • b:ブロックデバイス(一定サイズのブロック単位でアクセス)
  • c:キャラクタデバイス(1バイトずつアクセス)

特殊なデバイスファイル

/dev/null - ブラックホール

# 出力を捨てる
command_with_lots_of_output > /dev/null

# エラーメッセージも捨てる
noisy_command > /dev/null 2>&1

なぜ便利か:不要な出力を「どこかに書き込む」という統一的な方法で処理できる。

/dev/zero - 無限のゼロ

# 1GBのファイルを作成(すべてゼロで埋める)
dd if=/dev/zero of=bigfile bs=1M count=1024

# ディスクの速度測定
dd if=/dev/zero of=testfile bs=1M count=1000

/dev/random と /dev/urandom - 乱数生成器

# パスワード生成
tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 12

# 暗号鍵の生成
dd if=/dev/random of=secret.key bs=1 count=32

/proc - プロセス情報の可視化

/procは、実行中のシステムの状態を「ファイル」として見せる仮想ファイルシステムである:

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

# メモリの状態を見る
cat /proc/meminfo

# 稼働時間を見る
cat /proc/uptime

プロセスごとの情報

各プロセスは、そのPID(プロセスID)と同じ名前のディレクトリを持つ:

# bashプロセスのPIDを調べる
ps aux | grep bash
# 例:1234

# そのプロセスの情報を見る
ls /proc/1234/
# cmdline  - 起動時のコマンドライン
# environ  - 環境変数
# status   - プロセスの状態
# fd/      - 開いているファイル

実用例:プロセスが開いているファイルを調べる

# nginxが開いているファイルを見る
sudo ls -la /proc/$(pgrep nginx)/fd/

/sys - カーネルパラメータへの窓

/sysは、カーネルの内部パラメータを操作するためのインターフェースである:

# CPUの動作周波数を確認
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq

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

# LEDを制御(Raspberry Piなど)
echo 1 > /sys/class/leds/led0/brightness

3.4 統一的なインターフェースがもたらす運用の簡潔性

すべてを同じ方法で扱える利点

1. 学習コストの削減

基本的なファイル操作を覚えれば、あらゆるものを操作できる:

# 通常のファイル
cat file.txt           # 読む
echo "text" > file.txt # 書く
cp file.txt backup.txt # コピー

# デバイス
cat /dev/ttyUSB0           # シリアルポートから読む
echo "AT" > /dev/ttyUSB0   # シリアルポートに書く

# システム情報
cat /proc/loadavg          # 負荷を読む
echo 3 > /proc/sys/vm/drop_caches  # キャッシュをクリア

2. ツールの再利用

ファイル用に作られたツールが、そのままシステム管理に使える:

# grepで特定の情報を抽出
grep "processor" /proc/cpuinfo | wc -l  # CPU数を数える

# awkで情報を加工
awk '{print $1}' /proc/loadavg  # 1分間の平均負荷

# 定期的な監視も簡単
watch -n 1 cat /proc/meminfo  # メモリ使用状況を1秒ごとに更新

3. スクリプト化の容易さ

システム管理タスクを簡単に自動化できる:

#!/bin/bash
# システム情報レポート生成スクリプト

echo "=== System Report $(date) ==="

echo "CPU Information:"
grep "model name" /proc/cpuinfo | head -1

echo "Memory Usage:"
free -h

echo "Disk Usage:"
df -h /

echo "Network Interfaces:"
for interface in /sys/class/net/*; do
    if [ -f "$interface/operstate" ]; then
        echo "$(basename $interface): $(cat $interface/operstate)"
    fi
done

リダイレクトとパイプの真価

「すべてはファイル」の思想により、リダイレクトとパイプが強力なツールになる:

標準入出力もファイル

標準入力(stdin)  = ファイルディスクリプタ 0
標準出力(stdout) = ファイルディスクリプタ 1
標準エラー(stderr)= ファイルディスクリプタ 2

これらも「ファイル」として扱われるため:

# 標準出力を別のファイルに
ls > file_list.txt

# 標準エラーを別のファイルに
command_that_fails 2> error.log

# 両方を同じファイルに
command > output.log 2>&1

# 入力を別のファイルから
mysql < setup.sql

パイプ:プロセス間のファイル共有

パイプ( )は、一時的な「ファイル」を作って、プロセス間でデータを受け渡す:
# プロセス1の出力 → 一時ファイル → プロセス2の入力
ps aux | grep nginx | awk '{print $2}' | xargs kill

# これは以下と同等(ただし一時ファイルは見えない)
ps aux > temp1
grep nginx < temp1 > temp2
awk '{print $2}' < temp2 > temp3
xargs kill < temp3
rm temp1 temp2 temp3

権限管理の一貫性

ファイルと同じ権限システムがデバイスにも適用される:

ls -la /dev/sda
# brw-rw---- 1 root disk 8, 0  3月 15 09:00 /dev/sda

# 権限の意味:
# - rootユーザーは読み書き可能
# - diskグループのメンバーも読み書き可能
# - その他のユーザーはアクセス不可

これにより、「誰がハードディスクに直接アクセスできるか」を、通常のファイル権限と同じ方法で制御できる。

3.5 実世界での応用例

ケース1:ログ収集システム

複数のソースからログを収集する場合:

# アプリケーションログ
tail -f /var/log/app/application.log

# システムログ
tail -f /var/log/syslog

# カーネルメッセージ
tail -f /proc/kmsg

# すべてを統合
tail -f /var/log/app/application.log /var/log/syslog /proc/kmsg > /var/log/combined.log

すべてが「ファイル」なので、同じコマンドで扱える。

ケース2:システム監視

#!/bin/bash
# simple_monitor.sh - シンプルな監視スクリプト

while true; do
    # CPU使用率
    cpu_usage=$(grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage}')
    
    # メモリ使用率
    mem_info=$(cat /proc/meminfo)
    mem_total=$(echo "$mem_info" | grep MemTotal | awk '{print $2}')
    mem_free=$(echo "$mem_info" | grep MemAvailable | awk '{print $2}')
    mem_usage=$((100 - (mem_free * 100 / mem_total)))
    
    # ディスク使用率
    disk_usage=$(df -h / | tail -1 | awk '{print $5}' | sed 's/%//')
    
    # アラート条件
    if [ $cpu_usage -gt 80 ] || [ $mem_usage -gt 90 ] || [ $disk_usage -gt 85 ]; then
        echo "ALERT: CPU: ${cpu_usage}%, Memory: ${mem_usage}%, Disk: ${disk_usage}%" | \
            tee /dev/stderr | mail -s "System Alert" admin@example.com
    fi
    
    sleep 60
done

ケース3:デバッグとトラブルシューティング

# プロセスが何をしているか調査
strace -p $(pgrep problematic_app) 2>&1 | tee debug.log

# ネットワーク接続を確認
cat /proc/net/tcp

# 開いているファイルを確認
lsof -p $(pgrep nginx)

# システムコールの統計
perf stat -p $(pgrep mysql)

3.6 演習:/proc、/dev、/sysを探索して抽象化を実感

演習1:/procでシステムの「今」を見る

# 1. 起動してからの時間を確認
cat /proc/uptime
# 最初の数字:起動してからの秒数
# 次の数字:アイドル時間の合計秒数

# 2. 秒数を人間が読める形式に変換
uptime_seconds=$(cat /proc/uptime | cut -d' ' -f1)
uptime_days=$(echo "$uptime_seconds / 86400" | bc)
echo "システムは ${uptime_days} 日間稼働している"

# 3. 1秒ごとにメモリ使用量を観察
watch -n 1 'cat /proc/meminfo | grep -E "MemTotal|MemFree|MemAvailable"'

課題:別のターミナルで大きなアプリケーション(Firefoxなど)を起動し、メモリ使用量の変化を観察してください。

演習2:/devで対話的なデバイス操作

# 1. ターミナルデバイスを確認
tty
# 例:/dev/pts/0

# 2. 別のターミナルを開いて、そちらのttyを確認
# 例:/dev/pts/1

# 3. 最初のターミナルから、2番目のターミナルにメッセージを送る
echo "Hello from pts/0!" > /dev/pts/1

# 4. 音を鳴らす(ビープ音)
echo -e "\a" > /dev/console

# 5. ランダムな文字列を生成
cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 32 | head -n 1

発展課題/dev/zero/dev/urandomの読み取り速度を比較してください。なぜ違いがあるのか考えてみましょう。

演習3:/sysでハードウェアを制御

# 1. ネットワークインターフェースの一覧
ls /sys/class/net/

# 2. 各インターフェースの状態を確認
for iface in /sys/class/net/*; do
    echo -n "$(basename $iface): "
    cat $iface/operstate 2>/dev/null || echo "不明"
done

# 3. CPUの情報を調査
# 物理CPUの数
grep "physical id" /proc/cpuinfo | sort -u | wc -l

# CPUコアの数
grep "processor" /proc/cpuinfo | wc -l

# 4. バッテリー情報(ノートPCの場合)
if [ -d /sys/class/power_supply/BAT0 ]; then
    echo "バッテリー残量: $(cat /sys/class/power_supply/BAT0/capacity)%"
    echo "充電状態: $(cat /sys/class/power_supply/BAT0/status)"
fi

演習4:統合演習 - システム情報ダッシュボード作成

以下のスクリプトを作成して、システムの状態を一目で確認できるようにしましょう:

#!/bin/bash
# system_dashboard.sh

clear
echo "======================================"
echo "     System Dashboard - $(date)"
echo "======================================"

# ホスト名とカーネル
echo "Host: $(hostname)"
echo "Kernel: $(uname -r)"

# 稼働時間
echo "Uptime: $(uptime -p)"

# CPU情報
echo -e "\n--- CPU Information ---"
echo "Model: $(grep "model name" /proc/cpuinfo | head -1 | cut -d: -f2)"
echo "Cores: $(grep -c "processor" /proc/cpuinfo)"
echo "Load Average: $(cat /proc/loadavg | cut -d' ' -f1-3)"

# メモリ情報
echo -e "\n--- Memory Information ---"
total_mem=$(grep MemTotal /proc/meminfo | awk '{print $2}')
free_mem=$(grep MemAvailable /proc/meminfo | awk '{print $2}')
used_mem=$((total_mem - free_mem))
usage_percent=$((used_mem * 100 / total_mem))
echo "Total: $((total_mem / 1024)) MB"
echo "Used: $((used_mem / 1024)) MB ($usage_percent%)"

# ディスク情報
echo -e "\n--- Disk Information ---"
df -h / | tail -1

# ネットワーク
echo -e "\n--- Network Interfaces ---"
for iface in /sys/class/net/*; do
    if [ "$(cat $iface/operstate 2>/dev/null)" = "up" ]; then
        echo "$(basename $iface): UP"
        # 転送量を表示
        rx_bytes=$(cat $iface/statistics/rx_bytes)
        tx_bytes=$(cat $iface/statistics/tx_bytes)
        echo "  RX: $((rx_bytes / 1024 / 1024)) MB"
        echo "  TX: $((tx_bytes / 1024 / 1024)) MB"
    fi
done

# プロセス情報
echo -e "\n--- Top 5 Processes by CPU ---"
ps aux --sort=-%cpu | head -6 | tail -5 | awk '{printf "%-20s %5s%%\n", $11, $3}'

echo -e "\n--- Top 5 Processes by Memory ---"
ps aux --sort=-%mem | head -6 | tail -5 | awk '{printf "%-20s %5s%%\n", $11, $4}'

このスクリプトを実行して、すべての情報が「ファイル」から取得されていることを確認してください。

3.7 まとめ:複雑さを単純さで包む

抽象化の本質的な価値

「すべてはファイル」という思想は、単なる技術的な工夫ではない。これは、複雑なシステムを人間が理解し、制御できるようにするための根本的な設計原理である。

  • 複雑なハードウェア → シンプルなファイル操作
  • 動的なシステム状態 → 静的なファイルとして読める
  • 多様なデバイス → 統一的なインターフェース

現代システムへの影響

この思想は、現代のシステム設計にも大きな影響を与えている:

  1. REST API:すべてをリソース(≒ファイル)として扱う
  2. オブジェクトストレージ:あらゆるデータをオブジェクト(≒ファイル)として保存
  3. Kubernetes:すべての設定をYAMLファイルで管理

3.8 ストレージシステムの理解

RAID - 冗長性とパフォーマンスの両立

RAID(Redundant Array of Independent Disks)は、複数のハードディスクを組み合わせて、信頼性やパフォーマンスを向上させる技術です。

# 現在のRAID状況を確認
cat /proc/mdstat

# ソフトウェアRAIDの構築例(RAID1)
sudo mdadm --create /dev/md0 --level=1 --raid-devices=2 /dev/sdb1 /dev/sdc1

# RAID状態の詳細確認
sudo mdadm --detail /dev/md0

3.9 RAID構成詳細比較

各RAID構成の特徴比較

RAID構成詳細比較 - RAID 0、RAID 1、RAID 5の特徴

特徴比較表

RAID レベル 最小ディスク数 冗長性 パフォーマンス 容量効率 主な用途
RAID 0 2 なし 高速 100% 高速処理
RAID 1 2 あり 読み取り高速 50% 重要データ
RAID 5 3 あり バランス 67-94% サーバー
RAID 6 4 高い 中程度 50-88% 高信頼性
RAID 10 4 あり 高速 50% 高性能DB

次章への準備

次章では、これらの「ファイル」を実際に操作する方法を学ぶ。シェルとコマンドラインこそが、Linuxシステムと対話する最も強力な方法である。

なぜGUIではなくCUIが主流なのか。その答えは、自動化、再現性、そして効率性にある。小さなコマンドを組み合わせて、複雑なタスクを解決する。その技術を、次章で身につけていこう。

章末演習問題

問題1:基本理解の確認

以下の文章の空欄を埋めてください。

  1. Linuxの「すべてはファイル」という思想では、デバイスは(   )ディレクトリに、プロセス情報は(   )ディレクトリに、カーネルパラメータは(   )ディレクトリにファイルとして存在する。
  2. /dev/nullは(   )として機能し、/dev/zeroは(   )を生成し、/dev/randomは(   )を生成する。
  3. 標準入力、標準出力、標準エラー出力のファイルディスクリプタ番号は、それぞれ( )、( )、( )である。

問題2:概念の理解

次の質問に答えてください。

  1. なぜLinuxではハードウェアデバイスをファイルとして扱うのか、その利点を3つ挙げて説明してください。
  2. /procファイルシステムと/sysファイルシステムの違いと、それぞれの用途を説明してください。
  3. ブロックデバイスとキャラクタデバイスの違いを、具体例を挙げて説明してください。

問題3:実践的思考

以下のタスクをLinuxのファイルシステムを使って実現する方法を説明してください。

  1. 現在実行中のすべてのプロセスが開いているファイルの総数を調べたい。
  2. システムの温度センサーの値を定期的に記録したい(/sys配下にセンサー情報があると仮定)。
  3. 特定のプロセスのメモリ使用量の変化を5秒ごとに記録したい。

問題4:トラブルシューティング

以下の状況に対して、ファイルシステムの知識を使ってどのように対処するか説明してください。

  1. アプリケーションが大量のログを出力していて、ディスクを圧迫している。しかし、ログの内容は不要。
  2. USBデバイスを接続したが、認識されているか確認したい。
  3. 特定のプロセスがどのポートを使用しているか調べたい。

問題5:応用問題

次のシェルスクリプトを完成させてください。

#!/bin/bash
# システムリソースの使用状況をまとめるスクリプト

echo "=== System Resource Report ==="

# 1. CPU情報(コア数)を表示
echo "CPU Cores: $(        )"

# 2. 総メモリ量をGB単位で表示
echo "Total Memory: $(        ) GB"

# 3. 現在のネットワークインターフェースとその状態を表示
echo "Network Interfaces:"
for interface in $(        ); do
    state=$(        )
    echo "  $interface: $state"
done

# 4. 最も多くのファイルを開いているプロセスのPIDと名前を表示
echo "Process with most open files:"
# ここにコードを記述

問題6:発展的課題

  1. Dockerやコンテナ技術が、Linuxの「すべてはファイル」という思想をどのように活用しているか考察してください。特に、cgroupsとnamespacesの実装について言及してください。

  2. 「すべてはファイル」という抽象化の限界はどこにあると思いますか?この設計思想では対応が難しい、現代のコンピューティングの要求を挙げてください。