Pages

Thursday, 26 September 2019

详解「复制、拷贝、替身、软连接、硬连接」区别


在mac文件系统中可以对一个文件进行标题中的这5种操作,操作的结果都是生成一份副本,但是其中却有很大区别。
首先操作上的区别很明显
  1. 生成 软连接、硬连接 是通过命令行操作的
  2. 生成 替身、复制、拷贝 一般是通过finder进行操作的,右键某个文件,菜单中选择(其实复制也可以用命令cp来实现,跟finder操作是一个效果)
然后其中的 复制和拷贝 跟另外三种方式本质上不同,它们之间的区别也很好理解
  1. 他们的共同点是生成一份文件的副本,副本和原文件是两个独立的文件,两者的关系只有在生成时是关联的,在生成之后两者就没有关系,一个的被修改完全不会影响到另一个
  2. 区别是在你右键点击复制后就会立刻在当前目录下生成一份副本,而拷贝则是把此文件的副本放到剪贴板,等待被粘贴

软连接和硬连接

但 软连接、硬连接、替身 间的区别就需要进一步分析。
它们本质上都不会生成文件的副本,而是通过各种方式去指向原文件,让你可以跟访问原文件一样访问连接或是替身。
这里先分析软连接(或者说符号链接 symbolic link)和硬连接(hard link),它们都是在「POSIX」标准中就有的,所以在Linux和macOS上都会有,而且也是一样的。
他们的作用有:
  1. 让用户更方便的访问到文件、目录、驱动器或者网络设备等文件系统对象
  2. 当用户访问一个具有很深目录结构的文件时,不用再一级一级的打开目录,而是直接双击链接,就打开了相应的文件
  3. Linux 中常用它来解决一些库版本的问题
  4. 多数情况下,通过链接访问原始对象的过程对用户和应用程序是透明的,不可见的
从使用的角度讲,两者没有任何区别,都与正常的文件访问方式一样,支持读写,如果是可执行文件的话也可以直接执行。区别在于底层原理:软连接本身是一个文件,类似Windows 的快捷方式,可以让你通过连接文件到原文件;硬连接是通过文件系统的inode连接来产生新文件名,跟访问原文件一样进行访问,中间不会产生新文件,下面举个例子详细说明。
我们先以文件为例(目录稍有不同),在一个目录中创建一个文件和它的软硬连接
(注:因为涉及一些linux系统有关的讨论,本文中一律使用「目录」这个名称代替「文件夹」)
$ touch myfile && echo "my file first line" > myfile
$ ln myfile hard     # 创建一个硬连接文件hard
$ ln -s myfile soft  # 创建一个软连接文件soft
首先,我们会发现,通过软连接、硬连接和文件本身myfile对「文件内容」进行操作的效果都是一样的,实际上都是操作myfile
$ echo "new line by hard" >> hard
$ cat myfile
my file first line
new line by hard

$ echo "new line by soft" >> soft
$ cat myfile
my file first line
new line by hard
new line by soft
但如果我们我们对文件本身进程操作,对软连接和硬连接的影响就不太一样了
删除 myfile 文件,然后分别输出软硬链接的文件内容,你会发现软连接会找不到文件,但硬连接却能正常访问
$ rm myfile

$ cat hard
my file first line
new line by hard
new line by soft

$ cat soft
cat: soft: No such file or directory

# 先查看下现在目录的状态
$ ls -lhi
87446916 -rw-r--r--  1 bigbear  staff    63B 11 30 10:51 hard
87450113 lrwxr-xr-x  1 bigbear  staff     6B 11 30 10:44 soft -> myfile
来解释原因,首先回到删除文件之前,在目录下查看文件和连接的详细信息(目录文件的block内容)
$ ls -lhi
# inode号   文件属性 连接数  拥有者   所属用户组 大小  文件创建时间   文件名(路径)
87446916  -rw-r--r--  2   bigbear  staff    28B  11 30 10:42  hard
87446916  -rw-r--r--  2   bigbear  staff    28B  11 30 10:42  myfile
87450113  lrwxr-xr-x  1   bigbear  staff     6B  11 30 10:44  soft -> myfile
关于每列信息代表的意思都在注释里写了,几个特殊的稍微解释一下
  1. inode号
    1. (关于inode的内容可以参考我关于Linux文件系统的文章,写得很详细,忘了的话最好复习一遍)
    2. 每个文件的内容会分三处位置存储,文件的文件名存储在所在目录的目录文件的block中,这个block存储的是文件名和文件inode的映射关系,也就是文件名与目录有关;而文件属性和内容分别存储在文件的inode和block中,且文件的inode存有文件的block号
    3. 读取某个文件内容的过程是:在目录文件的block通过文件名找到对应的Inode号,找到这个inode然后读取其中的block号,找到这个block再读取这个block的内容
    4. 多个文件名可以对应到同一个inode号,当两个文件名的inode号相同,就代表在目录block的记录中,这两个文件名指向同一个inode,这两个文件名对文件系统来说就是同一个文件的不同名字
  2. 文件属性
    1. 一共十位,第一位是文件类型缩写,后面9位每三位一组表示用户权限、用户组权限、其他人权限的可读(r)、可写(w)、可执行(x)
  3. 连接数
    1. 表示有多少个文件名连接到这个inode号码,或者说这个inode指向的文件有多少个名字
  4. 所属用户组
    1. 有两大类常见的用户组wheel(轮子)和staff(人员)
    2. wheel 是一个特殊的可以使用 su 切换到 root 的用户组;而 staff 组是所有普通用户的集合
      于是我们能看到
  5. 硬连接和原文件的inode号相同,表示是同一个文件。两者除了文件名,其他的权限、属性、内容完全一样
  6. 软连接的inode和原文件不同,文件属性上也有一个 l 的 flag,表示连接文件
