- 物理機リージョン/AZ
- ハイパーバイザVM exit で捕まえる
- VMゲストOS
- コンテナnamespace / cgroup
- プロセスアプリ / DB今ここ
前回は書いたデータがどこにあるかを追いました。今回はその書いた先のファイルが、名前とは別の実体であることを見ます。
まず結論
ディレクトリの中の名前と、ファイルの実体(inode)は別です。名前は実体を指す参照にすぎないので、同じ実体に複数の名前を付けられますし、名前を消しても実体はすぐには消えません。
領域が戻るのは、名前が1つも残らず、かつ誰も開いていないときです。片方でも欠ければ、`ls` から消えたファイルがディスクを占めます。
あるプロセスがファイルを開いたまま、別の場所から `rm` でその名前を消しました。開いているプロセスはその後どうなるでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
名前は、実体を指す参照でしかない
rm が行き着く unlink(2) の説明は、こう始まります。
unlink() deletes a name from the filesystem.
消すのは名前です。ファイルでも中身でもありません。ディレクトリは名前と実体の対応表であり、unlink() はその表から1行を外すだけです。
では実体はいつ消えるのか。続きが条件を書いています。
If that name was the last link to a file and no processes have the file open, the file is deleted and the space it was using is made available for reuse.
条件が2つ並んでいます。「最後のリンクであること」かつ「どのプロセスも開いていないこと」。片方でも欠ければ領域は戻りません。次の段落が欠けた場合を述べます。
If the name was the last link to a file but any processes still have the file open, the file will remain in existence until the last file descriptor referring to it is closed.
名前が1つも無くても、開いている fd が1本でもあればファイルは存在し続けると書かれています。
この構造は、本コース5本目『ファイルディスクリプタが指しているもの — 番号の先にある表』と同じ形です。あちらは fd → open file description → inode の三層、今回は名前 → inode。名前と実体は別のものという同じ形が、階層を変えて2回出てきます。
名前を消しても、空き容量が1バイトも戻らない
64 MiB の tmpfs(メモリ上の FS。/dev/shm)に 32 MiB のファイルを作り、fd を開いたまま unlink() した実測です(カーネル 6.10.14-linuxkit / aarch64 / Alpine 3.24.1)。
[1] available before create : 65536 KB[2] available after 32MiB write : 32768 KB[3] name unlinked. ls -l says : ls: /dev/shm/big.bin: No such file or directory[4] available after unlink (fd OPEN): 32768 KB <-- NOT reclaimed[5] read through the open fd : b'HEADER1234'[6] file size via fstat : 33554432 bytes, st_nlink = 0[7] /proc/self/fd/3 -> /dev/shm/big.bin (deleted)[8] available after close(fd) : 65536 KB <-- reclaimed now
[3] で名前は消え、ls から見えません。ところが [4] の空きは 32768 KB のまま、[2] と同じです。1バイトも戻っていません。
[5] では、消したはずのファイルから HEADER1234 が読めています。[6] はサイズが 33554432 バイトのまま保たれ、st_nlink = 0 だと示します。リンク数はハードリンクの本数なので(inode(7) の Link count)、0 は「この実体を指す名前が1つも無い」という意味です。名前が無いのに実体は生きている。
[7] はカーネル側の見え方で、/proc/self/fd/3 のリンク先に (deleted) が付いています。そして [8]、close(fd) した直後に 65536 KB へ戻りました。戻ったのは unlink の時点ではなく close の時点です。
`unlink(2)` の後にファイルの領域が再利用可能になる条件として、公式の記述に合うのはどれですか。
同じ実体に、名前は何本でも張れる
名前が実体への参照にすぎないなら、参照は複数あってよいはずです。それがハードリンクです。link(2) はこう書きます。
This new name may be used exactly as the old one for any operation; both names refer to the same file (and so have the same permissions and ownership) and it is impossible to tell which name was the "original".
どちらが「元」だったかは区別できない、と明言されています。「元ファイルとそのリンク」という非対称な捉え方を、公式が否定しています。コピーでもありません。中身は1つで、指す名前が2つあるだけです。
symlink(7) は制約の理由まで書いています。
Hard links may not refer to directories (to prevent the possibility of loops within the filesystem tree, which would confuse many programs) and may not refer to files on different filesystems (because inode numbers are not unique across filesystems).
ハードリンクが別のファイルシステムへ張れないのは、inode 番号がファイルシステムをまたいで一意ではないからです。番号で名指しする以上、番号の意味が通じる範囲の外へは張れません。link(2) の ERRORS 節も EXDEV を挙げますが、そちらは事実を述べるだけで理由は書かれていません。
対してシンボリックリンクは別の作りです。
A symbolic link is a special type of file whose contents are a string that is the pathname of another file, the file to which the link refers. (...) In other words, a symbolic link is a pointer to another name, and not to an underlying object. For this reason, symbolic links may refer to directories and may cross filesystem boundaries.
指しているのが別の名前であって、下にある実体ではない。だからディレクトリも指せるし境界も越えられる、と因果で書かれています。
- ・rm a.txt は上段の箱を1つ外すだけ。inode は st_nlink = 1 で残る
- ・s.txt は消えた名前を指したまま残り、たどれなくなる
同じ rm が、片方には効かず片方を壊す
同じ実体に2つの名前を張り、シンボリックリンクを足してから rm を1回だけ実行した実測です(overlay 上)。
===== 1. hard link: same inode =====914269 -rw-r--r-- 2 root root 17 Sep 17 21:23 a.txt914269 -rw-r--r-- 2 root root 17 Sep 17 21:23 b.txt--- stat st_nlink ---a.txt inode=914269 nlink=2 size=17b.txt inode=914269 nlink=2 size=17===== 2. symlink: different inode, and it points to a NAME =====914269 -rw-r--r-- 2 root root 17 Sep 17 21:23 a.txt914261 lrwxrwxrwx 1 root root 5 Sep 17 21:23 s.txt -> a.txta.txt inode=914269 nlink=2 type=regular files.txt inode=914261 nlink=1 type=symbolic link===== 3. delete the original NAME; hard link still has the data =====--- after rm a.txt ---914269 -rw-r--r-- 1 root root 17 Sep 17 21:23 b.txt914261 lrwxrwxrwx 1 root root 5 Sep 17 21:23 s.txt -> a.txtb.txt content: original contents.txt (symlink) read attempt:cat: can't open 's.txt': No such file or directory-> dangling symlink, as expectedb.txt nlink=1===== 4. EXDEV: hard link cannot cross filesystems =====Filesystem Size Used Available Use% Mounted onoverlay 452.1G 88.3G 340.8G 21% /shm 64.0M 0 64.0M 0% /dev/shmln: /dev/shm/x.txt: Cross-device link
区画1で、a.txt と b.txt の inode 番号はともに 914269。同じ実体を2つの名前が指しており、3列目のリンク数はどちらも 2 です。区画2の s.txt だけが 914261 と別番号で、リンク数は 1、種別は symbolic link。
区画3が山場です。rm a.txt を1回実行しました。b.txt は中身が読めています(original content)。リンク数が 2 から 1 に減っただけで実体は無傷です。ところが同じ操作で s.txt は壊れました。指しているのが実体ではなく a.txt という名前だからです。
1回の rm が、片方には効かず、片方には致命的でした。差は「何を指しているか」だけです。同じことは本物の ext4 上でも確認しており、overlay 固有の挙動ではありません。
区画4は EXDEV です。overlay 上のファイルから shm(別のマウント)へハードリンクを張ろうとして Cross-device link で断られています。番号の意味が通じる範囲の外へは張れない、という理由が表れています。
ハードリンクが同じファイルシステム内でしか作れないのはなぜですか。
ハードリンク
同じ inode(実測では 914269)に2つ目の名前を張ります。どちらが元かは区別できません。片方を消してもリンク数が 2 から 1 に減るだけで中身は無傷です。番号で名指しするため、同じファイルシステム内でしか作れません(EXDEV)。
シンボリックリンク
中身が別のファイルのパス名という文字列である、別の inode(実測では 914261)を持つファイルです。指しているのは名前なので、元の名前が消えると開けません。実体を名指ししないぶん、ディレクトリも指せますしファイルシステムも越えられます。
同じ1回の rm が、ハードリンクには効かず、シンボリックリンクを壊しました。この差を生んでいるものは何でしょうか。
それぞれが「何を」指しているか、そして rm が「何を」消したかを並べてみてください。
この章のまとめ
unlink(2)が消すのは名前(unlink() deletes a name from the filesystem.)。ファイルの中身でも inode でもない。- 領域が戻る条件は2つの AND。「最後のリンクであること」かつ「どのプロセスも開いていないこと」。
- 実測では、
unlink後も空きは 32768 KB のまま、st_nlink = 0なのに中身が読め、close(fd)した時点で 65536 KB へ戻った(tmpfs 上・環境依存の値。主役は順序)。 - ハードリンクは同じ inode への2つ目の名前。
link(2)は「どちらが元かは区別できない」と書く。コピーではない。 - シンボリックリンクは「別の名前へのポインタであって、下にある実体へのポインタではない」(
symlink(7))。だからディレクトリも指せ、ファイルシステムも越えられる。 - 越えられない理由は「inode 番号がファイルシステムをまたいで一意ではないから」。表示上は
Cross-device link(EXDEV)。 st_nlinkの定義があるのはstat(2)ではなくinode(7)の Link count の項。
次はメモリが平らな一続きでないことを見ます。関連するクイズは『OSと低レイヤのしくみ』の群にあります。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
`unlink(2)` の DESCRIPTION 冒頭が、この呼び出しが何を消すと書いているのはどれですか。
用語集(5語)
- inode
- ファイルの実体側のメタデータ。symlink(7) は inode 番号を「an index into the inode table, which contains metadata about all files on a filesystem」と説明している。名前はこの実体への参照にすぎない。
- ハードリンク
- 同じ inode を指す2つ目以降のディレクトリ項目。link(2) で作る。どちらが元かは区別できず、同一ファイルシステム内でしか作れない。
- シンボリックリンク
- 中身が別のファイルのパス名という文字列である特別なファイル。symlink(7) は「a pointer to another name, and not to an underlying object」と書く。だからディレクトリも指せるしファイルシステムも越えられる。
- st_nlink(リンク数)
- そのファイルへのハードリンクの本数。定義は inode(7) の Link count の項。名前が1つも残っていなければ 0 になる。
- EXDEV
- ハードリンクを別のマウント先へ張ろうとしたときのエラー。link(2) の ERRORS に「oldpath and newpath are not on the same mounted filesystem.」とある。表示は `Cross-device link`。