Intro

硬链接和符号链接是引用硬盘驱动器中文件的两种不同方法。这些方法是文件系统的一部分,用于组织文件是什么以及在哪里。硬链接本质上是文件的同步副本,它直接引用文件的 inode。另一方面,符号链接直接指向引用索引节点的文件,这是一个快捷方式。

什么是索引节点?

  • inode 是描述文件/目录属性的控制结构—— 索引节点
  • 使用 inode,操作系统可以检索有关文件的信息,
    • 例如权限和数据在硬盘驱动器上的物理位置,以访问文件。
    • 如果文件从一个文件夹移动到另一个文件夹,该文件将被移动到硬盘驱动器上的不同位置,其 inode 值将随之自动更改。

什么是硬链接?

  • 硬链接是通过其 inode 直接引用文件。
  • 通过使用硬链接,可以更改原始文件的内容或位置,并且硬链接仍将指向原始文件,因为其 inode 仍指向该文件。
  • 此外,硬链接只能引用同一卷内的文件,否则将需要符号链接。

为什么只允许到目录的符号链接,而不允许硬链接?

  • 硬链接到目录,可能会导致目录中出现循环:

  • 上图会产生:/avi/book/avi/book/avi/book/…
  • 如何保证没有循环?
    • 只允许到文件的硬链接,而不允许到目录的硬链接;
    • 增加链接时,用循环检测算法确定是否合理;

什么是符号链接?

  • 符号链接本质上是引用文件而不是其 inode 的快捷方式
  • 这种方法可以应用于目录,并且可以跨不同的硬盘/卷进行引用。
  • 由于符号链接是指原始文件而不是其 inode 值,因此将原始文件替换到不同的文件夹将破坏符号链接,或创建悬空链接。

如何利用符号链接实现共享

为使链接父目录 D 能共享文件 F (位置任意,只要不在 D 中即可),可以由系统创建一个 SYMBOL_LINK 类型的新文件,取名为 S_F 并写入链接父目录 D 中,此时 D 就与文件 F 完成了链接。在新文件 S_F 中只包含被链接文件 F 的路径名。

新文件 S_F 中的路径名被看作是符号链,当用户通过目录 D 访问文件 F 时,就会访问 S_F 这个符号链接文件,这个要求被 OS 截获,OS 根据新文件中的路径名去找到文件 F,然后进行读写。

利用符号链接实现共享的优点

利用符号链接方式实现文件共享时,只有文件所有者才拥有指向其 inode 的指针,共享该文件的其它用户只有该文件的路径名。因此,

  • 这样就不会发生在文件所有者删除共享文件后留下悬空指针的情况,
  • 此时符号链接找不到对应文件,删除后也不会有任何影响。

利用符号链接的共享方式存在的问题

当其他用户读取共享文件时,系统根据给定的文件路径名逐个分量地查找目录,直至找到该文件的索引节点。因此

  • 每次访问共享文件时,可能要多次读磁盘,这导致读开销过大、增加了启动磁盘的频率。

无论硬链接还是符号链接,都会导致一个共享文件存在多个文件名,这会在遍历文件系统时多次遍历到同一共享文件。

如何在 Linux 上创建硬链接、软链接?

Transclude of Linux-Shell-学习笔记#46-创建链接

# 创建初始文件ln_test,并建立第一个硬链接hln_test,查看各自inode
 touch ln_test  
 ln ln_test hln_test  
 ls -i  
3166088 hln_test  3166088 ln_test
 
# 通过硬链接对原文件进行操作,再创建第二个硬链接hln_test,查看inode的引用计数
 echo "hello,world" >> hln_test  
 cat hln_test  
hello,world  
 cat ln_test  
hello,world  
 ln ln_test hln_test2  
 ls -l ln_test hln_test hln_test2  