所以进一步分析:
软连接
  1. 创建软链接会创建一个新的文件,这个文件本身有新的inode,有新的block,block的内容是原文件的绝对路径或相对路径
    1. 你会发现上表中软连接文件的大小为6bytes,因为箭头(->)右边的文件名「myfile」总共有6个英文,每个英文占用1个bytes ,所以文件大小就是6bytes了
    2. 这里是相对路径,也可以使用绝对路径(就是在ln命令中把文件名改成绝对路径),使用相对路径的话,移动软连接文件的位置就可能导致软连接失效,所以一般使用绝对路径
  2. 系统读到这个文件类型时,会特殊处理,让数据的读取指向这个地址的那个文件
  3. 这个原理本质上同Windows的快捷方式文件是一样的
硬连接
  1. 创建硬连接只是在当前(或某个)目录文件的block中新增一条新文件名连接到某inode号码的关连记录而已
    1. 这时既不会增加inode 也不会增加block(其实还是可能会增加block的,那就是当你新增这笔记录却刚好将目录的block填满时,就可能会新加一个block来记录,不过一般不会)
  2. 因为硬连接的形式跟原文件的形式是一模一样的,所以系统其实是无法区分硬连接和原文件的,在命令下是一样的,在mac的图标上看也是一样的,能看到hard其实就是文件,soft是快捷方式的样子
这样就能解释前面修改文件内容和修改文件本身的表现不同的原因
  1. 修改内容
    1. 软连接:系统读到这个文件类型时,会特殊处理,让数据的读取指向这个地址的那个文件
    2. 硬连接:就是同一个文件,当然完全一样了
  2. 修改文件
    1. 查看目录信息能看到文件确实只有myfile被删掉了,看上去hard和soft都不受影响
    2. 但因为软连接的 inode 所指向的内容是一个文件路径,当这个文件被删除,从文件路径上自然会无法找到该文件
    3. 而硬连接确实没有受到影响,因为它直接指向inode,只要inode对应的block还在,就还能找到。而删除一个文件只是删除另一个硬连接,并不会影响inode和block
可以画图演示两种连接读取过程的区别
-w350
从上面的说明来看,软连接缺点很大,似乎硬连接更安全,因为即使某一个目录下的文件被删掉了也没有关系,只要有任何一个目录下存在文件,那么该文件就不会不见。但由于硬连接的限制太多, 所以在用途上是比较受限的,反而是软连接的使用面更广。
软连接缺点
  1. 删除了原文件,就会找不到文件了
  2. 修改了原文件的文件名(更确切地说是修改文件路径),也会找不到文件,因为软连接block记录的文件路径不会动态更新
硬连接的限制
  1. 不能跨文件系统
    1. 因为inode号只有在同一个文件系统中才是唯一的,当 Linux 挂载多个文件系统后将出现 inode 号重复的现象
  2. 不能连接目录(这也是前面说的对目录来说稍有不同的点,其他都一样)
    1. 因为每个目录文件的block中默认自带两条记录:.和..,.这条记录的inode就是目录文件自身的inode号,..这条记录的inode是父目录文件的inode号
    2. 同时因为硬连接是真的硬,跟系统本身的文件一模一样,系统是分不清的,所以如果是在不同的目录底下建立目录的hard link 时,将可能会导致「同一个目录会有好几个父目录」的存在,如图-w310
    3. 还有一些看不懂的解释
      1. 因为如果使用hard link 连接到目录时, 连接的新目录得要多出一个. 以及.. ,那个.. 就会导致父目录也多出一个新的连接计算,如果多重处理时, 很可能会导致目录搜寻时的错误循环问题,导致一个名为死结的困境——鸟哥
      2. 对一个目录创建硬连接时,要对目录下的所有文件创建硬连接——鸟哥
    4. 其他可参考解释
      1. https://unix.stackexchange.com/questions/22394/why-are-hard-links-to-directories-not-allowed-in-unix-linux
      2. https://www.zhihu.com/question/50223526
      3. http://blog.csdn.net/longerzone/article/details/23870297

