Appearance
硬链接和软链接
2026 大纲 四(二)4 硬链接和软链接,是上一条无环图目录中"多个路径指向同一文件"的两种具体实现。
一个文件挂两个名字,有两种挂法
上一节末尾提到 . 和 .. 是两个硬链接。这一节把"链接"这件事本身讲清楚。
它要解决的需求很实际:同一份数据,想让它在两个地方都能被找到。 比如一个共享库,/usr/lib 和 /usr/local/lib 下都该看得见; 比如一个项目文件,想在桌面上放个入口。
file-concept 里那条判据已经给了第一种做法: inode 里不存文件名 ⇒ 名字和本体是分开的 ⇒ 那就在另一个目录里再加一条目录项,让它的 inode 号指向同一个 inode。 两个名字从此完全平等,谁也不是"原件"、谁也不是"副本"。 这就是硬链接。
第二种做法完全不同:新建一个独立的小文件,内容就是目标的路径字符串。 访问它时,系统读出这串路径,再重新解析一遍。这就是软链接(符号链接)。
两者的全部差别,都由"链接里存的是什么"这一条推出来—— 硬链接的目录项里存 inode 号,软链接文件的内容里存路径字符串。
存 inode 号意味着直接指向本体:零额外开销,但跨不出本文件系统 (inode 号只在本分区内唯一)。存路径字符串意味着要重走一遍解析: 跨得过文件系统,代价是一次读加一整轮路径解析, 而且目标一旦被删或改名,这串路径就指空了。
这一节剩下的每一行对照,包括删除时各自发生什么、引用计数怎么变、 搬家之后哪种还能用,都不必单独记——回到"存的是什么"这一句就能推出来。
一、硬链接
硬链接是让多个目录项指向同一个 inode——同一栋房子有两个门牌号,拆掉一个门牌号房子还在。
目录A: report.txt → inode 42 (links=2)
目录B: 共享文件.txt → inode 42 (links=2)
│
┌─────┴─────┐
│ 数据块... │
└───────────┘删除时,只有当链接计数减为 0(再没有名字能找到它)且没有任何进程还打开着它(再没有进程在用它)才真正释放。只有前者为 0 时,文件在目录里已经消失,但仍开着它的进程照样能读写,直到最后一个 close。
两条限制及其来源:不能跨文件系统,因为 inode 号只是本分区 inode 数组的下标,换个文件系统同一个号指向的是另一个文件;不能链接目录,因为目录之间若能互建硬链接就可能连成环,遍历时死循环(部分系统允许 root 创建)。软链接存的是路径、访问时按路径重新解析,而路径解析会经过挂载表、能从一个文件系统跨到另一个(见文件系统挂载),所以两条限制都不存在。
二、软链接(符号链接)
软链接是一个独立文件,内容是目标文件的路径名字符串——像桌面上的快捷方式,原文件被删了快捷方式就失效。
目录A: report.txt → inode 42 (links=1)
目录B: shortcut.txt → inode 99 (links=1)
│
┌─────┴─────────┐
│ "/A/report.txt"│ ← 存的是路径
└───────────────┘删软链接本身只删这个链接文件,目标不受影响;删目标文件则软链接变成悬空链接(dangling link),访问时报错。
快速符号链接:不一定要占一个数据块
软链接的全部内容就是一条路径字符串,通常只有几十字节;而 inode 里那十几个块指针字段(12 个直接 + 3 个间接,
软链接到底多花几次磁盘 I/O:与直接打开、走硬链接逐段比一遍(想弄清那个额外开销式子的来源、或换一组前提估代价时展开)
自造数据:某 Unix 文件系统,根目录的 inode 常驻内存,其余 inode 与数据块全在磁盘上,无任何缓存命中。真实文件是 /data/pub/note.txt;/home/li/h 是它的硬链接;/home/li/n 是指向它的软链接,内容为绝对路径 /data/pub/note.txt。
按目录那篇的结论:根 inode 在内存时,解析深度
(1)直接打开真实文件。 深度 data、pub、note.txt):
(2)打开硬链接。 /home/li/h 深度也是 3,且目录项里直接写着 inode 42 这个号,读到它就等于拿到了文件本体:
(3)打开软链接。 分三段:
| 段 | 做什么 | I/O |
|---|---|---|
① 解析 /home/li/n | 深度 3, | 6 |
② 读 n 的数据块,取出里面的路径字符串 | 链接内容存在数据块里,必须读一次 | 1 |
③ 重新解析 /data/pub/note.txt | 深度 3, | 6 |
| 合计 | 13 次 |
第 ② 段是软链接独有的一次读——硬链接的目录项里直接是 inode 号,不需要这一步。第 ③ 段是"重新解析",因为拿到的是一条路径,只能从头再走一遍完整流程。所以软链接比硬链接多
换一组前提怎么变:
- 目标是相对路径(如
../pub/note.txt):解析起点是链接文件所在的目录而不是根,路径更短则 I/O 更少 - 软链接指向另一个软链接:每嵌套一层就再加一次
,所以系统必须限制嵌套层数(否则可能成环、无限解析) - 有目录项缓存:③ 段中已缓存的前缀不必读盘,最好情况能把 6 降到接近 0
- 快速符号链接:② 段那 1 次读盘也省掉,变成 12 次
三、硬链接 vs 软链接对比
| 特性 | 硬链接 | 软链接 |
|---|---|---|
| 本质 | 多个目录项指向同一 inode | 独立文件,存储目标路径 |
| inode | 共享同一个 inode | 有自己独立的 inode |
| 链接计数 | 创建时 +1 | 不影响目标文件的链接计数 |
| 跨文件系统 | 不可以 | 可以 |
| 链接目录 | 不可以(防止环路) | 可以 |
| 删除目标文件 | 其他链接仍可访问 | 链接悬空,无法访问 |
| 访问速度 | 直接访问 inode,与原文件完全相同 | 多 |
| 占用空间 | 仅多一个目录项 | 一定占一个 inode;数据块只有路径太长时才占(快速符号链接不占) |
| 文件大小 | 无此概念 | 等于路径字符串的长度 |
一句话记忆
硬链接 = 给 inode 多取了一个名字;软链接 = 给文件建了一个快捷方式。
一组命令下 inode 与 links 计数怎么一步步变:建链接、建两种软链接、删原文件(想在具体计数上看清"原文件名并不特殊"时展开)
# 当前目录为 /home/li,file.txt 的 inode 号为 42,初始 links=1
# 创建硬链接
ln file.txt hard_link.txt
# 结果:hard_link.txt → inode 42, links=2
# 创建软链接(这里给的是相对路径,链接内容就原样存这个相对路径)
ln -s file.txt soft_link.txt
# 结果:soft_link.txt → inode 99(新inode), 内容 = "file.txt"
# 解析时以 soft_link.txt 所在目录 /home/li 为起点
# file.txt 的 links 仍为 2(软链接不改目标的链接计数)
# 若希望链接内容是绝对路径,要显式写出来
ln -s /home/li/file.txt abs_link.txt
# 结果:abs_link.txt 的内容 = "/home/li/file.txt",解析时从根开始
# 删除原文件
rm file.txt
# inode 42 的 links 变为 1(hard_link.txt 仍可访问)
# soft_link.txt 与 abs_link.txt 都变成悬空链接(访问会报错)ln -s file.txt soft_link.txt 存进去的是相对路径 file.txt,不是绝对路径。 解析时的起点是软链接文件自己所在的目录,不是当前工作目录,也不是根。由此推出两条:把 soft_link.txt 移到别的目录,它就找不到目标了(相对起点变了);而把整个目录连同链接和目标一起搬走,相对软链接仍然有效——绝对路径的软链接反而会失效。
最后一步也说明:对硬链接来说"原文件名"并不特殊,它和硬链接是平等的两个目录项,删掉一个只是链接计数减 1,数据仍在,另一个照常访问。
考点速记
- 一条判据分清两者:链接里存的是什么。 硬链接的目录项里存 inode 号,软链接文件的内容里存路径字符串。全部差别由它推出。
- 硬链接:多个目录项指向同一个 inode,创建时 inode 的链接计数
i_links+1(数的是有多少个目录项指向它,存在磁盘 inode 里)。 - ⚠️别与引用计数混用:
i_links数目录项、在磁盘 inode 里;引用计数数"有多少进程正打开着该文件"、在内存 inode 里。 - 删硬链接三步:删目录项 → 链接计数 −1 → 链接计数为 0 且无进程打开着它时才回收 inode 与数据块。两个条件缺一不可。
- 软链接是一个独立文件,内容是目标的路径名字符串;访问时先读内容取出路径,再重新解析一遍。⚠️ 因为它是独立文件,它有自己的 inode、自己的引用计数,与目标文件的计数毫无关系。
- 软链接的额外代价是
次磁盘 I/O(1 次读链接内容 + 一整轮对目标路径的重新解析, 目标路径深度)。⚠️"多一次间接寻址"低估了代价——多的是一次读 + 一整轮路径解析,长度取决于目标路径有多深而非链接本身有多深。 - 硬链接不产生任何额外开销:它与"原文件名"是完全平等的两个目录项,走哪条路径代价一样。
- 软链接一定占 inode,不一定占数据块:路径短到能塞进 inode 里本该放块指针的那片空间时叫快速符号链接。inode 必须占——它是独立文件。
- 跨不跨得了文件系统:硬链接跨不了(inode 号只在本分区内唯一),软链接跨得过去(路径字符串是全局的)。
- 相对 vs 绝对路径的软链接搬家后谁还能用:相对路径以软链接文件所在目录为起点(整个目录连同链接与目标一起搬走仍有效);绝对路径以根为起点(链接可随意移动,目标被搬走就失效)。两者恰好互补。
这一节在真题里被考过的形式:
三道题,全部落在"两种链接各自的计数怎么变、删除时各自动了什么"上。 只要抓住"软链接是一个独立文件"这一句,三道题都能推。
- 建软链接、建硬链接、再删原文件,问两个链接的引用计数(2009-31)。F1 引用计数初值 1 → 建软链接 F2(F2 是独立文件,自己的计数 = 1,不动 F1)→ 建硬链接 F3(F1 的计数变 2,F3 与 F1 共用同一个 inode)→ 删 F1(那个 inode 的计数减回 1)。故 F2 = 1、F3 = 1。⚠️ 最容易错的是以为建软链接会让 F1 的计数加 1——不会,软链接根本不碰目标的 inode。
- 判断硬链接的三条叙述(2017-31)。f1 与 f2 互为硬链接,两进程分别打开它们。答共享同一个内存索引结点 + fd1 与 fd2 分别指向各自用户打开文件表中的一项两条成立;"读写指针位置保持相同"不成立。⚠️ 判据在文件的操作:读写指针属于"一次打开",放在进程打开文件表里,各看各的;共享的是系统打开文件表那一层的 inode。同一个 inode 不等于同一个读写位置。
- 问删除文件时内核不必完成的工作(2021-30)。答删除 file1 的快捷方式。⚠️ 另三项都要做:释放 FCB、释放磁盘空间、删目录项。理由是快捷方式(软链接)是一个独立文件,内核根本不知道有谁指向了这个文件——这正是软链接会留下"断链"的原因,而硬链接因为有链接计数不会出现这种情况。
复习优先级:必须拿满,三道题共用两句话。 一是"软链接是独立文件、有自己的 inode 和计数、不碰目标", 二是"硬链接共享 inode,但读写指针属于每一次打开"。把这两句钉死, 三道题都是送分。第六条(
易错:认为建立软链接会让目标文件的链接计数加 1。不会——软链接是独立文件,根本不碰目标的 inode。
易错:认为互为硬链接的两个文件被打开后读写指针相同。指针属于每一次打开,各看各的;共享的只是 inode。
易错:认为删除文件时内核会一并清理指向它的快捷方式。内核不知道有谁指向它——软链接因此会留下断链。
易错:把链接计数和引用计数混用。前者数目录项、在磁盘上;后者数进程、在内存里。
易错:认为链接计数减到 0 就立刻回收。还要没有进程开着它,两个条件缺一不可。
易错:认为软链接的开销只是"多一次间接寻址"。是一次读 + 一整轮目标路径解析,共
次 I/O。
易错:认为硬链接能跨文件系统。不能——inode 号只在本分区内唯一;软链接可以。
教材出处
- 汤小丹《计算机操作系统》印刷版 p243(7.4.2 利用符号链实现文件共享):符号链方式下"在新文件 F 中只包含被链接文件 F8 的路径名";其代价是"系统是根据给定的文件路径名逐个分量(名)地去查找目录,直至找到该文件的索引结点。因此,在每次访问共享文件时,都可能要多次地读盘。这使每次访问文件的开销甚大,且增加了启动磁盘的频率",以及"链本身实际上是一个文件,尽管该文件非常简单,却仍要为它配置一个索引结点,这也要耗费一定的磁盘空间"——这是本篇量化
次 I/O 与"一定占一个 inode"的依据。 - 同书 p243 还指出符号链方式的一个优点:共享者"只有该文件的路径名,并不拥有指向其索引结点的指针",因此文件主删除共享文件后不会留下悬空指针,访问失败即可把符号链一并删除。
相关知识
目录|文件系统的全局结构|文件元数据与索引节点|文件系统挂载