動作環境は Docker コンテナ上の Ubuntu:22.04 です。

docker pull ubuntu:22.04
docker run -it --privileged ubuntu:22.04

#Linux ファイルシステムとは

Linux においてファイルシステムは、ストレージデバイス上のデータを永続的に保存・管理するための仕組みであり、カーネルとユーザープログラムの間を橋渡しする重要な役割を担っている。

ファイルシステムの主な役割は以下の通り。

  • データの永続化: 電源を切ってもデータが失われないようにストレージへ書き込む
  • ディレクトリツリー構造: すべてのファイルを / (ルート) を頂点とした木構造で管理する
  • メタデータ管理: ファイルの所有者・パーミッション・タイムスタンプを保持する
  • アクセス制御: カーネルがパーミッションチェックを行い、不正アクセスを防ぐ

カーネルとのレイヤー関係は以下のようになっている。

flowchart TD
    A["ユーザープログラム (open / read / write)"]
    B["システムコール層"]
    C["VFS (Virtual File System)\n統一インターフェース"]
    D["具体的な FS ドライバ (ext4 / xfs / tmpfs ...)"]
    E["ブロックデバイス層 / メモリ"]

    A --> B --> C --> D --> E

#VFS (Virtual File System)

##なぜ VFS が必要か

Linux はディスクに書き込む ext4 や xfs、メモリ上に展開する tmpfs、ネットワーク越しにアクセスする NFS など、用途や実装の異なる多種多様なファイルシステムをサポートしている。

VFS がなければ、open()read() を呼び出すたびにプログラムが「どの FS か」を意識する必要がある。 VFS はこの差異を吸収し、どのファイルシステムに対しても同一のシステムコールインターフェースを提供する抽象化レイヤーである。

##主要な抽象オブジェクト

VFS は 4 つのオブジェクトでファイルシステムの状態を表現する。

オブジェクト役割
superblockマウントされたファイルシステム全体の情報 (ブロックサイズ・総 inode 数・空き容量など)
inodeファイルやディレクトリのメタデータ (パーミッション・所有者・サイズ・データブロックの場所)
dentryディレクトリエントリ。パス名の各コンポーネント (ディレクトリや ファイル名) と inode の対応を保持するキャッシュ
fileプロセスがオープンしているファイルの状態 (現在のファイルオフセットなど)

##システムコールから FS ドライバへの流れ

open("/etc/hostname", O_RDONLY)
  ↓ システムコール
  VFS: パスを dentry キャッシュで解決し、対応する inode を取得

  inode の i_op (inode operations) を呼び出す

  ext4 / xfs などの具体的な FS ドライバが実処理を担う

  ファイルディスクリプタを返す

各 FS ドライバは file_operationsinode_operations といった関数ポインタのテーブルを VFS に登録しており、VFS はそれを経由して具体的な処理を委譲する。

#inode

##inode とは

inode (index node) はファイルやディレクトリのメタデータを格納するデータ構造で、ファイルシステムごとにディスク上に固定数が確保される。

inode が持つ主な情報は以下の通り。

フィールド内容
ファイルタイプ通常ファイル・ディレクトリ・シンボリックリンク・デバイスファイルなど
パーミッション所有者・グループ・その他への rwx 権限
所有者UID / GID
タイムスタンプatime (最終アクセス) / mtime (最終更新) / ctime (メタデータ変更)
ファイルサイズバイト単位
リンクカウントこの inode を指しているハードリンクの数
データブロックポインタファイルの実データが格納されたブロックの場所

ファイル名は inode に含まれない。 ファイル名はディレクトリエントリ (dentry) が「名前 → inode 番号」のマッピングとして保持している。これがハードリンクの根拠になる。

##stat / ls -i / df -i コマンドで確認する

# inode の詳細情報を表示
stat /etc/hostname
  File: /etc/hostname
  Size: 13        	Blocks: 8          IO Block: 4096   regular file
