Linux文件系统

对Linux中文件系统的相关知识进行整理:

1 整体概况

文件系统是应用程序与块设备(磁盘等)之间的桥梁,是对文件进行统一管理的中间层。对上向上层用户提供读写文件的操作接口,对下将文件在磁盘上进行存储及有效的管理。对上图由下往上:

1)最底层为块设备,即存储硬盘,如PATA, SATA和AHCI等;

2)不同硬盘对应不同的驱动模块,在Linux系统中,对不同硬盘所提供的驱动模块一般都存放在内核目录树drivers/ata中,对于一般通用的硬盘驱动,也许会直接被编译到内核中;

3)通用块设备层,不同的硬盘驱动会提供不同的IO接口,为方便管理,内核把这些接口抽象形成一个统一的对外接口,这样任意硬盘驱动对外所提供的IO接口都一视同仁的被看作块设备来处理;

4)文件系统层,对应到具体格式化硬盘的真实文件系统,每个文件系统实现导出一组通用接口供 VFS 使用。缓冲区缓存会缓存文件系统和相关块设备之间的请求。例如对底层设备驱动程序的读写请求会通过缓冲区缓存来传递。这就允许在其中缓存请求,减少访问物理设备的次数,加快访问速度;

5)虚拟文件系统层,同样地,不同的文件系统对应的结构和操作函数存在差异,VFS就把这些不同的文件系统做一个抽象,提供统一的API访问接口,这样用户空间就不用关心不同文件系统中不一样的API了。Vfs中还有两个针对文件系统对象的缓存(inode 和 dentry)。它们缓存最近使用过的文件系统对象,减少从硬盘读取的次数;

6)系统调用层,提供统一的API访问接口给用户。

 

2 硬盘存储(了解)

现代计算机大部分文件存储功能都是由机械硬盘这种设备提供的。(现在的SSD和闪存从概念和逻辑上都部分继承自机械硬盘,所以使用机械硬盘来进行理解也是没有问题的)机械硬盘能实现信息存储的功能基于:磁性存储介质能够被磁化,且磁化后会长久保留被磁化的状态,这种被磁化状态能够被读取出来,同时这种磁化状态还能够不断被修改,磁化正好有两个方向,所以可以表示0和1。于是硬盘就是把这种磁性存储介质做成一个个盘片,每一个盘片上都分布着数量巨大的磁性存储单位,使用磁性读写头对盘片进行写入和读取。

  • 一个硬盘中的磁性存储单位数以亿计(1T硬盘就有约80亿个),所以需要一套规则来规划信息如何存取,于是就有了这些物理、逻辑概念:
  •  一个硬盘有多张盘片叠成,不同盘片有编号;
  •  每张盘片上的存储颗粒成环形一圈圈地排布,每一圈称为磁道,有编号;
  •  每条磁道上都有一圈存储颗粒,每512*8(512字节,0.5KB)个存储颗粒作为一个扇区,扇区是硬盘上存储的最小物理单位
  •  N个扇区可以组成簇,N取决于不同的文件系统或是文件系统的配置,簇是此文件系统中的最小存储单位,相当于文件系统中的块;
  •  所有盘面上的同一磁道构成一个圆柱,称为柱面,柱面是系统分区的最小单位。 

磁头读写文件的时候,首先是分区读写的,由inode编号找到对应的磁道和扇区,然后一个柱面一个柱面地进行读写。机械硬盘的读写控制系统是一个令人叹为观止的精密工程(一个盘面上有几亿个存储单位,每个磁道宽度不到几十纳米,磁盘每分钟上万转),同时关于读写的逻辑也是有诸多细节(比如扇区的编号并不是连续的)等等。

有了硬盘并不意味着LInux可以立刻把它用来存储,还需要组合进Linux的文件体系才能被Linux使用。

 

3 真实文件系统,以ext2为例

1、硬盘分区

硬盘分区是硬盘结合到文件体系的第一步,本质是「硬盘」这个物理概念转换成「区」这个逻辑概念,为下一步格式化做准备。分区本身并不是必须的,可以把一整块硬盘作为一个区。但从数据的安全性以及系统性能角度来看,分区还是有很多用处的,所以一般都会对硬盘进行分区。

