译者注: 最近在看文件系统相关的,每当读到inode相关的东西时,书上或者博客上都会涉及硬链接/软链接相关的内容,于是今天专门针对硬链接翻译了几篇英文,弄懂它!
一、硬链接
在传统的UNIX文件系统中,一个目录就是一个包含关联列表的文件。目录文件中的条目是字符串形式的文件名及其对应的唯一文件标识符-- inode号。一个inode号本质上是一个磁盘上的指针,文件对象可以高效的通过它定位。没有两个磁盘对象共享一个inode号,也没有一个磁盘目标有两个inode号。
“硬链接”本质上是“目录项”的同义词。当一个目标第一次被创建,就会为它创建一个目录项。这其实就是硬链接,而大多数人常常把“硬链接”联想成“为一个已有的对象创建一个额外的目录项”。但是原来的目录项其实没有任何特殊,所有的链接都是平等的,所以一定意义上来说没有方法能识别出哪个是原来的。
目录也可以包含目录,当然,这是通过硬链接完成的。当一个子目录被创建时,在其父目录中也创建了一个目录项,这个目录项用于将子目录的名称与新创建inode关联起来。此外,新的目录文件中也自动创建了两个目录项,这两个目录项分别将".",".."和当前目录及其父目录关联起来。所以,创建一个子目录会创建一个新的硬件链接到其父目录,以及两个对新创建对象(子目录)的硬件链接:一个来自其父目录,另一个来自他自己("."),也就是说一个目录项的硬链接数最少是2。
long@zhouyl:~/test$ mkdir abc
long@zhouyl:~/test$ ls -l
total 1
drwxr-xr-x 2 long long 4096 Apr 17 09:02 abc
|
-- 硬链接数
目录硬链接比较特殊。首先,创建它们惟一的方法是创建目录;操作系统硬件链接函数不会允许一个硬链接的操作目标是一个目录inode。其中的原因是可能会在文件系统目录结构中产生循环。这也得根据内核,是否允许目录硬链接也需要遵从文件系统模块本身。在传统的UNIX文件系统中,循环很不好,有如下两个原因:第一,存储的回收是基于引用计数的,而它不处理循环引用。特殊的方向引用是"."和"..",但是它们是被当作特殊情况来处理的。其次,在树形结构中方向引用可以导致恶心的多线程问题。在传统的内核设计中(比如BSD内核),正在使用的inode表现为内存中的结构 vnodes。这些节点被同时访问,并且包含锁。一些操作会在访问一个目录的子目录时保留该目录的锁。这可能会导致死锁的发生。这些锁操作一般是不能被信号中断的,所以死锁的进程会一直保持死锁状态直至重启。在BSD中访问".."时有特殊的方法来避免这种死锁。基本上,在原来目录vnode上的锁刚释放,".."锁就被请求然后原来目录再次被锁。这就象一个竞赛一样。(这一段和下一段翻译得不好,但是和理解硬链接关系不大,其实上面已经解释的足够了!我只是想尽量完整而已)曾经我实现了一个对于vnode锁的周期检测算法,尽量支持一个BSD版本的文件系统的循环硬链接,但是问题是:尽管程序运行得很好,但是很难让内核的其他部分配合。内核中的很多地方,比如文件系统驱动以上的层都简单地假设锁会成功,或者最终会成功,所以并没有处理EDEADLK错误的方法。这并不很清楚,甚至如果你被允许使用那些提示你一个死锁可能会发生的信息,你又该如何处理呢?你会打断所有的系统调用?你会使用什么样的重试?应用进程又该如何响应可能有死锁的随机文件系统操作?
二、为什么硬链接不能指向目录
第一节中已经对硬链接和inode等概念有了很好的解释,但是为了保证原文的完整性,下面内容可能有重复解释!
2.1 从inode角度谈
允许目录的硬链接可能会打破文件系统的有向无环图结构,可能创建目录循环,这可能会导致fsck以及其他一些遍历文件树的软件出错。
首先,要想理解这点必须先了解inode。文件系统中的数据保存在磁盘上的数据块中,而这些数据块由inode集合在一起。可以说inode就是文件,但是inode缺少文件名,所以就需要链接。一个链接其实就是一个指向inode的指针。目录是一个保存着这些链接的inode,目录中的每一个文件名都是一个指向inode的链接。这里提一下,UNIX系统中打开一个文件也会创建一个链接,但是它是不同类型的链接(它不是一个命名链接)。
硬链接只是一个指向inode的额外的目录项,当你使用ls -l命令查看文件时,文件权限后面的数字就是命名连接数。绝大多数文件只有一个链接。创建一个新的硬链接到一个文件会将两个文件名指向同一个inode。
long@zhouyl:~/test$ touch test
long@zhouyl:~/test$ ls -l
total 0
-rw-r--r-- 1 long long 0 Apr 16 16:56 test
long@zhouyl:~/test$ ln test test1
long@zhouyl:~/test$ ls -l
total 0
-rw-r--r-- 2 long long 0 Apr 16 16:56 test
-rw-r--r-- 2 long long 0 Apr 16 16:56 test1
现在你可以清楚的看到,其实并没有什么硬链接,一个硬链接和正常的名字是一样的(这和第一节中介绍的硬链接一样,第一节中解释硬链接就是目录文件中的条目,记录的就是一个文件名与其对应inode),在上面的例子中,test 和 test1 哪个是原始文件,哪个是硬链接?其实你并不能分辨(忽略时间戳)因为它们都是指向相同内容相同inode的链接。
long@zhouyl:~/test$ ls -li
total 0
2114356 -rw-r--r-- 2 long long 0 Apr 16 16:56 test
2114356 -rw-r--r-- 2 long long 0 Apr 16 16:56 test1
使用ls -li (-i 标志让 ls 将文件的 inode 号显示在第一列)我们可以看到此时 test 和test1 有着相同的 inode 号。现在,如果你被允许在目录上使用硬链接,文件系统中的不同指针的不同目录项会指向相同的东西。实际上,一个子目录可以指向他的父目录从而创建一个循环。
为什么需要考虑这个循环?因为当你遍历目录树时,你没有办法检测到循环(如果您没有跟踪遍历的inode号)。比如说,你现在在使用du命令,du需要遍历所有的子目录来了解磁盘的使用情况。而du命令如何知道它遇到了个循环?这很容易发生错误。
软链接,亦称符号链接,是一个完全不同的东东,因为它们是一种特殊类型的文件(译者添加:UNIX文件系统中的文件种类包括:普通文件,目录文件,块特殊文件,字符特殊文件,FIFO,套接字以及符号链接。比如通过 “ ln -s a b ” 创建的软链接,创建软链接之后文件 b 和 a 的 inode 号并不一样,也就是说此时文件 a 和 b 并不是同一文件。 此时文件 b 中存的是文件 a 的路径,当读取 b 时,系统识别出文件 b 是符号链接会自动导向其对应的文件 a。)。注意,一个符号链接可以指向一个不存在的目标,因为他们指向的只是名字而不是直接指向inode。这与硬链接不一样,因为硬链接就表示肯定有文件存在。
那么为什么du可以很轻松的处理符号链接而不能处理硬链接?我们前面讨论过,如果对目录使用硬链接和正常的目录是没有区别的,而软链接是特殊的,可检测的且可跳过的。du注意到一个目录是一个符号链接它会完全跳过它。
long@zhouyl:~/test$ ln -s /home/long/Videos test2
long@zhouyl:~/test$ ls -l
total 0
-rw-r--r-- 2 long long 0 Apr 16 16:56 test
-rw-r--r-- 2 long long 0 Apr 16 16:56 test1
lrwxrwxrwx 1 long long 17 Apr 16 17:31 test2 -> /home/long/Videos
long@zhouyl:~/test$ du -ah
0 ./test
0 ./test2
4.0K .
2.2 从挂载点角度谈
从挂载点角度来说,任何目录有且只有一个父目录".."。
pwd的一个方法就是检查设备:"."和".."的inode,如果它们一样,说明你已处于"/"。否则,查找父目录名称并入栈,然后比较"../."和"../..",此后比较"../../.""../../.."...。直到抵达"/"后,开始出栈并打印栈中保存的目录项名称,最后得到当前目录的完整目录名。这个算法依赖于每个目录有且只有一个父目录。
如果对目录的硬链接是允许的,".."该指向多个父目录中的哪个?这是一个“为什么不允许对目录的硬链接”比较令人信服的理由。而目录的软链接不会引发这种问题,如果一个程序需要,它可以通过对路径名进行 lstat() 来检测是否遇到的是符号链接。pwd算法会返回目标目录的正确的路径。
三、总结
UNIX 文件系统的历史上,对目录的硬链接是可能的。但是这可能会在文件系统树中产生循环,而这会使得遍历文件系统变得混乱(在《Unix高级环境编程》中提到作者Steven在自己的系统上做过实验,结果是:创建目录硬链接后,文件系统变得错误百出)。一个目录甚至可以是自身的父目录,如下图显示,在目录foo中如果创建一个testdir 的硬链接指向 foo 本身,这样一个循环就出现了。嘿嘿,a lot of bad things will come !
现代文件系统一般禁止这些混淆状态,只有根目录保持了特例:根目录是自身的父目录。ls /.. 就是根目录的内容。当然,我们可以使用“mount -o bind /dir1 /dir2” 将dir1挂载到dir2上,从而达到与对目录硬链接一样的效果,只不过这个命令要求dir1和dir2都必须存在。
也有人说硬链接和软链接的本质区别是软链接是可以被系统侦测到的而硬链接却做不到。所以对目录创建软链接是安全的,而硬链接不是。了罢此文,可喜可贺~~
======================
注:此文翻译的比较长,如果有不正确之处请大家指点。此外,转载请注明出处