Device: fd11h/64785d	Inode: 1577295     Links: 1
Access: (0644/-rw-r--r--)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2026-03-28 01:47:39.736176500 +0000
Modify: 2026-03-28 01:47:39.736176500 +0000
Change: 2026-03-28 01:47:39.736176500 +0000
 Birth: 2026-03-28 01:47:39.736176500 +0000

# inode 番号を表示
#  -i, --inode                print the index number of each file
ls -i /etc/hostname
1577295 /etc/hostname

# ファイルシステム全体の inode 使用状況
# df --help
# Usage: df [OPTION]... [FILE]...
# Show information about the file system on which each FILE resides,
# or all file systems by default.
df -i
Filesystem      Inodes  IUsed   IFree IUse% Mounted on
overlay        6553600 216708 6336892    4% /
tmpfs           250802    213  250589    1% /dev
shm             250802      1  250801    1% /dev/shm
/dev/vdb1      6553600 216708 6336892    4% /etc/hosts

#ハードリンク・シンボリックリンク

##ハードリンク

ハードリンクは、既存の inode に対して別のディレクトリエントリ (名前) を追加することで作成される。同一の inode を指す複数の名前が存在する状態であり、inode のリンクカウントが増加する。

  • inode 番号が同じため、どちらの名前で編集しても内容は同じ
  • いずれかの名前を削除してもリンクカウントが 0 になるまでデータは消えない
  • ディレクトリへのハードリンクは通常禁止 (ループが発生するため)
  • 異なるファイルシステム間では作成できない

# ハードリンクを作成
ln original.txt hardlink.txt

# inode 番号が同じことを確認
ls -li original.txt hardlink.txt
556656 -rw-r--r-- 2 root root 12 Mar 28 01:55 hardlink.txt
556656 -rw-r--r-- 2 root root 12 Mar 28 01:55 original.txt
# ↑ 同じ inode 番号 (524300)、リンクカウントが 2

# 片方を削除してもデータは残る
rm original.txt
cat hardlink.txt  # まだ読める

# リンクカウントが 1 に戻る
ls -li hardlink.txt
556656 -rw-r--r-- 1 root root 12 Mar 28 01:55 hardlink.txt

使用例として、pnpm はグローバルのストアから特定のプロジェクトへのハードリンクを作成することで、ディスク使用量を節約している。

##シンボリックリンク

シンボリックリンク (ソフトリンク) は、リンク先のパス文字列を内容として持つ独立したファイルである。inode 番号は元ファイルとは異なる。

  • 別のファイルシステムや存在しないパスへもリンクできる
  • ディレクトリへのリンクも作成可能
  • リンク先が削除されると「壊れたリンク」になる (dangling symlink)
  • ls -l でリンク先が表示される
# シンボリックリンクを作成
ln -s /etc/hostname mylink


# inode 番号が異なることを確認
ls -li /etc/hostname mylink
1577295 -rw-r--r-- 1 root root 13 Mar 28 01:47 /etc/hostname
 556656 lrwxrwxrwx 1 root root 13 Mar 29 09:05 mylink -> /etc/hostname
# ↑ inode 番号が異なる、パーミッションが l で始まる

# リンク先の確認
readlink mylink
/etc/hostname

##ハードリンク vs シンボリックリンクの比較

項目ハードリンクシンボリックリンク
inode同じ
別 FS またがり不可
ディレクトリ不可 (通常)
リンク先削除後データ保持壊れたリンクになる

#ディレクトリ構造 (FHS)

##FHS とは

FHS (Filesystem Hierarchy Standard) は、Linux におけるディレクトリ構造の標準規格。各ディレクトリが何を格納すべきかを定義しており、主要なディストリビューション (Ubuntu・CentOS・Debian など) が準拠している。

これにより、ディストリビューションが異なっても設定ファイルやバイナリの場所が予測できるようになっている。

##主要ディレクトリ

