2021 年,一次"我明明把一个 5GB 的大文件 rm 删掉了,df 一看磁盘空间【一个字节都没释放】"的事故,把我对"文件"和"删除"这两件事的理解,从头到尾翻新了一遍。那台服务器磁盘报警,根分区快满了。我盯上了 /data 下一个 5GB 的旧数据文件 bigdata.bin,确认它没用了,rm 干净利落地删掉。我心想:5G 到手,警报该解除了。可我 df -h 一刷新——根分区的 Used,纹丝不动,还是那个快满的数字。我以为 df 缓存了,等了一会儿再看,还是没变。我又怀疑是不是没删干净,ls /data/bigdata.bin——No such file or directory,文件确确实实没了。我甚至 du -sh /data 看了下,/data 这个目录的大小,确实【少了 5GB】。可整个磁盘的 df,就是不动。我彻底懵了:文件,我删了;du 也证实 /data 小了 5G;可这 5G 的空间,既没回到 df 的"可用"里,也不在 /data 里了。它【既被删了,又没被释放】——它去哪了?一个文件被 rm 之后,它占的空间,难道不是【立刻】就还给磁盘了吗?如果不是——那 rm 这个动作,到底【删掉的是什么】?这件事逼着我把文件名、inode、数据块这三层结构、硬链接与软链接的天壤之别、inode 的引用计数,还有 rm 真正的含义,彻底理清了。本文复盘这次实战。
问题背景
环境:CentOS 7,磁盘紧张,要删大文件腾空间
事故现象:
- rm 删掉了一个 5GB 的 /data/bigdata.bin
- ★ df -h 一看,磁盘 Used 一点没变,空间没释放
- ls 确认文件确实没了,du 也显示 /data 少了 5G
现场排查:
# 1. ★ 确认文件真的删了
$ ls -l /data/bigdata.bin
ls: cannot access '/data/bigdata.bin': No such file or directory
# 2. ★ df 看磁盘 —— 空间没还回来
$ df -h /
Filesystem Size Used Avail Use% Mounted on
/dev/vda1 100G 95G 3.5G 97% / # ★ 删了 5G,Used 还是 95G
# 3. ★★ 关键:删之前,我看一下这个文件的"链接数"
# (其实是删之前就该看的)
$ ls -l /data/bigdata.bin # (假设还没删时)
-rw-r--r-- 2 root root 5368709120 ... bigdata.bin
# ^ ★★ 这个数字是 2,不是 1!
# 4. ★ 这个 "2" 是什么:它表示这个文件有"2 个名字"
$ stat /data/bigdata.bin
# Size: 5368709120 ...
# Inode: 1572864 Links: 2 # ★★ Links: 2
# 5. ★ 用 inode 号,找出指向同一个文件的所有名字
$ find / -inum 1572864 2>/dev/null
/data/bigdata.bin
/backup/bigdata.bin # ★★ 还有一个!在 /backup 下
根因(后来想清楚的):
1. ★ 一个文件,在磁盘上不是"一个东西",是【三层】:
- 文件名:挂在某个目录里的一个"标签";
- inode:文件的元信息 + 数据块索引;
- 数据块:真正存内容的地方(这才是占的 5GB)。
2. ★ "文件名"和"inode"是【多对一】的:同一个
inode,可以挂【好几个文件名】。每个名字,叫一个
"硬链接"。
3. ★ inode 里有个【引用计数(link count)】,记着
"现在有几个文件名指向我"。ls -l 第二列就是它。
4. ★ 之前有人对这个文件做过 ln /data/bigdata.bin
/backup/bigdata.bin —— 这创建了一个【硬链接】,
于是这个 inode 的引用计数变成了 2。
5. ★★ rm 这个命令,它【不直接删数据】。它做的是:
把那个"文件名"从目录里抹掉,然后把 inode 的
引用计数【减 1】。
6. ★ 我 rm /data/bigdata.bin -> 引用计数从 2 减到
1 -> ★ 计数还不是 0 -> 内核【绝不释放】数据块。
因为 /backup/bigdata.bin 这个名字,还指着它。
真相:rm 删的是"名字",不是"文件内容"。只有当
最后一个名字也没了、引用计数归 0,空间才真正释放。
修复 1:删了文件空间没释放——问题出在"链接数"
# === ★ 先解开本文最直接的困惑:rm 了为什么不释放 ===
# === ★ 颠覆认知:rm 不是"删除文件" ===
# ★ 我们都以为 rm bigfile = "把这个文件删掉、把它
# 占的空间还给磁盘"。★ 这个理解,是错的。
# ★ rm 这个命令,它的真实动作,准确地说是
# ★ "unlink" —— 解除一个【链接】。它做两件事:
# - ① 把这个【文件名】,从它所在的目录里抹掉;
# - ② 把这个文件名指向的 inode,引用计数【减 1】。
# ★ ★ 注意:rm 自始至终,【没有碰过数据块】。
# === ★ 那数据块(那 5GB)什么时候才释放 ===
# ★ 内核的规则只有一条:★ 当一个 inode 的引用计数
# 【减到 0】时 —— 也就是,指向它的【最后一个
# 文件名】也被删掉时 —— 内核才会把这个 inode
# 连同它的【数据块】,真正回收、还给磁盘。
# ★ 只要引用计数还 > 0,哪怕只剩 1,数据块就【一定
# 还在】,空间就【一定不释放】。
# === ★ 看一个文件的"引用计数"在哪 ===
$ ls -l /data/somefile
# -rw-r--r-- 1 root root 1024 ... somefile
# ^ ★ 第二列那个数字,就是引用计数(硬链接数)
$ stat /data/somefile
# Inode: 1572880 Links: 1 # ★ Links 就是它
# ★ 普通文件,这个数通常是 1(只有一个名字)。
# ★ 如果它 > 1,说明这个文件【有多个名字(硬链接)】。
# === ★ 本文事故,完整对上了 ===
# ★ bigdata.bin 的引用计数是 2 —— 它有两个名字:
# /data/bigdata.bin 和 /backup/bigdata.bin。
# ★ 我 rm /data/bigdata.bin -> 计数 2 减到 1。
# ★ 计数没到 0(/backup/那个名字还在)-> 内核【不
# 释放】那 5GB。这就是 df 纹丝不动的原因。
# === ★ 怎么真正释放这 5GB ===
$ find / -inum 1572864 2>/dev/null # ★ 先找出所有指向它的名字
# /backup/bigdata.bin
$ rm /backup/bigdata.bin # ★ 把最后一个名字也删掉
$ df -h / # ★ 这下计数归 0,5GB 才回来
# ★ 删之前【务必】先 find -inum 确认还有哪些名字、
# 那些名字是不是也确实没用了 —— 别误删了还在用的。
# === 认知 ===
# ★ rm 不是"删除文件",它的真实动作是 unlink:把一个
# 【文件名】从目录里抹掉,并把对应 inode 的引用计数
# 减 1 —— 它【从不直接碰数据块】。数据块(磁盘空间)
# 只有在引用计数【减到 0】、即最后一个文件名也被删
# 时才释放。ls -l 第二列 / stat 的 Links 就是引用
# 计数,> 1 说明这文件有多个名字(硬链接)。
修复 2:一个文件其实是三层——文件名、inode、数据块
# === ★ 把"文件"这个东西,拆成三层看清楚 ===
# === ★ 第一层:数据块(data block)===
# ★ 文件的【内容】,实实在在存在磁盘的数据块里。
# 一个 5GB 的文件,就占了 5GB 的数据块。
# ★ ★ 这一层,才是"占磁盘空间"的那一层。
# === ★ 第二层:inode ===
# ★ inode 是文件的"档案卡":记着文件的权限、属主、
# 大小、三个时间戳,以及【内容散落在哪些数据块】
# 的索引。
# ★ ★ 注意:inode 里【没有文件名】。inode 只有一个
# 编号(inode number),没有名字。
$ ls -i /etc/hosts
# 1234567 /etc/hosts # ★ 前面那个数字是 inode 号
# === ★ 第三层:文件名(目录条目)===
# ★ 文件名不在文件里,也不在 inode 里。文件名,是
# 记在【目录】里的。
# ★ 一个目录,本质就是一张表,每一行是
# ★ "一个文件名 -> 一个 inode 号" 的对应。
# ★ 所以"文件名"这一层,真正的身份是:★ 一个指向
# inode 的【指针 / 引用】。
# === ★ 三层的关系,串起来 ===
# 文件名(目录里的条目)
# | 指向
# v
# inode(档案卡,含数据块索引)
# | 索引到
# v
# 数据块(真正的内容,占空间的就是它)
# === ★ 关键洞察:文件名和 inode 是"多对一" ===
# ★ 一个 inode,可以被【多个文件名】同时指向。
# ★ 这些名字,地位【完全平等】—— 没有谁是"正本"、
# 谁是"副本"。它们都只是"指向同一个 inode 的
# 指针"而已。每一个这样的名字,就叫一个【硬链接】。
# ★ inode 里那个"引用计数",数的就是:★ 现在有
# 几个文件名,正指向我。
# === ★ 于是 rm / "删除" 这件事,本质清楚了 ===
# ★ rm 一个名字 = 从目录表里划掉一行 + inode 计数减 1。
# ★ 数据块(内容)是挂在 inode 下的。inode 只要还被
# 【至少一个名字】指着(计数 > 0),它和它的数据块
# 就都【必须活着】。
# ★ 最后一个名字也没了 -> 计数 0 -> inode 没人要了
# -> 内核回收 inode + 数据块。空间这才释放。
# === 认知 ===
# ★ 一个文件是【三层】:数据块(存内容,占空间的是
# 它)、inode(档案卡,记元信息和数据块索引,只有
# 编号没有名字)、文件名(记在目录里,本质是"名字
# -> inode 号"的指针)。文件名和 inode 是【多对一】:
# 一个 inode 可被多个平等的名字指向,每个名字就是
# 一个硬链接。rm 删的是名字层,数据块只在 inode
# 引用计数归 0 时才回收。
修复 3:硬链接——同一个 inode 的多个名字
# === ★ 把硬链接(ln,不加 -s)彻底讲清楚 ===
# === ★ 创建一个硬链接:ln(不加 -s)===
$ ln /data/bigdata.bin /backup/bigdata.bin
# ★ 这一行做的事:在 /backup 目录里,新增一个名字
# "bigdata.bin",让它指向【和 /data/bigdata.bin
# 完全同一个 inode】。
# ★ 它【没有复制】任何数据。两个名字,共用一份数据块。
# === ★ 验证:两个名字,inode 号完全一样 ===
$ ls -li /data/bigdata.bin /backup/bigdata.bin
# 1572864 -rw-r--r-- 2 root root 5368709120 ... /data/bigdata.bin
# 1572864 -rw-r--r-- 2 root root 5368709120 ... /backup/bigdata.bin
# ^^^^^^^ ★ inode 号一样 ^ ★ 引用计数都是 2
# ★ inode 号相同 = 它们【就是同一个文件】,只是
# 有两个名字。引用计数 2 = 有两个名字指着它。
# === ★ 硬链接的几个关键性质 ===
# ★ 性质 1:★ 多个硬链接,地位完全平等。没有"原件"
# 和"链接"之分。/data/那个 和 /backup/那个,谁是
# "本尊"?★ 都是,也都不是 —— 它们是平级的两个名字。
# ★ 性质 2:改其中一个,另一个【同步变】。因为它们
# 是同一份数据。echo x >> /data/bigdata.bin,
# /backup/bigdata.bin 的内容也跟着变。
# ★ 性质 3:删其中一个,另一个【完好无损】。计数
# 减 1 而已,数据还在。
# === ★ 硬链接的两个硬限制(★ 很重要)===
# ★ 限制 1:★ 硬链接【不能跨文件系统 / 跨分区】。
# inode 号只在【一个文件系统内部】唯一。/data 和
# /backup 若在不同分区,硬链接做不了:
$ ln /data/x.bin /mnt/otherdisk/x.bin
# ln: failed to create hard link: Invalid cross-device link
# ★★ "Invalid cross-device link" —— 见到这个报错,
# 就是你想跨分区做硬链接了,这是物理上不可能的。
# ★ 限制 2:★ 普通用户【不能给目录做硬链接】。
# (只允许给文件做。否则会破坏目录树结构。)
# === ★ 找出一个文件的所有硬链接 ===
$ ls -l /data/bigdata.bin # 第二列 > 1,就说明有多个
$ find / -inum 1572864 2>/dev/null # 按 inode 号找出全部名字
$ find / -samefile /data/bigdata.bin 2>/dev/null # 或用 -samefile
# ★ ★ 删一个引用计数 > 1 的文件前,务必先这样查一遍
# —— 否则你以为删了,空间根本不会释放。
# === 认知 ===
# ★ 硬链接(ln 不加 -s)= 给同一个 inode 再起一个
# 名字,不复制数据。两个名字 inode 号相同、地位
# 完全平等(没有原件副本之分)、改一个另一个同步
# 变、删一个另一个无损。两个硬限制:★ 不能跨文件
# 系统(报 Invalid cross-device link)、不能给目录
# 做。删引用计数 > 1 的文件前,必须 find -inum 或
# -samefile 查清所有名字。
修复 4:软链接——一个存着路径字符串的小文件
# === ★ 把软链接(符号链接,ln -s)讲清楚 ===
# === ★ 创建一个软链接:ln -s ===
$ ln -s /data/bigdata.bin /home/me/data.bin
# ★ -s = symbolic(符号)。这创建的是【软链接】,
# 和硬链接是【完全不同】的东西。
# === ★ 软链接的本质:它是一个【独立的小文件】 ===
# ★ ★ 软链接,自己是一个【独立的文件,有自己的
# inode】。它的"内容",就是一个【字符串】——
# 它指向的那个目标的【路径】。
$ ls -li /home/me/data.bin
# 2000001 lrwxrwxrwx 1 me me 17 ... data.bin -> /data/bigdata.bin
# ^^^^^^^ ★ 它有自己的 inode 号(和目标不同!)
# ^ ★★ 第一个字符是 l,表示这是个软链接(link)
# ^^^^^^^^^^^^^ ★ 它存的就是这串路径
# ★ 看那个 Size:17 —— 这个软链接文件本身才 17 字节,
# 因为它只存了 "/data/bigdata.bin" 这 17 个字符。
# ★ 它【不占】目标文件的空间。
# === ★ 软链接是怎么工作的:路径替换 ===
# ★ 你访问 /home/me/data.bin,内核发现它是个软链接,
# 就读出它内容里那串路径,然后【转而去访问那串
# 路径】。这叫"跟随(follow)软链接"。
# ★ 所以软链接像一个"路标":它本身不是目的地,它
# 上面写着"目的地在那边 ->",内核照着走过去。
# === ★ 软链接最大的特点:它会"悬空" ===
# ★ 因为软链接存的只是【一串路径文字】,它和目标
# 之间【没有 inode 级别的绑定】。
# ★ 一旦目标文件被删除 / 改名 / 移走:
$ rm /data/bigdata.bin # 把目标删了
$ cat /home/me/data.bin
# cat: /home/me/data.bin: No such file or directory
$ ls -l /home/me/data.bin
# lrwxrwxrwx ... data.bin -> /data/bigdata.bin # ★ 链接还在
# ★ ★ 软链接文件【自己还在】,但它指向的路径已经
# 空了 —— 这叫【悬空链接(dangling link)】。
# 一个指向虚无的路标。
# === ★ 软链接能做硬链接做不到的两件事 ===
# ★ ① 软链接【可以跨文件系统】—— 它只存一串路径
# 文字,路径写跨分区的地址完全没问题。
# ★ ② 软链接【可以指向目录】—— 部署里 current ->
# releases/v5 这种,用的就是软链接指目录。
# === ★ 看软链接到底指向哪、最终指向谁 ===
$ readlink /home/me/data.bin # 看它直接指向的路径
$ readlink -f /home/me/data.bin # ★ 一路跟到底,看最终的真实文件
$ ls -lL /home/me/data.bin # -L:看它【目标】的信息,而非链接自身
# === 认知 ===
# ★ 软链接(ln -s)和硬链接是完全不同的东西:它是
# 一个【独立的小文件,有自己的 inode】,内容就是
# 目标的【路径字符串】(像个路标)。ls -l 第一个
# 字符是 l。访问它时内核读出路径再转去访问目标。
# ★ 目标一旦被删/改名/移走,软链接就【悬空】——
# 自己还在,却指向虚无。软链接能跨文件系统、能指向
# 目录,这两点硬链接都做不到。
修复 5:硬链接 vs 软链接——完整对照与各自的坑
# === ★ 把两者并排放,一次看明白 ===
# === ★ 核心区别一张表 ===
# 维度 硬链接(ln) 软链接(ln -s)
# ----------------------------------------------------------
# 本质 同一 inode 的另一个名字 独立的文件,存目标路径
# 有没有自己 inode 没有(共用目标的) ★ 有(自己一个)
# inode 号 和目标【相同】 和目标【不同】
# ls -l 首字符 普通(-) ★ l
# 占空间 不额外占 占一点点(存路径串)
# 删了目标后 ★ 不受影响(数据还在) ★ 悬空,失效
# 跨文件系统 ★ 不行 ★ 可以
# 链接目录 ★ 不行 ★ 可以
# 引用计数 会让目标计数 +1 ★ 不影响目标计数
# === ★ 坑 1:rm 一个硬链接 > 1 的文件,空间不释放 ===
# ★ 本文的事故。删之前 ls -l 看第二列,> 1 就要警惕。
# === ★ 坑 2:误以为软链接"占了双份空间" ===
# ★ 有人看到 du 把软链接和目标都算了,以为浪费。其实
# 软链接自己只占几十字节。倒是【硬链接】要小心 du:
$ du -sh /data /backup
# ★ 如果 /data/big 和 /backup/big 是硬链接,du 分别
# 算两个目录时,可能【把那 5GB 算两次】(取决于
# 是否在同一次 du 调用里)。同一次 du 多个目录,du
# 会去重;分两次单独 du,就会各算一遍 -> 看着像
# 占了 10G,实则 5G。
# === ★ 坑 3:cp 一个软链接,你以为复制了链接 ===
$ cp /home/me/data.bin /tmp/d.bin
# ★ ★ cp 默认会【跟随软链接】—— 它复制的是【目标
# 文件的内容】,/tmp/d.bin 是一个真实的大文件,
# 不再是链接!想原样复制链接本身,要 cp -P 或 cp -d。
$ cp -P /home/me/data.bin /tmp/d.bin # -P:不跟随,链接照搬
# === ★ 坑 4:tar / rsync 对链接的处理,要确认 ===
# ★ tar 默认会把软链接当链接打包;rsync 要不要跟随
# 软链接,有 -l / -L 等一堆选项。批量同步前务必
# 确认,否则链接到了对端可能变成实文件、或悬空。
# === ★ 坑 5:删目录时,小心里面的软链接 ===
# ★ rm -rf 一个【软链接指向的目录】,行为和带不带
# 末尾斜杠有关,容易误删到目标目录的真实内容。
# 操作软链接指向的目录前,先 readlink -f 看清楚
# 它到底指到哪。
# === ★ 该用哪个:经验法则 ===
# ★ 要"同一份数据多个入口、删任意一个其他不受影响"
# -> 硬链接(但受同文件系统限制)。
# ★ 要"跨分区、要链目录、要一个能随时改指向的路标"
# (如部署的 current -> release)-> 软链接。
# ★ ★ 日常绝大多数场景,用软链接。它直观、能跨盘、
# 能链目录,且 ls 一眼能看出它是个链接。
# === 认知 ===
# ★ 硬链接是同 inode 的另一个名字(inode 号相同、删
# 目标不受影响、不能跨文件系统、不能链目录);软
# 链接是存路径的独立小文件(inode 号不同、ls 首字符
# l、目标删了就悬空、能跨盘能链目录)。常见坑:删
# 硬链接数 > 1 的文件不释放空间、分次 du 硬链接会
# 重复计数、cp 默认跟随软链接复制的是目标内容(要
# cp -P 才照搬链接)。日常多数场景用软链接。
修复 6:链接与删除排查纪律
# === 这次事故暴露的认知盲区,定几条纪律 ===
# === 1. ★ rm 删的是"文件名",不是数据;空间只在引用计数归 0 才释放 ===
# === 2. ★ 删大文件前,先 ls -l 看第二列引用计数,> 1 说明有别的硬链接 ===
$ ls -l 大文件 # 第二列就是硬链接数
# === 3. ★ 引用计数 > 1,先 find -inum 或 -samefile 找出所有名字 ===
$ find / -samefile /path/to/file 2>/dev/null
# === 4. ★ 硬链接和目标 inode 号相同;软链接 ls -l 首字符是 l ===
$ ls -li 文件 # 看 inode 号 和 首字符
# === 5. ★ 硬链接不能跨文件系统(报 Invalid cross-device link)不能链目录 ===
# === 6. ★ 软链接只存一串路径,目标删了它就悬空,自己还在却指向虚无 ===
# === 7. 看软链接指向哪、最终指向谁,用 readlink 和 readlink -f ===
$ readlink -f 软链接
# === 8. ★ cp 默认跟随软链接复制的是目标内容,要原样复制链接用 cp -P ===
# === 9. 分次 du 多个含硬链接的目录会重复计数,以 df 的真实占用为准 ===
# === 10. 排查"删了文件空间没释放"的步骤链 ===
$ df -h # ① 确认空间确实没释放
$ ls -l 被删文件 2>/dev/null # ② 删之前看引用计数(>1?)
$ find / -samefile 文件 2>/dev/null # ③ 找出还有哪些硬链接名字
$ lsof | grep deleted # ④ 或:文件被进程句柄占着(另一种)
# 引用计数没到 0,或有进程占着句柄 -> 空间都不会释放。
命令速查
需求 命令
=============================================================
看文件引用计数(硬链接数) ls -l(第二列)/ stat 看 Links
看文件 inode 号 ls -i 文件
创建硬链接 ln 源文件 新名字
创建软链接 ln -s 目标 链接名
找出某文件的所有硬链接 find / -samefile 文件 2>/dev/null
按 inode 号找文件 find / -inum 编号 2>/dev/null
看软链接指向哪 readlink 软链接
看软链接最终的真实文件 readlink -f 软链接
原样复制软链接(不跟随) cp -P 软链接 目标
看软链接目标的信息 ls -lL 软链接
查已删除但被句柄占着的文件 lsof | grep deleted
口诀:rm 删的是名字不是数据 引用计数归 0 空间才释放
硬链接同 inode 不能跨盘 软链接是存路径的小文件目标没了就悬空
避坑清单
- rm 删的是文件名不是数据,它把名字从目录抹掉并让 inode 引用计数减 1
- 磁盘空间只有在 inode 引用计数减到 0 即最后一个文件名也删掉时才真正释放
- 删大文件前先 ls -l 看第二列引用计数,大于 1 说明这文件还有别的硬链接名字
- 一个文件是三层:文件名是目录里的指针,inode 是档案卡,数据块才是占空间的内容
- 硬链接是同一个 inode 的多个平等名字,inode 号相同,删其中一个另一个完好无损
- 硬链接不能跨文件系统会报 Invalid cross-device link,也不能给目录创建硬链接
- 软链接是一个独立的小文件,内容是目标的路径字符串,ls -l 第一个字符是 l
- 软链接的目标被删除改名或移走后会悬空,链接文件还在却指向一个不存在的路径
- cp 默认会跟随软链接复制的是目标文件内容,要原样复制链接本身得用 cp -P
- 分多次 du 含硬链接的目录会把同一份数据重复计数,真实占用以 df 为准
总结
这次"删了文件空间却没释放"的事故,纠正了我一个关于"文件"的、藏得极深的错觉。在我过去的脑子里,一个文件,就是【一个东西】——一个不可分割的整体。它有一个名字,名字就是它;它有内容,内容也是它。名字、内容、它占的那块空间,在我心里是【焊死在一起】的一坨。基于这个"一坨"的认知,"删除"就顺理成章地是一个【原子动作】:我 rm 一下,这一坨——名字、内容、空间——就【一起】灰飞烟灭了。所以当 rm 之后,名字没了(ls 找不到了)、空间却还在,我整个人是崩溃的:我那个"一坨"的整体,怎么会【碎开】?怎么会一半消失、一半赖着?这在我的世界观里,是不可能发生的事。直到我把 inode 那一层挖出来,我才看清,我以为的那"一坨",其实从来就是【三层】拼起来的:最底下是数据块,是真正占地方的内容;中间是 inode,是那张管着内容的档案卡;最上面,是文件名——而文件名,根本不是"文件本身",它只是挂在某个目录里、【指向 inode 的一个指针】。我恍然大悟:原来一个文件可以有【好几个】这样的指针,它们平起平坐,都指着同一个 inode。而 rm,这个我以为"毁天灭地"的命令,它的真名其实叫 unlink——"解除一个链接"。它做的事,谦逊得超出我的想象:它只是【拔掉一根指针】,然后看一眼"还有没有别的指针指着这个 inode"。还有,它就什么都不做;一根都不剩了,它才通知内核回收。我那 5GB 没释放,不是 bug,是 rm 在尽职:它拔掉了 /data 那根指针,可 /backup 那根还指着——数据还"被人需要着",它当然不能动。复盘到最深我才明白,我犯的错,是把"名字"和"实体"这两样东西,当成了一样。我以为删掉名字,就等于销毁实体。可名字从来只是【对实体的一个引用】,实体真正的存亡,不取决于"某一个名字在不在",而取决于"【还有没有任何人】在引用它"。这件事一旦想通,我发现它的影子到处都是:一个对象的内存,不在你把某个变量置空时释放,而在【最后一个引用它的变量】都没了时,垃圾回收才动手——这就是引用计数,和 inode 是【同一个东西】。一个 Docker 镜像,你 rmi 它报"image is being used",因为还有容器引用着它。一个共享库,还有进程加载着它,你就删不干净。一份配置、一个密钥、一张数据库里的主表记录——但凡还有【别处】引用着它,它就不该、也不会,因为你在【这一处】划掉了它的名字,就真的消失。这次最大的收获,是我学会了把"删除"这个念头,从"销毁那个东西"改成"解除我和那个东西的这一处关联"。在我 rm 任何重要东西之前,我现在会多问一句:我划掉的,是它【唯一】的名字,还是【众多名字之一】?在我之外,还有没有别人,正握着指向它的另一根指针?rm 不叫"删除",它叫 unlink——这个我用了十几年才真正读懂的名字,把一个朴素的道理刻进了我心里:你能亲手了断的,从来只是【你和它的那一根链接】;那个东西本身,是死是活,要等所有牵着它的手,都一一松开。
—— 别看了 · 2026