每块硬盘上最重要的第一扇区中有硬盘主引导记录(Master boot record, MBR) 及分区表(partition table), 其中 MBR 占有 446 bytes,而分区表占有 64 bytes。硬盘主引导记录放有最基本的引导加载程序,是系统开机启动的关键环节。而分区表则跟分区有关,它记录了硬盘分区的相关信息,但因分区表仅有 64bytes , 所以最多只能记彔四块分区(分区本身其实就是对分区表进行设置)。

只能分四个区实在太少了,于是就有了扩展分区的概念,既然第一个扇区所在的分区表只能记录四条数据, 那可利用额外的扇区来记录更多的分区信息。

把普通可以访问的分区称为主分区,扩展分区不同于主分区,它本身并没有内容,它是为进一步逻辑分区提供空间的。在某块分区指定为扩展分区后,就可以对这块扩展分区进一步分成多个逻辑分区。操作系统规定:

     四块分区每块都可以是主分区或扩展分区

     扩展分区最多只能有一个(也没必要有多个)

     扩展分区可以进一步分割为多个逻辑分区

     扩展分区只是逻辑概念,本身不能被访问,也就是不能被格式化后作为数据访问的分区,能够作为数据访问的分区只有主分区和逻辑分区

逻辑分区的数量依操作系统而不同,一般给硬盘进行分区时,一个主分区一个扩展分区,然后把扩展分区划分为N个逻辑分区是最好的。特殊的,最好单独分一个swap区(内存置换空间),它独为一类,功能是:当有数据被存放在物理内存里面,但是这些数据又不是常被 CPU 所取用时,那么这些不常被使用的程序将会被丢到硬盘的 swap 置换空间当中, 而将速度较快的物理内存空间释放出来给真正需要的程序使用。

2、文件系统格式化

Linux操作系统支持很多不同的文件系统,比如ext2、ext3、XFS、FAT等等,而Linux把对不同文件系统的访问交给了VFS(虚拟文件系统),VFS能访问和管理各种不同的文件系统。所以有了区之后就需要把它格式化成具体的文件系统以便VFS访问。

标准的Linux文件系统Ext2是使用「基于inode的文件系统」。一般操作系统的文件数据除了文件实际内容外, 还带有很多属性,例如 Linux 操作系统的文件权限(rwx)与文件属性(拥有者、群组、 时间参数等),文件系统通常会将属性和实际内容这两部分数据分别存放在不同的区块。在基于inode的文件系统中,权限与属性放置到 inode 中,实际数据放到 data block 区块中,而且inode和data block都有编号。

Ext2 文件系统在此基础上:

1)文件系统最前面有一个启动扇区(boot sector)。这个启动扇区可以安装开机管理程序, 这个设计让我们能将不同的引导装载程序安装到个别的文件系统前端,而不用覆盖整个硬盘唯一的MBR, 也就是这样才能实现多重引导的功能;

2)把每个区进一步分为多个块组 (block group),每个块组有独立的inode/block体系。如果文件系统高达数百 GB 时,把所有的 inode 和block 通通放在一起会因为 inode 和 block的数量太庞大,不容易管理。每个块组实际还会分为分为6个部分,除了inode table 和 data block外还有4个附属模块,起到优化和完善系统性能的作用。

故ext2文件系统的硬盘布局大致如下:

如上图,引导启动块(可以没有,有的话一包在起始处)之后存储了文件系统的元数据和各个文件有用的数据。超级块是用于存储文件系统自身元数据的核心结构,可以看到每个块组中都有一个超级块,信息是冗余的,但是这么做的原因是:

1)  如果系统崩溃破坏了超级块,有关文件系统结构和内容的所有信息都会丢失。如果有冗余的副本,该信息是可能恢复的虽然难度极高;

2)  通过使文件和管理数据尽可能接近,减少了磁头寻道和旋转,可以提高文件系统的性能。

