代表性面试主题

Linux 面试:硬链接与符号链接有什么区别?

通用中等
OfferCC 编辑团队发布 更新

题干

一次 Linux 发布为同一个配置文件创建了硬链接和相对符号链接,随后在某个进程仍持有文件描述符时重命名并删除路径。请判断哪些路径与描述符仍然可用,解释硬链接为什么不能跨文件系统而符号链接可以,并说明如何验证每个结论。

题干与适用场景

某服务发布了 /srv/releases/v1/app.conf,把 /srv/live/pinned.conf 建成硬链接,把 /srv/live/current.conf 建成相对符号链接。一个 worker 先打开发布文件,运维人员随后重命名并 删除若干目录项。

请解释文件名、硬链接、符号链接、inode 和已打开文件描述符分别引用什么,并预测每一步结果。 然后为不可变快照、可移动发布指针和跨文件系统引用选择合适机制。本文限定 Linux 普通文件;文件 系统特有快照和 Windows 快捷方式不在范围内。

面试官考察什么

第一项信号是对象模型。目录项把一个名称绑定到 inode。硬链接是指向同一 inode 的另一个目录项, 不存在特殊的“原文件名”。符号链接是另一个文件系统对象,其内容是一段在使用时解析的路径。

第二项信号是生命周期推演。unlink 删除一个目录项,并不一定立即销毁文件对象。只要还有其他硬 链接或已打开文件描述,inode 和数据就继续存在。符号链接本身可以存在,但它保存的路径可能已经无法 解析。

第三项信号是能否预测操作,而非背诵对比表。重命名一个硬链接不影响其他同伴;重命名符号链接操作的 是链接自身。移动相对符号链接或目标可能改变路径含义。硬链接不能跨文件系统,因为 inode 身份只在 单个文件系统内有效。

最后看工程判断:检查对象身份与链接计数,在需要时做同文件系统原子替换,并避免在不可信路径上使用 “先检查、后打开”的符号链接竞态。

回答前需要澄清的问题

  • 源文件与新硬链接是否位于同一个挂载文件系统? 如果不是,link 会返回 EXDEV;复制或

符号链接解决的是另一种需求。

  • 调用方需要固定对象还是可移动名称? 硬链接固定一个 inode;发布别名通常应使用可替换目录项的

符号链接。

  • 目标是普通文件还是目录? Linux 通常禁止创建目录硬链接,符号链接可以指向文件或目录。
  • 符号链接保存绝对路径还是相对路径? 相对目标从符号链接所在目录开始解析,不是从进程当前工作

目录开始。

  • 是否有进程已经打开文件? 重命名或删除路径不会把既有描述符改指其他对象。
  • 切换对读者是否必须原子可见? rename 的原子替换限于同一挂载文件系统;跨文件系统需要不同

的发布协议。

  • 路径组件是否由不可信用户控制? 若是,先 lstat 再 open 存在竞态,必须在打开操作本身

强制执行遍历策略。

30 秒回答框架

“硬链接是指向同一 inode 的另一个目录项,因此多个名称共享数据和元数据。符号链接拥有自己的 inode, 保存一段访问时才解析的路径。删除一个硬链接名称只会减少链接计数;其他硬链接或已打开描述符仍可让 对象存活。删除或重命名目标则可能让符号链接悬空。

我会用 ls -li、stat、lstat 或 readlink 以及预先打开的描述符验证。需要在同一文件系统 固定精确对象时用硬链接;需要可替换发布别名或跨文件系统引用时用符号链接;原子切换别名时先创建临时 符号链接,再在同一文件系统内 rename。不可信路径不能分两步检查,打开时就要限制符号链接和目录 越界。”

分步骤深入解答

第一步:建立名称到对象的模型

对普通文件而言,目录保存名称与 inode 引用。inode 保存文件类型、所有者、权限、时间戳、大小、数据 块映射和硬链接计数。文件内容不属于某个特殊的“原文件名”。

