目录
Please enable Javascript to view the contents

Linux查漏补缺-1-链接

 ·  ☕ 6 分钟
核心结论细节
目录软链接删除区分带 / 与不带 /rm <软链接>/ 触发目标目录的删除逻辑,rm <软链接> 只删除软链接文件本身
硬链接无法跨分区 + 不能对目录创建跨分区不共享 inode table(机制决定);目录硬链接可能破坏树形结构(硬性规定),但 ... 本身就是目录的硬链接
硬链接共用同一个 inode删除硬链接只是 link count -1,直到 link count = 0 文件数据才被回收
软链接靠路径字符串查找目标,非 inode相对路径的软链接不能随意移动,绝对路径可以

1.简介

某次重装软件时,对软链接理解存在偏差导致误删目录。问题复现:

  1. 因为未移除干净软链接,导致安装失败;
  2. 尝试删除软链接 /root/bin -> /bin
  3. 执行 rm /root/bin/,报错 Is a directory
  4. 想当然执行 rm -rf /root/bin/,导致其 link 的目录 /bin 被删除。

目录软链接的正确删除方式应为 rm /root/bin,而非 rm -rf /root/bin/见 §2.2.5 详细说明

本文由这次踩坑引出对链接相关概念的梳理。

2.说明

2.0 前置概念:inode 与文件系统

inode(索引节点):注1

  • 每个文件占用一个 inode,inode 指向 block 获取文件内容;
  • 跨文件系统(磁盘分区)时 inode 不唯一;
  • inode 与文件名为一对多关系——多个文件名可以指向同一个 inode。

系统读取文件的步骤:注1

  1. 根据文件名找到对应的 inode 编号
  2. 通过 inode 编号获取 inode 信息
  3. 根据 inode 信息找到文件数据所在的 block,读出数据

链接:UNIX 文件系统提供的一种将不同文件名链接至同一个文件的机制,是 POSIX 概念,主流文件系统都支持。

查看 inode / 链接信息的命令(ls -li、stat)
1
2
3
$ ls -li
60822147 -rw-rw-r-- 1 hex hex 12 Oct 25 17:36 test
60822175 lrwxrwxrwx 1 hex hex  4 Oct 25 17:36 test-sl -> test

要点:

  • 第一列:inode 号
  • 第三列:硬链接数(多少个文件名指向这个 inode)
  • 第一字符:- 普通文件、l 软链接、d 目录
1
2
3
4
$ stat test-sl
  File: test-sl -> test
  Size: 4               Inode: 60822175    Links: 1
  Type: symbolic link

stat 直接显示文件类型(symbolic link)、inode、链接数。

2.1 软链接 vs 硬链接

功能项软链接硬链接
使用对象文件、目录仅文件
inode 是否相同不同相同
跨文件系统可以不可以
原文件删除后软链接不可用硬链接不受影响
关联方式记录原文件路径共用同一个 inode
创建命令ln -s <src> <file-sl>ln <src> <file-hl>

软链接:新建一个独立文件,内容存储的是原文件的路径。大小很小,权限显示 777,但实际权限由原文件决定。

  • 删除软链接 → 不影响原文件
  • 删除原文件 → 软链接仍存在,但指向失效(cat 提示 “No such file or directory”)

硬链接:为原文件创建一个别名,共享同一个 inode

  • 创建硬链接时 inode 的 link count +1
  • 删除硬链接时 link count -1
  • 系统调用检查 link count ≥ 1 则不回收 inode,文件内容不被删除

2.2 引申问题

2.2.1 软链接跳转靠的是文件名而非 inode

结论: 软链接记录的是原文件的路径,而非 inode。用相对路径创建的软链接,移动到其他目录后会失效——它找的是相对于自身新位置的路径,而非原位置。用绝对路径则不会。

验证实验(5步)

创建软链接时原文件参数用相对路径,再将软链接移动到其他目录,观察是否失效。

  1. 准备测试环境

    1
    2
    3
    
    mkdir /tmp/ln-test/ && cd /tmp/ln-test/
    echo "source file" > test
    ln -s test test-sl
  2. 查看状态

    1
    2
    3
    
    $ ls -li
    60822147 -rw-rw-r-- 1 hex hex 12 Oct 25 17:36 test
    60822175 lrwxrwxrwx 1 hex hex  4 Oct 25 17:36 test-sl -> test
  3. 移动软链接到上级目录

    1
    
    mv test-sl ../
  4. 进入上级目录,cat 软链接 → 报错

    1
    2
    3
    
    $ cd ../
    $ cat test-sl
    cat: test-sl: No such file or directory

    stat 显示 File: test-sl -> test——软链接仍在找当前目录下的 test,但那里并没有。

  5. 在新位置创建同名 test,cat 恢复

    1
    2
    3
    
    $ echo "Other test file" > test
    $ cat test-sl
    Other test file