-rw-r--r-- 3 senjl senjl 29  9月10日 17:48 hln_test  
-rw-r--r-- 3 senjl senjl 29  9月10日 17:48 hln_test2  
-rw-r--r-- 3 senjl senjl 29  9月10日 17:48 ln_test
 
# 创建第一个符号链接sln_test,并查看其inode的引用计数
 ln -s ln_test sln_test
 ls -la
总计 16
drwxr-xr-x 1 senjl senjl  64  9月10日 17:45 .
drwxr-xr-x 1 senjl senjl 196  9月10日 17:36 ..
-rw-r--r-- 3 senjl senjl  12  9月10日 17:42 hln_test
-rw-r--r-- 3 senjl senjl  12  9月10日 17:42 hln_test2
-rw-r--r-- 3 senjl senjl  12  9月10日 17:42 ln_test
lrwxrwxrwx 1 senjl senjl   7  9月10日 17:45 sln_test -> ln_test
 
# 再创建第二个符号链接sln_test2,查看其inode的引用计数
 ln -s ln_test sln_test2  
 ls -la  
总计 20  
drwxr-xr-x 1 senjl senjl  82  9月10日 17:58 .  
drwxr-xr-x 1 senjl senjl 196  9月10日 17:36 ..  
-rw-r--r-- 3 senjl senjl  29  9月10日 17:48 hln_test  
-rw-r--r-- 3 senjl senjl  29  9月10日 17:48 hln_test2  
-rw-r--r-- 3 senjl senjl  29  9月10日 17:48 ln_test  
lrwxrwxrwx 1 senjl senjl   7  9月10日 17:45 sln_test -> ln_test  
lrwxrwxrwx 1 senjl senjl   7  9月10日 17:58 sln_test2 -> ln_test
 
# 通过符号链接操作原文件
 echo "symbol link test" >> sln_test  
 cat sln_test  
hello, world  
symbol link test  
 cat ln_test  
hello, world  
symbol link test
 
# 如果删除原文件,各链接是否正常工作?
 rm ln_test  
 cat hln_test  
hello, world  
symbol link test  
 cat sln_test  
cat: sln_test: 没有那个文件或目录  
 ls -la  
总计 16  
drwxr-xr-x 1 senjl senjl  68  9 10 18:01 .  
drwxr-xr-x 1 senjl senjl 196  9 10 17:36 ..  
-rw-r--r-- 2 senjl senjl  29  9 10 17:48 hln_test  
-rw-r--r-- 2 senjl senjl  29  9 10 17:48 hln_test2  
lrwxrwxrwx 1 senjl senjl   7  9 10 17:45 sln_test -> ln_test  
lrwxrwxrwx 1 senjl senjl   7  9 10 17:58 sln_test2 -> ln_test
# 这表明硬链接完全代替了原文件的作用,硬链接是原文件的一个别名
# 原文件名称本身其实就是一个人可读的硬链接,链接到与该文件对应的 inode 而已
 
# 重新建立一个 ln_test,会发生什么?
 echo "new ln_test" > ln_test  
 cat ln_test  
new ln_test  
 cat hln_test  
hello, world  
symbol link test  
 cat sln_test  
new ln_test   
 ls -lai  
总计 20  
3166068 drwxr-xr-x 1 senjl senjl  82  9 10 18:07 .  
357713 drwxr-xr-x 1 senjl senjl 196  9 10 17:36 ..  
3166088 -rw-r--r-- 2 senjl senjl  29  9 10 17:48 hln_test  
3166088 -rw-r--r-- 2 senjl senjl  29  9 10 17:48 hln_test2  
3168150 -rw-r--r-- 1 senjl senjl  12  9 10 18:06 ln_test  
3166651 lrwxrwxrwx 1 senjl senjl   7  9 10 17:45 sln_test -> ln_test  
3167682 lrwxrwxrwx 1 senjl senjl   7  9 10 17:58 sln_test2 -> ln_test
 
