硬链接和软链接的区别--inode 详解 📅 2026/7/13 3:00:47 OSTEP中说明在文件系统中关于存储虚拟化有两个抽象第一个是文件而文件的低级名称通常称为inode号。第二个抽象则是目录它包含一个用户可读名字低级名字对的列表。本文从 stat 命令出发通过 ln、strace 一步步验证硬链接、软链接、删除的本质并把这些现象跟 inode 的设计联系起来。一、前言日常学习时会产生几个现实中的疑问每当在目录里用ls -l看到的一大堆信息——大小、权限、时间——存在哪里硬链接的本质到底是什么跟软链接有什么区别rm删文件是真的删掉了吗这篇文章我通过理论加实验的方式对这些问题一一解答。二、stat 命令–详细理解文件信息平时查看文件信息最常用的是ls -lrrrr-VMware-Virtual-Platform:~/os_lab/inode_test$ls-la.txt -rw-rw-r--1rr rr6Jul1200:01 a.txt这里能看到权限、硬链接数、所有者、大小、修改时间。但有一个最关键的信息没显示——inode 号。加-i参数就能看到rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$ls-lia.txt442403-rw-rw-r--2rr rr6Jul1200:01 a.txt第一列的442403就是这个文件的 inode 号。每个文件都有一个唯一的 inode 号可以理解为文件在文件系统中的身份证号码。更详细的信息需要用stat命令rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$stata.txt File: a.txt Size:6Blocks:8IO Block:4096regularfileDevice: 811h/2065d Inode:442403Links:2Access:(0664/-rw-rw-r--)Uid:(1000/ rr)Access:2026-07-12 00:00:00.000000000 0000 Modify:2026-07-12 00:01:00.000000000 0000 Change:2026-07-12 00:01:05.000000000 0000每一行都是在读 inode 这个数据结构里的一个字段stat 输出里面的信息Inode: 442403inode 号C 里叫st_ino文件的身份证Size: 6文件大小st_size单位字节Links: 2硬链接数st_nlink有几个目录项指向这个 inode权限字文件类型和权限st_mode0664 就是 -rw-rw-r–Uid/Gid所有者st_uid和所属组st_gid三个时间st_atime最后访问 /st_mtime最后修改 /st_ctime最后状态变更Blocks: 8磁盘占用st_blocks计数单位 512 字节不是文件系统的分配单位为什么不是 Blocks: 1文件系统按 4096 字节分配IO Block6 字节文件也得占一整块。st_blocks只是个统计口径单位固定是 512 字节POSIX 遗留标准所以显示4096 ÷ 512 8。不是文件系统按 512字节 分配。这里有一个细节Links硬链接数显示为 2。普通文件刚创建时硬链接数应该是 1——这个值偏高是因为我在开始写这篇文章之前已经对这个文件做过硬链接实验并非刚创建时的状态。用 C 程序读取 inode 信息命令行的stat本质上就是读取 inode 结构体。用 C 语言也可以做同样的事说明一下inode 号和硬链接数不可能为负所以用无符号 %lu文件大小虽然也不为负但 off_t 标准定义是有符号的因为 lseek 需要支持负偏移所以用 %ld。#includestdio.h#includesys/stat.hintmain(){structstats;stat(a.txt,s);printf(inode: %lu\n,s.st_ino);printf(size: %ld\n,s.st_size);printf(hard links: %lu\n,s.st_nlink);return0;}编译运行rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$ gcc stat_test.c-ostat_test./stat_test inode:442403size:6hard links:2C 语言的struct stat和磁盘上的 inode 结构是对应的——当你调用stat()时内核从 inode 缓存若未缓存则先从磁盘读到缓存读取 inode 结构体填充这个结构体返回给用户态。三、文件名的误区很多人会下意识地认为文件名就是文件。但通过上面的 stat 输出可以意识到一个问题文件名并没有出现在 inode 里。inode 存的是大小、权限、时间、数据块位置——但不存文件名。在文章开头介绍了目录包含了用户可读名字所以文件名存放在目录里。然而目录本质上也是一个文件也有自己的 inode。但目录的数据块内容很特殊——它存的是一张映射表文件名 → inode号/目录的结构大致如下. → 2 当前目录 .. → 2 父目录根目录的父目录也是自己 home → 12345 usr → 67890 tmp → 11111这就是为什么 inode 里没有文件名——文件名是属于目录的不属于文件本身。同一个 inode 可以有多个文件名这就是硬链接。用图来表示这三者的关系数据块inode目录inode 号inode 号大小硬链接数指针内容目录项 a.txtinode 442403目录项 b.txt6 字节2数据块位置hello\流程目录项存文件名→inode号 → inode存元数据数据块指针 → 数据块存真正内容。目录的底层实现不只是简单列表把目录看作一张文件名→inode号的映射表在概念上是正确的。但不同文件系统实际存储这张表的方式不同。ext4 用线性列表。目录的数据块中按顺序存放一个个条目每个条目包含 inode 号、文件名的长度和文件名本身。要查找一个文件ext4 从头到尾扫描这个列表逐个比较文件名。目录中的文件越多查找就越慢O(n) 复杂度。所以如果一个目录下放几十万个小文件性能会显著下降。XFS 用 B 树索引。XFS 在目录较大时自动把线性列表转换成 B 树结构查找复杂度降到 O(log n)。所以在 XFS 上即使一个目录下有百万个文件创建和查找的性能依然稳定。这个差异在面试中可能会被问到。一个简单的验证方式在 ext4 和 XFS 上分别创建大量文件对比ls的响应时间。但无论底层怎么存目录对外暴露的抽象始终没变——文件名到 inode 号的映射。上层应用不需要关心它是线性扫描还是 B 树查找。四、硬链接同一个 inode 的多个名字4.1 实验rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$echohelloa.txt rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$lna.txt b.txt# 创建硬链接rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$ls-li442403-rw-rw-r--2rr rr6Jul1200:01 a.txt442403-rw-rw-r--2rr rr6Jul1200:01 b.txt两个文件 inode 号相同都是 442403。这说明a.txt 和 b.txt 根本就是同一个文件——只是两个名字指向同一个 inode。注意看硬链接数列ls -l的第二列从 1 变成了 2。这个数字就是 inode 里 nlink 记录的硬链接引用计数表示有多少个目录项指向这个 inode。4.2 测试删除原文件对硬链接的影响rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$rma.txt rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$ls-li442403-rw-rw-r--1rr rr6Jul1200:01 b.txt rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$catb.txt helloa.txt被删了但b.txt还在内容完好。因为rm a.txt只是删除了指向 inode 442403 的一个目录项nlink 从 2 减为 1。inode 还在数据块还在通过 b.txt 照样可以访问。4.3 硬链接的本质简单来说硬链接不是创建文件而是创建一个新的目录项指向已有的 inode。硬链接目录项 a.txtinode 442403目录项 b.txthello\创建硬链接后两个目录项指向同一个 inode共享同一份数据。4.4 硬链接的限制不能跨文件系统。因为 inode 号只在当前文件系统内唯一不同文件系统的 inode 号是各自独立管理的同一个编号可能指向完全不同的文件。不能链接目录。假设现在有个目录/home/user/projects/如果允许对它建硬链接在 projects 里建一个指向根目录/的硬链接/home/user/projects/root → /然后从根目录遍历/ → /home/ → /user/ → /projects/ → /root又回到 / → /home/ → ……find、du、rm -rf一旦陷入循环就跳不出来了。所以 POSIX 禁止对目录建硬链接。.和..虽然也是目录硬链接但那是内核自己维护的遍历时内核会识别并直接跳过。自己个人建的目录硬链接内核没法区分只能往下继续遍历必然会陷入循环。五、软链接存路径的独立文件5.1 实验rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$ln-sa.txt c.txt# 创建软链接rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$ls-li442403-rw-rw-r--1rr rr6Jul1200:01 a.txt442398lrwxrwxrwx1rr rr5Jul1200:01 c.txt -a.txt注意区别c.txt 的 inode 号是 442398跟 a.txt442403完全不同。文件类型显示为llink大小只有 5 个字节——正好是目标文件名 “a.txt” 的长度。5.2 软链接数据块内容rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$catc.txt hello通过软链接访问到了内容。但如果看它本身的数据其实是路径字符串rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$ readlink c.txt a.txt软链接的工作原理可以用图说清cat c.txt 时软链接目录项 c.txtinode 442398a.txt读 c.txt 数据块拿到 a.txt访问 a.txt 的 inode读数据块 hello\c.txt 是一个独立文件它的数据块存的是路径字符串 “a.txt”。5.3 测试删除原文件对软链接的影响rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$rma.txt rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$catb.txt# 硬链接hello rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$catc.txt# 软链接cat: c.txt: No suchfileor directory硬链接还能访问软链接报错——因为原文件的路径不存在了。详细来说cat c.txt 时读取 c.txt 的数据块也就是拿到了字符串 a.txt然后再从目录中根据文件名和 inode 号映射找对应的 inode 号但是此时已经 remove 了 a.txt所以找不到对应的 inode 号进而报错。这就是所谓的悬空引用dangling reference。5.4 硬链接、软链接特点与对比维度硬链接软链接inode 号与原文件相同独立 inode数据块共享不占额外数据空间存路径字符串占少量数据空间nlink硬链接数增加原 inode 的引用计数不影响原文件跨文件系统不能可以链接目录不能POSIX 禁止可以原文件被删还在引用计数 0悬空路径失效ls -li表现同 inode 号不同 inode 号显示-一张图汇总三者的关系数据目录读取路径后解析a.txtinode 442403b.txt硬链接c.txt软链接inode 442398数据块 hello数据块 a.txt六、删除(rm)的本质6.1 strace 观察rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$stracermb.txt21|grepunlink unlinkat(AT_FDCWD,b.txt,0)0rm的底层是unlinkat系统调用新版本 Linux 用 unlinkat 替代了早期的 unlink。该调用做了两件事从目录中删除 “b.txt” 这个映射条目将 inode 442403 的 nlink 减 1此时 nlink 从 1 变成 0如果满足以下两个条件内核才会真正释放这个 inode 和数据块nlink 0没有目录项指向它没有进程持有这个文件的文件描述符没有进程正在读写它这也解释了为什么有时磁盘空间没释放——可能是一个进程仍然持有已删除文件的 fd。6.2 用 strace 验证 cp 和 mv 的底层操作继续实验对比 cp 和 mvrrrr-VMware-Virtual-Platform:~/os_lab/inode_test$echohelloa.txt rrrr-VMware-Virtual-Platform:~/os_lab/inode_test$stracecpa.txt d.txt21|grep-Eopenat.*\.txtopenat(AT_FDCWD,a.txt, O_RDONLY)3openat(AT_FDCWD,d.txt, O_WRONLY|O_TRUNC)4cp 创建了一个新的 inoded.txt打开了源文件a.txt从源文件读数据写入新文件数据被完整复制了一遍。因为 grep 只过滤了 openat 的行所以没有看到调用 read/write 的语句若想查看可执行stracecpa.txt d.txt21|grep-Eread|writerrrr-VMware-Virtual-Platform:~/os_lab/inode_test$stracemvd.txt e.txt21|greprenamerenameat2(AT_FDCWD,d.txt, AT_FDCWD,e.txt, RENAME_NOREPLACE)0mv 只调了一个 rename 系统调用——没有读数据也没有写数据只是在目录中改了文件名。这也说明了 mv 命令无论文件有多大都能快速执行。这里有一个边界情况跨文件系统的 mv 相当于 cp rm。因为 rename 无法跨文件系统操作系统会退化成复制到目标位置→删除原文件inode 号会发生变化。七、排查场景理论归理论在实践中可能会碰到以下场景磁盘空间没释放——df -h显示满了但找不出大文件。用lsof | grep deleted排查看是否有进程持有了已删除文件的 fd。原因就是文章讲的nlink 归零但 fd 还在内核不释放。硬链接数异常——stat看到 nlink 比你预期的多。用find / -inum inode号找出所有指向这个 inode 的文件名看看多了哪个硬链接。软链接断裂——ls -l看到一堆红底白字的链接。用find -L /path -type l批量列出所有悬空引用然后决定是删掉还是重建目标文件。八、总结读完本文之后即可解决开头的几个问题ls -l看到的大小、权限、时间存在哪里——存在 inode 里。stat命令输出的每一行本质上都是读 inode 结构体里的一个字段。硬链接的本质是什么跟软链接有什么区别——硬链接不是创建文件而是在目录中新增一个条目指向已有 inodenlink 加 1删除原文件后数据依然可访问。软链接是创建一个独立文件类型l数据块存放的是目标路径字符串cat时先读 c.txt 的数据块拿到 “a.txt”再去目录查找 a.txt 的 inode然后读取数据若原文件已被删除路径解析失败即报错。rm 删文件到底删了什么——删目录项inode 引用计数nlink减 1。只有 nlink 归零且没有进程持有文件描述符fd时内核才真正释放磁盘空间。本文是作者关于操作系统文件系统的第一篇文章。本文涉及到 inode 结构硬链接和软链接的本质。但这只是文件系统的一小部分。后续会继续推进关于文件系统的相关博客一步步往深了走深入理解文件系统。