ディレクトリ内容
/ルートディレクトリ。すべてのファイルシステムの頂点
/bin基本的なコマンド (ls, cp, mv など)。シングルユーザーモードでも必要なもの
/sbinシステム管理コマンド (root 向け: fdisk, ip など)
/usrユーザー向けプログラムとデータ。/usr/bin, /usr/lib など
/etcシステム全体の設定ファイル
/home一般ユーザーのホームディレクトリ
/rootroot ユーザーのホームディレクトリ
/var可変データ (ログ・スプール・キャッシュなど)
/tmp一時ファイル。再起動で消えることがある
/procプロセス情報などを公開する仮想ファイルシステム (procfs)
/sysデバイス・ドライバ情報を公開する仮想ファイルシステム (sysfs)
/devデバイスファイル (ディスク・端末・乱数生成器など)
/lib共有ライブラリ (/bin/sbin が依存するもの)
/mnt一時的なマウントポイント
/mediaリムーバブルメディア (USB・CD-ROM など) のマウントポイント
/bootブートローダーとカーネルイメージ
/optサードパーティのソフトウェア
/run起動後に生成されるランタイムデータ (PID ファイルなど)

現在はほとんどのケースで /bin/usr/bin のシンボリックリンクとなっている。 昔は /bin/usr/bin は別のディスクに置かれることがあり、 /bin にはシステム起動・緊急修復に最低限のコマンドが置かれていた。 (/usr がマウントされていなくても動く必要があったため。)

# ルートディレクトリの一覧
ls /
bin  boot  dev  etc  home  lib  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var

# 主要ディレクトリのサイズ確認
# du --help
# Usage: du [OPTION]... [FILE]...
# or:  du [OPTION]... --files0-from=F
# Summarize disk usage of the set of FILEs, recursively for directories.
du -sh /usr /var /etc
70M	/usr
5.1M	/var
640K	/etc

#ファイルシステムの種類

##主要なファイルシステムの比較

FS用途ジャーナリング最大ファイルサイズ特徴
ext4Linux 汎用あり16TB安定性・実績が高い。Linux のデフォルト
xfs大容量・高性能あり8EB大規模ファイルに強い。RHEL のデフォルト
btrfsスナップショット・RAIDあり16EBCoW・スナップショット・圧縮が得意
tmpfsメモリ上の一時FSなしRAM 依存再起動で消える。/tmp/run に使用
vfatUSB・SD カード互換なし4GBWindows との互換性が高い

##ジャーナリング

ジャーナリングとは、データを書き込む前に「これから行う操作」をジャーナル (ログ) に記録しておく仕組み。クラッシュや電源断が発生した場合でも、ジャーナルを再実行またはロールバックすることでファイルシステムの整合性を保てる。

ジャーナリングがない FS では、クラッシュ後に fsck で手動チェックが必要になる場合がある。

##コマンドで確認する

# ブロックデバイスの一覧とマウント情報
# lsblk --help
# List information about block devices.
#         NAME  device name
#      MAJ:MIN  major:minor device number
#           RM  removable device
#         SIZE  size of the device
#           RO  read-only device
#         TYPE  device type
#  MOUNTPOINTS  all locations where device is mounted
lsblk
NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
vda     253:0    0   20G  0 disk
|-vda1  253:1    0   19G  0 part
|-vda15 253:15   0   99M  0 part
`-vda16 259:0    0  923M  0 part
vdb     253:16   0  100G  0 disk
`-vdb1  253:17   0  100G  0 part /etc/hosts
                                 /etc/hostname
                                 /etc/resolv.conf
vdc     253:32   0 53.1M  1 disk

# ファイルシステムの種類と使用状況
# -T, --print-type      print file system type
df -T
Filesystem     Type    1K-blocks    Used Available Use% Mounted on
overlay        overlay 102624184 2229468  95135556   3% /
tmpfs          tmpfs       65536       0     65536   0% /dev
shm            tmpfs       65536       0     65536   0% /dev/shm
/dev/vdb1      ext4    102624184 2229468  95135556   3% /etc/hosts