硬链接为同一 inode 增加另一个目录项。通过任意名称修改字节或权限,其他名称都会看到变化。符号链接 则有自己的 inode,内容是一段如 ../releases/v1/app.conf 的字符串;普通路径查找会继续解析这段 字符串指向的名称。

第二步:运行具体实验

以下命令建立题设,Shell 文件描述符会故意保持打开:

bash
mkdir -p /srv/releases/v1 /srv/live
printf 'version=1\n' > /srv/releases/v1/app.conf
ln /srv/releases/v1/app.conf /srv/live/pinned.conf
ln -s ../releases/v1/app.conf /srv/live/current.conf
exec 3< /srv/releases/v1/app.conf

ls -li /srv/releases/v1/app.conf /srv/live/pinned.conf
readlink /srv/live/current.conf
stat -L -c '%F %i %h' /srv/live/current.conf
stat -c '%F %i %h' /srv/live/current.conf

mv /srv/releases/v1/app.conf /srv/releases/v1/app.conf.moved
cat /srv/live/pinned.conf
cat /srv/live/current.conf
cat <&3

rm /srv/releases/v1/app.conf.moved /srv/live/pinned.conf
cat <&3
exec 3<&-

重命名前,发布路径和 pinned.conf 显示相同 inode,硬链接计数为二。readlink 输出符号链接保存的 字符串。普通 stat 跟随符号链接查看目标,示例中的不跟随检查则查看符号链接对象自身。

第三步:预测重命名行为

同文件系统内的 mv 使用重命名操作处理源目录项。inode 没有移动,所以 pinned.conf 和描述符 3 继续可用。current.conf 仍保存 ../releases/v1/app.conf;旧名称消失后,解引用会失败。若重命名 符号链接本身,移动的是链接对象,不是目标;由于相对路径从链接新的所在目录解析,含义还可能改变。

若发布需要切换 current 且不能出现名称缺失窗口,应先在同一目录创建完整的临时符号链接,再用 rename 替换 current。替换后才打开的读者会解析旧或新目录项;已经打开旧目标的进程继续使用旧 描述符。

第四步:预测删除与空间回收

删除 app.conf.moved 只减少一个硬链接,pinned.conf 仍指向 inode,数据继续存在。再删除 pinned.conf 后,最后一个目录项消失,但描述符 3 仍持有打开引用,cat <&3 仍能读取。只有最后 一个硬链接和最后一个打开引用都消失,存储空间才可回收。

这也解释了为什么删除正在写入的大日志不一定释放空间,但不能因此随意截断未知描述符。应先确认所属 进程及其日志轮转契约,必要时按安全流程发信号或重启。

第五步:应用文件系统与目录边界

inode 编号只在所属文件系统内唯一。硬链接无法从另一个文件系统命名该 inode,link 会返回 EXDEV。符号链接保存路径,访问时可以跨挂载点解析,也可以指向目录或尚不存在的目标。

Linux 阻止普通用户创建目录硬链接,以避免文件树出现环和遍历歧义。目录符号链接允许存在,但移动相对 符号链接可能使其失效,不同递归工具也可能选择不同的跟随策略。备份、删除和发布工具需要明确这些策略。

第六步:按不变量选择

若不变量是“新增名称必须持续指向同一个 inode”,对象位于同一文件系统且确实需要共享元数据,选择硬 链接。若不变量是“这个别名解析到当前保存的路径”,尤其目标是目录、发布指针或跨文件系统名称,选择 符号链接。若需要独立字节、元数据、保留周期或文件系统位置,选择复制。

两种链接都不是备份。硬链接名称共享同一对象的修改与损坏;符号链接不保存目标数据。备份需要独立故障 边界、独立保留边界和恢复测试。

第七步:验证身份与安全路径解析

比较设备号与 inode,而不是只比较 inode,因为不同文件系统可能复用相同编号。用 stat 检查硬链接 计数,用不跟随链接的检查查看符号链接自身,用 readlink 查看其内容,并在描述符保持打开时实际执行 重命名与删除序列。