但是实际上,数据并非在每个块组中都复制,内核一般只用超级块的第一个副本工作。在进行文件系统检查时,会将第一个超级块的数据传播到剩余的超级块,供紧急情况下读取。但是这种方式会消耗大量的存储空间,因此现在一般采用稀疏超级块技术,即超级块不再存储到文件系统的每个块组中,而是只写入到块组0、1和其他可表示为3、5、7的幂的块组中。且超级块的数据缓存在内存中,使得内核不必重复从硬盘中读取数据。

块组中各个结构的作用如下:

1)超级块:用于存储文件系统自身元数据的核心结构,一般大小为1024bytes,记录的信息主要有:block 与inode 的总量;未使用与已使用的inode / block 数量;一个valid bit 数值,若此文件系统已被挂载,则valid bit 为0 ,若未被挂载,则valid bit 为1;block 与inode 的大小 (block 为1, 2, 4K,inode 为128bytes 或256bytes);其他各种文件系统相关信息:filesystem 的挂载时间、最近一次写入资料的时间、最近一次检验磁碟(fsck) 的时间,及文件系统类型磨数用于mount;

内核中其对应的数据结构如下:

如上其中s_blocks_count 记录了硬盘分区上 block 的总数,而 s_blocks_per_group 记录了每个 group 中有多少个 block,文件系统上的 block groups 数量假设为G,则G = (s_blocks_count - s_first_data_block - 1) / s_blocks_per_group + 1。减去 s_first_data_block是因为 s_blocks_count 是硬盘分区上全部的 block 的数量,而在 s_first_data_block 之前的 block 是不归 block group 管的,所以要减去。最后加一是因为尾巴上可能多出来一些 block,这些 block要把它划在一个相对较小的 group 里面。

2)组描述符表:包含的信息反映了文件系统中各个块组的状态,如块组中空闲块和inode的数目,每个块组都包含了文件系统中所有块组的组描述符信息,数据结构如下: 

指针bg_block_bitmap指向这个 block group 的块位图block bitmap,bg_inode_bitmap指向 inode bitmap, bg_inode_table则指向inode_table。

3)数据块位图和inode位图(均只有一个块):用于保存长的比特位串,这些结构中的每个比特位都对应于一个数据块或inode,如果位为0表示有数据,为1则表示空闲;

4)inode表:包含块组中所有的inode,inode用于保存文件系统中与各个文件和目录相关的所有元数据;主要记录文件的属性以及该文件实际数据是放置在哪些block中,它记录的信息至少有:文件大小、文件真正内容的block号码(一个或多个);访问模式(read/write/excute);拥有者与群组(owner/group);各种时间:建立或状态改变的时间、最近一次的读取时间、最近修改的时间;注意没有文件名!文件名在目录的block中!目录实际上是一个目录文件,其block中存放的是目录下的文件名/子目录名及其对应的inode号。

一个文件占用一个 inode,每个inode有编号,Linux 系统存在 inode 号被用完但磁盘空间还有剩余的情况。注意这里的文件不单单是普通文件,目录文件也是一个文件,inode 的数量与大小在格式化时就已经固定了,每个inode 大小均固定为128 bytes (新的ext4 与xfs 可设定到256 bytes);文件系统能够建立的文件数量与inode 的数量有关,存在空间还够但inode不够的情况;系统读取文件时需要先找到inode,并分析inode 所记录的权限与使用者是否符合,若符合才能够开始实际读取 block 的内容,inode结构如下:

 

   通过inode号读取文件数据的过程:一个硬盘分区上的block 计数是从 0 开始的,并且这个计数对于这个硬盘分区来说是全局性质的,inode 计数同 block 计数一样,也是全局性质的,inode 计数是从 1 开始。在 super block 中有一个字段 s_inodes_per_group 记载了每个 block group 中有多少个 inode。用得到的 inode 号数除以 s_inodes_per_group可知这个 inode 是在哪一个 block group 里面,余数即为这个 inode 是这个 block group 里面的第几个 inode;然后先找到这个 block group 的 group descriptor,从这个 descriptor找到这个 group 的 inode table,再从 inode table 找到所需的第几个 inode,之后就可以开始读取 inode 中的用户数据了。即:block_group = (ino - 1) / s_inodes_per_group。这里 ino 就是得到的 inode 号数。而 offset = (ino - 1) % s_inodes_per_group,这个 offset 就指出了该inode 是这个 block group 里面的第几个 inode。减1因为块组从0开始。