软链接自身 inode 始终为 60822175,未改变;但前后找到的 test 的 inode 从 60822147 变成了 60822202——软链接依赖的是路径字符串,而非 inode

2.2.2 硬链接无法跨分区,软链接可以

硬盘格式化时,操作系统将硬盘分为两个区域:

  • 数据区:存放文件内容
  • inode 区:存放 inode 信息(inode table)

硬链接通过与原文件共用同一个 inode 访问 block 读取文件内容。跨分区无法共享 inode,所以硬链接无法跨分区。软链接记录的是原文件的路径,而非 inode,因此可以跨分区。

2.2.3 硬链接不支持目录的原因

系统限制对目录进行硬链接是硬性规定,并非逻辑上不允许。ln -d 命令允许 root 创建目录硬链接,且 ... 本身就是目录的硬链接。限制原因有二:

  1. Linux 目录是树状结构,对目录的硬链接可能破坏这种结构,甚至形成循环(如 /usr/bin -> /usr/),使用 ls -R 等遍历命令时会陷入无限循环。

  2. 如果对目录做硬链接,链接的数据需要连同被链接目录下所有数据都创建链接——未来在任一目录下创建新文件时,连带另一目录也要同步创建硬链接,复杂度极高。

举例:如果用硬链接把 /etc 映射为 /etc_hd,则 /etc_hd 下的所有文件名都要与 /etc 下的文件名一一创建硬链接。未来在 /etc_hd 下新增文件时,/etc 下也要同步创建硬链接。

2.2.4 ... — 目录的硬链接

创建目录时,默认生成两个目录项:...

  • . 相当于当前目录的硬链接
  • .. 相当于父目录的硬链接

硬链接计数规则:

  • 空目录的硬链接总数 = 2(目录名自身 + .
  • 有子目录时,硬链接总数 = 2 + 子目录数量(每个子目录中的 .. 都指向当前目录)

2.2.5 目录软链接删除:软链接/软链接

rm <软链接> 删除的是软链接文件本身;rm <软链接/> 删除的是原目录的内容

验证:

1
2
mkdir -p /tmp/ln-test/test-dir
ln -s test-dir/ test-dir-sl
1
2
3
$ ls -li
60822338 drwxrwxr-x 2 hex hex 4096 Oct 26 10:22 test-dir
60822339 lrwxrwxrwx 1 hex hex    9 Oct 26 10:23 test-dir-sl -> test-dir/

关键对比:

命令目标stat 显示inode类型
stat test-dir-sl软链接本身symbolic link60822339软链接
stat test-dir-sl/原目录directory60822338目录

删除验证:

1
2
3
4
5
6
$ rm test-dir-sl/
rm: cannot remove 'test-dir-sl/': Is a directory   ← 试图删原目录,被拒绝

$ rm test-dir-sl                                    ← 正确:只删除软链接
$ ls -li
60822338 drwxrwxr-x 2 hex hex 4096 Oct 26 10:22 test-dir   ← 原目录完好

2.2.6 应用场景

软链接:

  1. 版本切换python -> python3.5,升级时只需改软链接目标
  2. 动态库版本管理libfoo.so -> libfoo.so.1.3,升级库文件后更新软链接即可,旧版本保留便于回退
  3. 路径快捷方式:将深层目录链接到浅层位置

硬链接:

  1. 同一个文件从不同路径分类索引
  2. 多人共享同一文件而不占额外空间
  3. 文件备份——rsync 使用硬链接实现 --link-dest 增量备份

3.总结

  1. 目录软链接删除时注意区分带 / 与不带 /——rm <软链接>/ 触发的是目标目录的删除逻辑,rm <软链接> 只是删除软链接文件本身;
  2. 硬链接无法跨分区由实现机制决定(跨分区不共享 inode table),无法创建目录硬链接是硬性规定(防止树形目录结构被破坏),但 ... 本身就是目录的硬链接;
  3. 硬链接共用同一个 inode,增加硬链接相当于给文件建一个新别名,删除一个硬链接只是 link count -1,直到 link count = 0 时文件数据才真正被回收;
  4. 软链接通过原文件路径字符串查找目标,而非 inode——相对路径的软链接不能随意移动到其他目录,绝对路径的可以。

4.参考

[注1]: linux-inode说明

[注2]: “醉卧沙场:计算机专业性文章及回答总索引-存储和文件系统”

[注3]: “鸟哥私房菜-链接ln说明”

分享

Hex
作者
Hex
CloudNative Developer