对不可信路径,lstat(path) 后再 open(path) 会给攻击者留下替换中间组件的窗口。Linux 上应以 可信目录描述符为起点打开,并通过 openat2 的解析限制禁止符号链接或越出目录。安全规则必须由最终 返回描述符的那次路径查找执行。

高质量示范回答

“我先从目录项解释。app.conf 与 pinned.conf 是同一 inode 的两个平等名称,所以共享内容、 权限、所有者和时间戳。current.conf 是独立的符号链接 inode,保存 ../releases/v1/app.conf,相对路径从 /srv/live 开始解析。

发布路径重命名后,硬链接与已经打开的描述符仍指向原文件对象。符号链接保存的仍是旧路径,所以变成 悬空链接。删除移动后的名称时,硬链接继续让对象存活;再删除硬链接后,打开描述符依然可用,内核要等 它关闭后才能回收对象。

我会用硬链接固定同文件系统内的精确制品,用符号链接作为可移动的 current 别名,需要独立保留时 创建真正副本。更新 current 时,先创建临时链接,再在同一目录原子重命名。我会用设备号与 inode、 链接计数、readlink、悬空访问和打开描述符测试证明结果。不可信路径则在打开操作内强制禁止符号链接 与目录越界。”

常见错误

  • 把硬链接称为指向原文件名的指针 → 所有硬链接都是同一 inode 的平等目录项 → **说明名称、

inode 身份与链接计数。**

  • 声称删除一定立即销毁文件 → 其他名称或打开引用可能仍然存在 → **同时追踪链接计数与文件

描述符。**

  • 把符号链接当作保存 inode 引用 → 它保存的路径以后可能解析到别处或失败 → **检查链接内容与

解析起点。**

  • 只比较 inode 编号 → inode 编号仅在文件系统内唯一 → 比较设备号加 inode。
  • 用硬链接跨挂载点或指向目录 → Linux 会拒绝这些场景 → 按独立性需求选择符号链接或复制。
  • 移动相对符号链接却不重新核对 → 链接所在目录决定解析起点 → 移动后重新计算或生成目标。
  • 把任一种链接当作备份 → 一个共享对象,另一个只保存路径 → 创建可独立恢复的副本。
  • 先检查路径、稍后再打开 → 攻击者可以在两步之间替换符号链接 → 打开时原子执行遍历限制。

追问及应对

追问一:不同硬链接有各自的权限和所有者吗?

没有。权限、所有者、大小和时间戳属于共享 inode,通过一个名称修改后,所有同伴都会看到。目录项名称 和父目录权限是另一层;重命名或删除名称由所在目录的权限决定。

追问二:相对符号链接移动后为什么可能失效?

其内容从符号链接所在目录开始解析。移动链接会改变这个起点,却不改变保存的字符串。绝对符号链接虽不 受该移动影响,但在容器、chroot、不同挂载布局或其他主机中可能指错。应根据迁移需求选择。

追问三:rename 能否跨文件系统原子替换 current?

不能。Linux rename 跨挂载文件系统会返回 EXDEV。应先在目标文件系统内发布完整对象,再在目标 目录创建临时别名,并在同一目录内重命名覆盖 current;之前的复制阶段需要单独定义清理与恢复。

追问四:最后一个路径已删除,磁盘空间为什么仍被占用?

某个打开文件描述可能仍引用 inode。先定位描述符及所属进程,再使用应用安全的轮转或重启流程。只有 最后一个名称和最后一个打开引用都消失,空间才会回收;删除路径只证明该名称不存在。

追问五:stat、lstat 与 readlink 有何不同?

stat 通常跟随最终符号链接并报告目标;lstat 报告链接对象;readlink 返回链接保存的路径, 但不解析它。验证时要同时证明别名自身和它当前指向的对象。

追问六:上传目录如何防止符号链接竞态?

以可信目录描述符为起点打开,让路径查找本身强制停留在该目录下并禁止不允许的符号链接,再用基于描述 符的操作检查结果。先做路径检查、再单独打开会留下被替换窗口。

公开来源

同类题目