由于inode大小有限,记录一个block 号码需要4byte ,假设一个文件有400MB 且每个block 为4K 时, 那么至少也要十万条block 号码的记录,那么需要花费很大的空间来存储,因此系统将inode 记录block 号码的区域定义为12个直接,一个间接, 一个双间接与一个三间接记录区:

由inode可知其可存放 EXT2_N_BLOCKS个 block 指针:

这组 15 个 block 指针的前 12 个是direct blocks,里面直接存放的就是用户数据。第 13 个 block为indirect block,里面存放的全部是 block 指针,这些 block 指针指向的 block 才被用来存放用户数据。第 14 个 block 是所谓的 double indirect block,里面存放的全是 block 指针,这些 block 指针指向的 block 也被全部用来存放 block 指针,而这些 block 指针指向的 block才被用来存放用户数据。第 15 个 block 是所谓的 triple indirect block,比double indirect block 又多了一层 block 指针,结构如下图。一个 inode 里面实际有多少个 block是由 inode 字段 i_size 再通过计算得到的。i_size 记录的是文件或者目录的实际大小,用它的值除以 block 的大小,就可以得出这个 inode 一共占有几个 block。

 

5)数据块:包含了文件的真实数据,在格式化时block的大小就固定了,且每个block都有编号,以方便inode的记录;原则上,block 的大小与数量在格式化完就不能够再改变了(除非重新格式化);在Ext2文件系统中所支持的block大小有1K, 2K及4K三种,由于block大小的区别,会导致该文件系统能够支持的最大磁盘容量与最大单一文件容量各不相同,Block 大小为4KB时最大文件长度为2TB。每个block 内最多只能够放置一个文件的资料,但一个文件可以放在多个block中(大的话);若文件小于block ,则该block 的剩余容量就不能够再被使用了(磁盘空间会浪费,带来碎片),但如果block 较小的话,那么大型档案将会占用数量更多的block ,而inode 也要记录更多的block 号码,此时将可能导致档案系统不良的读写效能,因此需要根据情况决定,一般为4k.

 

4 挂载(需要补充)

在一个区被格式化为一个文件系统之后,它就可以被Linux操作系统使用了,只是这个时候Linux操作系统还找不到它,所以我们还需要把这个文件系统「注册」进Linux操作系统的文件体系里,这个操作就叫「挂载」 (mount)。挂载是利用一个目录当成进入点(类似选一个现成的目录作为代理),将文件系统放置在该目录下,也就是说,进入该目录就可以读取该文件系统的内容,类似整个文件系统只是目录树的一个文件夹(目录)。这个进入点的目录称为「挂载点」。由于整个 Linux 系统最重要的是根目录,因此根目录一定需要挂载到某个分区。 而其他的目录则可依用户自己的需求来给予挂载到不同的分去。

Linux的文件体系的构建过程总结一下就是:硬盘经过分区和格式化,每个区都成为了一个文件系统,挂载这个文件系统后就可以让Linux操作系统通过VFS访问硬盘时跟访问一个普通文件夹一样。

 

5 虚拟文件系统VFS

文件系统并不只有上面所说的ext2等,文件系统一般可以分为以下几种:

1)  基于磁盘的文件系统是在非易失性介质上存储文件的经典方法,如ext2/3等;

2)  虚拟文件系统在内核中生成,是一种使用户应用程序和用户通信的方法,如procfs,sysfs,其不需要在任何种类的硬件设备上分配存储空间,其内容是从内核数据结构包含的信息生成的。

3)  网络文件系统是基于磁盘的文件系统和虚拟文件系统之间的折中。数据实际上存储在一个不同系统的硬件设备上,内核无需关注文件存取、数据组织和硬件通信的细节,这些由远程计算机的内核处理。