# 使用 unlink 删除链接
 ln ln_test hln_new  
 ln -s ln_test sln_test3  
 ls -lai  
总计 28  
3166068 drwxr-xr-x 1 senjl senjl 114  9 10 18:11 .  
357713 drwxr-xr-x 1 senjl senjl 196  9 10 17:36 ..  
3168150 -rw-r--r-- 2 senjl senjl  12  9 10 18:06 hln_new  
3166088 -rw-r--r-- 2 senjl senjl  29  9 10 17:48 hln_test  
3166088 -rw-r--r-- 2 senjl senjl  29  9 10 17:48 hln_test2  
3168150 -rw-r--r-- 2 senjl senjl  12  9 10 18:06 ln_test  
3166651 lrwxrwxrwx 1 senjl senjl   7  9 10 17:45 sln_test -> ln_test  
3168516 lrwxrwxrwx 1 senjl senjl   7  9 10 18:11 sln_test2 -> ln_test  
3168532 lrwxrwxrwx 1 senjl senjl   7  9 10 18:11 sln_test3 -> ln_test  
 unlink sln_test2  
 unlink hln_new  
 ls -lai  
总计 20  
3166068 drwxr-xr-x 1 senjl senjl  82  9 10 18:12 .  
357713 drwxr-xr-x 1 senjl senjl 196  9 10 17:36 ..  
3166088 -rw-r--r-- 2 senjl senjl  29  9 10 17:48 hln_test  
3166088 -rw-r--r-- 2 senjl senjl  29  9 10 17:48 hln_test2  
3168150 -rw-r--r-- 1 senjl senjl  12  9 10 18:06 ln_test  
3166651 lrwxrwxrwx 1 senjl senjl   7  9 10 17:45 sln_test -> ln_test  
3168532 lrwxrwxrwx 1 senjl senjl   7  9 10 18:11 sln_test3 -> ln_test
 
# 现在知道,硬链接和原文件名其实都指向文件真正的元数据 inode
# 事实上,引用计数作为存储文件系统跟踪到的链接到 inode 的文件名的数量,在引用计数减少到 0 之前,文件并没有被真正删除
# 除此之外,使用 stat ()也可以查看引用计数
 stat ln_test  
 文件:ln_test  
 大小:12              块:8          IO 块大小:4096   普通文件  
设备:0,38      Inode: 3168150     硬链接:1  
权限:(0644/-rw-r--r--)  Uid: ( 1000/   senjl)   Gid: ( 1000/   senjl)  
访问时间:2023-09-10 18:11:20.356811450 +0800  
修改时间:2023-09-10 18:06:16.283571403 +0800  
变更时间:2023-09-10 18:12:29.243277006 +0800  
创建时间:2023-09-10 18:05:36.362745210 +0800  
 stat hln_test  
 文件:hln_test  
 大小:29              块:8          IO 块大小:4096   普通文件  
设备:0,38      Inode: 3166088     硬链接:2  
权限:(0644/-rw-r--r--)  Uid: ( 1000/   senjl)   Gid: ( 1000/   senjl)  
访问时间:2023-09-10 18:01:38.929989741 +0800  
修改时间:2023-09-10 17:48:49.757407959 +0800  
变更时间:2023-09-10 18:01:28.659510510 +0800  
创建时间:2023-09-10 17:36:53.997187110 +0800  
 stat sln_test  
 文件:sln_test -> ln_test  
 大小:7               块:8          IO 块大小:4096   符号链接  
设备:0,38      Inode: 3166651     硬链接:1  
权限:(0777/lrwxrwxrwx)  Uid: ( 1000/   senjl)   Gid: ( 1000/   senjl)  
访问时间:2023-09-10 17:45:31.014779041 +0800  
修改时间:2023-09-10 17:45:20.910972832 +0800  
变更时间:2023-09-10 17:45:20.910972832 +0800  
创建时间:2023-09-10 17:45:20.910972832 +0800