mount --bind

另外,虽然上面说解决硬连接无法连接目录的方法有软连接,但其实还有一个办法,在Linux中还能使用mount --bind命令
有些程序里对软连接的支持并不好,这时就可以通过mount --bind命令来将两个目录连接起来
  1. 新建两个目录「mkdir test1 test2」,可以看到两个目录的inode号不同
  2. 将两个目录bind起来「mount --bind test1 test2」,会发现inode号全部变成之前test1的inode号了,所以mount --bind命令是将前一个目录挂载到后一个目录上,所有对后一个目录的访问其实都是对前一个目录的访问
但不同于硬连接是把两个文件名关联同一个inode记录,当mount --bind命令执行后:
  1. Linux将会把被挂载目录的目录项(也就是该目录文件的block,记录了下级目录的信息)屏蔽,在本例里就是 test2 的下级路径被隐藏起来了
    1. 注意,只是隐藏不是删除,数据都没有改变,只是访问不到了
  2. VFS会在内存创建一个vfsmount对象,这个对象里包含了整个文件系统所有的mount信息,其中就会添加本次mount中的信息,这个对象是一个HASH值对应表(HASH值通过对路径字符串的计算得来),表里就有 test1 到 test2 两个目录的HASH值对应关系
  3. 命令执行完后,当访问 test2下的文件时,系统会告知 test2 的目录项被屏蔽掉了,自动转到内存里找VFS,通过vfsmount了解到 test2 和 test1 的对应关系,从而读取到 /test1 的inode,这样在 /test2 下读到的全是 /test1 目录下的文件
由上述过程可知,mount --bind 和硬连接的主要区别是:
  1. mount --bind连接的两个目录的inode号码并不一样,只是被挂载目录的block被屏蔽掉,inode被重定向到挂载目录的inode(被挂载目录的inode和block依然没变)
  2. 两个目录的对应关系存在于内存里,一旦重启挂载关系就不存在了

替身

其实从上面的描述,你会发现,不管是软连接还是硬连接,都有各自的缺点:
  1. 软连接
    1. 删除了原文件或修改原文件的路径,就会找不到原文件了
  2. 硬连接
    1. 不能跨文件系统
    2. 不能连接目录
那能不能设计一种连接方式,避免这些缺点,比这两种连接方式更稳定?
重新设计有点困难,我们可以从优化开始:假设能把这两种方式结合在一起,是不是就完全没有缺点了?看上去可行,因为软连接的问题硬连接完全没有,硬连接的问题软连接也完全没有,只要两者结合就能互补,但你仔细想一下,软连接的问题硬连接也有,那就是虽然硬连接删除了原文件也能工作,但是需要在同一文件系统内才行,所以两种方式都有同样的问题:跨文件系统删除原文件,都会找不到原文件。
假如忽略这个问题,结合软连接和硬连接的最简单方式就是扩展软连接了,因为硬连接本身就是文件系统的元功能,修改的动静太大,所以扩展软连接比较靠谱。扩展的方式很简单:在软连接文件的block中添加一条信息,也就是inode号,先找路径,如果找不到,就看Inode在不在,或者查找顺序反过来。
而如果要进一步解决最后的问题,只能升级Inode了,让它能带有不同文件系统的前缀之类的,让inode号整个操作系统唯一。
我们的思考先到这里,来看苹果公司的思考吧,苹果不仅思考了这个问题,而且还给出了他们的解决方案:替身(Alias)。
其实说白了苹果的解决方案跟我们刚才想的一样,替身文件包含了原文件的路径和inode(好像不完全是这样,但是是类似的),当用户访问替身文件时,系统分析替身文件,找到原始文件的路径信息,然后判断原始文件是否存在,如果存在就访问它,如果不存在,就找具有相同inode的文件,然后访问该文件。
所以替身虽然具有软硬连接的优点,但还会面临上面提到的最后一个问题。
同时替身只是macOS自己的概念,并且是Finder层级的概念,所以终端下的程序(比如vim)是不能识别替身的。
比如我手动创建一个替身「myfile的替身」,能看到确实是新文件,inode号不一样,大小也大很多,同时会看到文件属性稍微有点不一样(是普通文件类型,但是最后多了一个属性@),然后使用cat查看时,不会自动定向到myfile上,而是直接看到了它的内容.

No comments:

Post a Comment