由于文件系统存在差异,实现及接口也不一致,内核为了管理使用了VFS虚拟文件系统进行抽象,提供一种操作文件、目录和其他对象的统一方法,使用户看不到底层文件系统的差异。该文件系统只是虚拟存在,必须使用各种对象和函数指针与每种文件系统适配。

VFS 作为文件系统接口的根层,记录当前支持的文件系统以及当前挂载的文件系统。可以使用一组注册函数在 Linux 中动态地添加或删除文件系统。内核保存当前支持的文件系统的列表,可以通过 /proc 文件系统在用户空间中查看这个列表。这个虚拟文件还显示当前与这些文件系统相关联的设备。在 Linux 中添加新文件系统的方法是调用 register_filesystem()。这个函数的参数定义一个文件系统结构(file_system_type)的引用,这个结构定义文件系统的名称、一组属性和两个超级块函数。可通过unregistr_filesystem()注销文件系统。在注册新的文件系统时,会把这个文件系统和它的相关信息添加到 file_systems 列表中。这个列表定义可以支持的文件系统。可通过cat /proc/filesystems查看该列表。

Linux VFS 存在四个基本对象:超级块对象 (superblock object)、索引节点对象 (inode object)、目录项对象 (dentry object) 及文件对象 (file object)。

超级块对象代表一个已安装的文件系统,每个超级块实例对应一个挂载的文件系统,如果已经挂载,就是活动超级块,当然一个超级块可以挂载到多个地方,每安装一个文件系统,就对应有一个超级块和安装点。超级块通过它的一个域s_type指向其对应的具体的文件系统类型。具体的文件系统通过file_system_type中的一个域fs_supers链接具有同一种文件类型的超级块。同一种文件系统类型的超级块通过域s_instances链接(因此对于每个挂载的文件系统都有一个超级块,一个安装实例和一个超级块对象一一对应,同一文件系统的超级块实例通过哈希链表组织)

索引节点对象代表一个文件,索引节点对象存储了文件的相关信息,代表了存储设备上的一个实际的物理文件。当一个文件首次被访问时,内核会在内存中组装相应的索引节点对象,以便向内核提供一个对文件进行操作时所必须的全部信息;这些信息一部分存储在磁盘特定位置,另外一部分是在加载时动态填充的。inode 表示文件系统中的一个对象,它具有惟一标识符,各个文件系统提供将文件名映射为惟一 inode 标识符和 inode 引用的方法;   

目录项对象代表一个目录项,一个路径的各个组成部分,不管是目录还是普通的文件,都是一个目录项对象,如设备文件 event5 在路径 /dev/input/event5 中,其存在四个目录项对象:/ 、dev/ 、input/ 及 event5。为文件路径的快速解析,Linux VFS 设计了目录项缓存(Directory Entry Cache,即 dcache),inode 和目录缓存分别保存最近使用的 inode 和 dentry,对于 inode 缓存中的每个 inode,在目录缓存中都有一个对应的 dentry。

文件对象代表由进程打开的文件,文件对象通过f_inode指向其对应的inode。文件对象和物理文件的关系有点类似进程和程序的关系。因为多个进程可以同时打开和操作同一个文件,所以同一个文件也可能存在多个对应的文件对象。文件对象仅仅在进程观点上代表已经打开的文件。一个文件对应的文件对象可能不是惟一的,但是其对应的索引节点和目录项对象是惟一的。

整体结构概观如下:图源

需要清楚VFS是真实文件系统的一个抽象,具体的操作都由真实文件系统实现,VFS使用相关的函数指针指向具体文件系统的函数实现,如super_operations, inode_operations 和 file_operations等。VFS相关数据结构需要使用实际文件系统数据进行填充,如vfs inode 和ext3 inode,如下,系统中的alloc_inode函数:

Ext3_create中调用了ext3_new_inode:

Ext3_new_inode中:

EXT_I为:return container_of(inode, struct ext3_inode_info, vfs_inode);

可见ext3_inode_info中成员vfs_inode指向对应的vfs inode,并且使用实际的ext3_inode_info填充 vfs inode的相关成员,而vfs inode中使用i_private指向对应的ext3_inode_info, 类似地,vfs中super_block结构中使用s_fs_inof指针指向实际的ext3_sb_info,可见vfs中的相关数据结构和真实文件系统的数据结构是有对应的。