# ファイルシステムの詳細情報 (ext4)
# tune2fs: ext2、ext3、ext4ファイルシステムのパラメータを変更する
tune2fs -l /dev/vda1 | grep -E "Filesystem (state|created|features)"
Filesystem features:      has_journal ext_attr resize_inode dir_index filetype needs_recovery extent 64bit flex_bg sparse_super large_file huge_file dir_nlink extra_isize metadata_csum
Filesystem state:         clean
Filesystem created:       Sun Jul 27 11:29:37 2025

#マウント

##マウントとは

マウントとは、ストレージデバイスやファイルシステムをディレクトリツリーの特定の場所 (マウントポイント) に接続する操作。アンマウントするまでそのディレクトリ以下でデバイスのファイルシステムが利用できる。

##mount / umount コマンド

# ファイルシステム種別を明示してマウント
mkdir /mnt/data
mount -t ext4 /dev/vdb1 /mnt/data

# 現在のマウント状況を確認
mount
# または
findmnt
TARGET             SOURCE     FSTYPE  OPTIONS
/                  overlay    overlay rw,relatime,lowerdir=/var/lib/containerd/io.containerd.snapshotter.v1.overlayfs/snapshots/840/fs:/var/lib/containerd/io.c
|-/mnt/data        /dev/vdb1  ext4    rw,relatime
|-/proc            proc       proc    rw,nosuid,nodev,noexec,relatime
|-/dev             tmpfs      tmpfs   rw,nosuid,size=65536k,mode=755,inode64
| |-/dev/console   devpts[/0] devpts  rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666
| |-/dev/pts       devpts     devpts  rw,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=666
| |-/dev/mqueue    mqueue     mqueue  rw,nosuid,nodev,noexec,relatime
| `-/dev/shm       shm        tmpfs   rw,nosuid,nodev,noexec,relatime,size=65536k,inode64
|-/sys             sysfs      sysfs   rw,nosuid,nodev,noexec,relatime
| `-/sys/fs/cgroup cgroup     cgroup2 rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot
|-/etc/resolv.conf /dev/vdb1[/docker/containers/5ba3be7065491808c8d893e298c8445c2a6f66071f86306e6122f03be2399693/resolv.conf]
|                             ext4    rw,relatime
|-/etc/hostname    /dev/vdb1[/docker/containers/5ba3be7065491808c8d893e298c8445c2a6f66071f86306e6122f03be2399693/hostname]
|                             ext4    rw,relatime
`-/etc/hosts       /dev/vdb1[/docker/containers/5ba3be7065491808c8d893e298c8445c2a6f66071f86306e6122f03be2399693/hosts]
                              ext4    rw,relatime

# アンマウント
umount /mnt/data

##/etc/fstab

/etc/fstab は起動時に自動マウントするファイルシステムの設定ファイル。

# <device>         <mountpoint>  <fstype>  <options>         <dump> <pass>
/dev/sda1          /             ext4      defaults           0      1
/dev/sda2          none          swap      sw                 0      0
UUID=xxxx-yyyy     /mnt/data     ext4      defaults,nofail    0      2
tmpfs              /tmp          tmpfs     defaults,size=512m 0      0
フィールド説明
deviceデバイスファイル・UUID・ラベルなど
mountpointマウント先ディレクトリ
fstypeファイルシステム種別
optionsマウントオプション (defaults / ro / noexec など)
dumpdump コマンドのバックアップ対象 (通常 0)
pass起動時の fsck 実行順 (0=スキップ、1=最初、2=その後)
# fstab の設定を実際にマウントせず検証
mount -a --fake

# fstab にある未マウントのエントリをすべてマウント
mount -a

##bind mount

bind mount は既存のディレクトリを別の場所にも見せる仕組みで、デバイスを伴わない点が通常のマウントと異なる。

# /data を /mnt/data にも見せる
mount --bind /data /mnt/data

# 読み取り専用で bind mount
mount --bind /data /mnt/data
mount -o remount,ro,bind /mnt/data

コンテナ技術での活用として、Docker はホストのディレクトリをコンテナ内に公開するためにバインドマウントを使用している。

# Docker でホストディレクトリをコンテナにマウント
docker run -v /host/data:/container/data ubuntu:22.04

# 内部的には Mount Namespace 内で bind mount が行われる
# コンテナの /container/data がホストの /host/data を指す

#特殊ファイルシステム

Linux ではディスクを持たない仮想的なファイルシステムが存在し、カーネル内部の情報をファイルとして公開している。

##procfs (/proc)

プロセス情報やカーネルパラメータをファイルとして公開する仮想ファイルシステム。

# 実行中プロセスの情報
ls /proc/$$
attr        cmdline          environ  io                 loginuid   mounts      oom_adj        projid_map  setgroups     statm           timers
autogroup   comm             exe      ksm_merging_pages  map_files  mountstats  oom_score      root        smaps         status          timerslack_ns
auxv        coredump_filter  fd       ksm_stat           maps       net         oom_score_adj  sched       smaps_rollup  syscall         uid_map
cgroup      cpuset           fdinfo   latency            mem        ns          pagemap        schedstat   stack         task            wchan
clear_refs  cwd              gid_map  limits             mountinfo  numa_maps   personality    sessionid   stat          timens_offsets

# プロセスのコマンドライン引数
cat /proc/$$/cmdline | tr '\0' ' '
/bin/bash root@5ba3be706549:~#

# プロセスのステータス
cat /proc/$$/status
Name:	bash
State:	S (sleeping)
Pid:	1
VmRSS:	    2432 kB     # 物理メモリ使用量

# CPU 情報
cat /proc/cpuinfo | head -5
processor	: 0
BogoMIPS	: 48.00
Features	: fp asimd evtstrm aes pmull sha1 sha2 crc32 atomics fphp asimdhp cpuid asimdrdm jscvt fcma lrcpc dcpop sha3 asimddp sha512 asimdfhm dit uscat ilrcpc flagm sb paca pacg dcpodp flagm2 frint
CPU implementer	: 0x61
CPU architecture: 8

# メモリ情報
cat /proc/meminfo | head -5
MemTotal:        2006420 kB
MemFree:         1058944 kB
MemAvailable:    1745248 kB
Buffers:          208752 kB
Cached:           512312 kB

# カーネルパラメータ (sysctl の実体)
cat /proc/sys/net/ipv4/ip_forward
0

# 動的に変更
echo 1 > /proc/sys/net/ipv4/ip_forward
# または
sysctl -w net.ipv4.ip_forward=1

##sysfs (/sys)

デバイス・ドライバ・バスなどのカーネルオブジェクトを階層構造で公開する仮想ファイルシステム。/proc より整理された設計になっている。

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

# インターフェースの状態確認
cat /sys/class/net/eth0/operstate
up

root@5ba3be706549:~# cat /sys/class/net/eth0/speed
10000   # Mbps

# ブロックデバイス情報
ls /sys/class/block/
loop0  loop1  loop2  loop3  loop4  loop5  loop6  loop7  vda  vda1  vda15  vda16  vdb  vdb1  vdc

cat /sys/class/block/vda/size  # 512バイトセクタ単位
41943040

# cgroups も sysfs 経由で管理されている
ls /sys/fs/cgroup/
cgroup.controllers  cgroup.procs  cgroup.subtree_control  memory.pressure  ...

##tmpfs

RAM 上に展開されるファイルシステム。アクセスが非常に高速な反面、再起動するとデータは消える。

# tmpfs のマウント状況を確認
df -T | grep tmpfs
tmpfs          tmpfs       65536       0     65536   0% /dev
shm            tmpfs       65536       0     65536   0% /dev/shm

# 任意のサイズで tmpfs をマウント
mount -t tmpfs -o size=256m tmpfs /mnt/ramdisk

# /dev/shm は tmpfs で、プロセス間共有メモリに利用される
ls /dev/shm

# Docker コンテナで tmpfs を使う
docker run --tmpfs /tmp:size=128m ubuntu:22.04

#ファイルディスクリプタ

##ファイルディスクリプタとは

ファイルディスクリプタ (fd) は、プロセスがオープンしたファイルやソケット・パイプなどを識別するための非負整数。プロセスごとに「オープンファイルテーブル」が管理されており、fd はそのテーブルへのインデックスとして機能する。

システムコール open() が成功するとカーネルは fd を返し、以降 read()write() の第一引数として fd を渡すことで操作できる。

##標準入出力

プロセス起動時に以下の 3 つの fd が自動的に割り当てられる。

fd名前用途
0stdin (標準入力)キーボード入力など
1stdout (標準出力)通常の出力
2stderr (標準エラー)エラーメッセージ

##/proc//fd での確認

# 現在のシェルがオープンしているファイルディスクリプタ

root@5ba3be706549:~# ls -la /proc/$$/fd
total 0
dr-x------ 2 root root  4 Apr  1 13:40 .
dr-xr-xr-x 9 root root  0 Mar 28 01:47 ..
lrwx------ 1 root root 64 Apr  1 13:40 0 -> /dev/pts/0   # stdin
lrwx------ 1 root root 64 Apr  1 13:40 1 -> /dev/pts/0   # stdout
lrwx------ 1 root root 64 Apr  1 13:40 2 -> /dev/pts/0   # stderr
lrwx------ 1 root root 64 Apr  1 14:24 255 -> /dev/pts/0 # bash 内部用

# ファイルをオープンして fd を確認
exec 3< /etc/hostname   # fd 3 で /etc/hostname を読み取り専用で開く
ls -la /proc/$$/fd/3
lrwx------ 1 user user 64 Mar 28 10:00 3 -> /etc/hostname
exec 3<&-               # fd 3 を閉じる

# lsof で特定プロセスの fd を確認
lsof -p $$
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
bash    1234 user  cwd    DIR    8,1     4096  524288 /home/user
bash    1234 user    0u   CHR  136,0      0t0    3 /dev/pts/0
bash    1234 user    1u   CHR  136,0      0t0    3 /dev/pts/0
bash    1234 user    2u   CHR  136,0      0t0    3 /dev/pts/0

##リダイレクトとパイプ

リダイレクトやパイプはファイルディスクリプタを付け替える操作として実現される。

# stdout (fd 1) をファイルにリダイレクト
command > output.txt
# 内部的には fd 1 を output.txt の fd に付け替えている

# stderr (fd 2) を stdout (fd 1) にリダイレクト
command 2>&1

# stdout と stderr を同じファイルへ
command > output.txt 2>&1
# bash 4.0 以降の短縮記法
command &> output.txt

# パイプ: 左コマンドの stdout を右コマンドの stdin に接続
ls -la | grep ".txt"
# 内部的には pipe(2) システムコールで無名パイプを作成し
# ls の fd 1 と grep の fd 0 を繋いでいる

# /dev/null に捨てる (fd を /dev/null に向ける)
command > /dev/null 2>&1

##プロセスごとの fd 上限

1 プロセスが同時にオープンできる fd 数には上限がある。

# ソフトリミット (プロセスが自分で変更できる上限)
ulimit -n
1024

# ハードリミット (root のみ変更可能な上限)
ulimit -Hn
1048576

# 上限を変更 (セッション内のみ有効)
ulimit -n 4096

# システム全体の上限
cat /proc/sys/fs/file-max
9223372036854775807

# 現在システム全体でオープンされている fd 数
cat /proc/sys/fs/file-nr
6144    0    9223372036854775807
# 使用中  解放済み  上限

Web サーバーやデータベースなどの多接続サービスでは fd の枯渇がボトルネックになることがあるため、ulimit -n/etc/security/limits.conf で適切に調整することが重要。