对于vfs的目录项dentry,每个由vfs发送到底层实现的请求,都会导致创建一个新的dentry对象以保存请求的结果,这些对象保存在一个缓存中在下一次需要时可以快速访问。dentry表示目录文件,目录文件block内容有:该目录项的数据所在inode的编号,文件或目录的名称,block中还会自动生成两条记录,一条是.文件夹记录,inode指向自身,另一条是..文件夹记录,inode指向父文件夹。以查找/use/bin/emacs文件对应的inode进行说明:第一个为根目录/,依次查找得到最终文件的inode。Dentry结构中d_subdirs链表为给定目录下的所有文件/子目录相关联的dentry实例,d_parent为父目录dentry实例,d_name为目录/文件名称,当其很短时存至成员d_iname中。

 

6 块io

块设备(Block Device)是支持以固定长度的块为单位读写数据的存储设备的统称。Linux内核中负责提交对块设备IO请求的子系统被称为块IO子系统,也被称为Linux块层,结构如上图。

1)通用块层为各种类型的块设备建立了一个统一的模型,它主要的工作是接收上层发出的磁盘请求,并最终发出IO请求。该层隐藏了底层硬件块设备的特性,为块设备提供了一个通用的抽象视图。

2)IO调度层:接收通用块层发出的IO请求,缓存请求并试图合并相邻的请求(如果请求在磁盘上面是相邻的),并根据设置好的算法,回调驱动层提供的请求处理函数,以处理具体的IO请求。

3)块设备驱动层:具体的IO处理交给块设备驱动层来完成,视块设备的不同。对于大多数逻辑块设备,块设备驱动可能是一个纯粹的软件层,并不需要直接和硬件打交道,只是机械地重定向IO。对于SCSI块设备,其块设备驱动即为SCSI磁盘驱动,为SCSI子系统的高层驱动,从而将块IO子系统和SCSI子系统联系了起来。

块IO子系统的一般IO处理流程是:上层调用通用块层提供的接口向块IO子系统提交IO请求,这些请求首先被放入IO调度层的调度队列,经过合并和排序,最终将转换后的IO请求派发到具体的块设备的等待队列,由后者的驱动进一步处理。这个过程涉及两种形式的IO请求:一种是通用块层的IO请求,即上层提交的IO请求,在Linux内核中以bio结构描述;另一种是块设备驱动层的IO请求,即经过IO调度层转换后的IO请求,在Linux内核中以request描述。bio表示上层发给通用块层的请求,称为通用块层请求,它关注的是请求的应用层面,即读取(或写入)哪个块设备,读取(或写入)多少字节的数据,读取(或写入)到哪个目标缓冲区等、request表示通用块层为底层块设备驱动准备的请求,称作块设备驱动层IO请求,或块设备驱动请求,它关注的是请求的实施层面,即构造哪种类型的SCSI命令。

IO简单来讲,就是将数据从磁盘读入内存或者从内存写入磁盘。但是,为了提升系统性能,块IO子系统采用了聚散IO(scatter/gather IO)这样一种机制:将对磁盘上连续,但内存中不连续的的数据访问由单次操作即可完成。也就是说,在单次操作中,从磁盘上的连续扇区中的数据读取到几个物理上不连续的内存空间或者将物理上不连续的内存空间的数据写入磁盘的连续扇区。前者叫分散读,后者叫聚集写。上层向通用块层提交的IO请求是基于聚散IO的,它包含多个“请求段(segment)”,一个“请求段”是一段连续的内存区域,其中包含了和其他“请求段”处于连续扇区的数据。

一个块设备驱动层请求可能包含多个通用块层请求,也就是说,一次SCSI命令可以服务多个上层请求,这就是所谓的请求合并。在Linux内核实现中,请求合并就是将多个bio链入到同一个request。此外,块IO子系统还涉及不同的请求队列,包括IO调度队列和派发队列。IO调度队列是块IO子系统用于对通用块层请求进行合并和排序的队列。派发队列是针对块设备驱动的,即块IO子系统严格按照队列顺序提交块设备驱动层请求给块设备驱动处理。一般来说,每个块设备都有一个派发队列,IO子系统又为它内部维护了一个IO调度队列,不同的块设备可以采用不同的IO调度算法。

通用块层请求到达块IO子系统时,首先在IO调度队列中进行合并和排序,变成为块设备驱动层的请求。之后块设备驱动层请求按照特定的算法被转移到派发队列,从而被提交到块设备驱动。在Linux内核中,IO调度队列和派发队列都反应在request_queue结构中。

 

一些问题:

1、  为什么不能对目录创建硬链接

(1)首先明确硬链接和软链接的概念:软链接也称为符号链接。链接为 Linux 系统解决了文件的共享使用,还带来了隐藏文件路径、增加权限安全及节省存储等好处。若一个 inode 号对应多个文件名,则称这些文件为硬链接。换言之,硬链接就是同一个文件使用了多个别名。  

由于硬链接是有着相同 inode 号仅文件名不同的文件,因此硬链接存在以下几点特性:文件有相同的 inode 及 data block;只能对已存在的文件进行创建;不能交叉文件系统进行硬链接的创建;不能对目录进行创建,只可对文件创建;删除一个硬链接文件并不影响其他有相同 inode 号的文件。

软链接与硬链接不同,若文件用户数据块中存放的内容是另一文件的路径名的指向,则该文件就是软连接。软链接就是一个普通文件,只是数据块内容有点特殊。软链接有着自己的 inode 号以及用户数据块。因此软链接的创建与使用没有类似硬链接的诸多限制:软链接有自己的文件属性及权限等;可对不存在的文件或目录创建软链接;软链接可交叉文件系统;软链接可对文件或目录创建;创建软链接时,链接计数 i_nlink 不会增加;删除软链接并不影响被指向的文件,但若被指向的原文件被删除,则相关软连接被称为死链接(若被指向路径文件被重新创建,死链接可恢复为正常的软链接)。两者如图:

 

(2)为什么不能对目录创建硬链接

1)如果使用 hard link 链接到目录时, 链接的数据需要连同被链接目录底下的所有数据都创建链接,举例来说,如果你要将 /etc 使用实体链接创建一个 /etc_hd 的目录时,那么在 /etc_hd 底下的所有文件/目录同时都与 /etc 底下的文件/目录创建 hard link 。 且未来如果需要在 /etc_hd 底下创建新文件时,连带的 /etc 底下的数据又得要创建一次 hard link ,因此造成环境相当大的复杂度;

2)硬链接的创建会导致当前目录.和父目录..指向混乱,具体

3)对目录的硬链接可能在目录中引入循环,在遍历目录的时候会使系统陷入无限循环。举例来说,文件夹 a,b,在a下面创建b文件夹的硬链接c,在b下面创建a文件夹的硬链接d,ls a ,会看到c,ls c,看到b下的文件夹d,再ls d又看到c,这样可以无限ls下去。

在linux系统中,每个文件(目录也是文件)都对应着一个inode结构,其中inode数据结构中包含了文件类型(目录,普通文件,符号连接文件等等)的信息,操作系统在遍历目录时可以判断出符号链接,既然可以判断出符号链接就可以采取一些措施来防范进入过大的循环,因此系统在连续遇到8个符号连接后就停止遍历,故目录符号链接不会进入死循环。软连接在访问时readlink有递归次数的限制,硬链接就是普通inode,所以没办法记录递归次数。

另:dentry通过文件名和父目录dentry结构地址进行哈希放入哈希表中,内核中哈希函数的实现

 

参考《深入Linux内核架构》及如下链接

整体:https://www.cnblogs.com/bellkosmos/p/detail_of_linux_file_system.html

Ext2: https://www.ibm.com/developerworks/cn/linux/filesystem/ext2/index.html

块io过程:https://www.cnblogs.com/luxiaodai/p/9254118.html

https://blog.csdn.net/hty46565/article/details/74783749

posted @ 2019-03-30 19:41  ccxikka  Views(377)  Comments(0Edit  收藏  举报