对Linux中文件系统的相关知识进行整理:html
1 总体概况前端
文件系统是应用程序与块设备(磁盘等)之间的桥梁,是对文件进行统一管理的中间层。对上向上层用户提供读写文件的操做接口,对下将文件在磁盘上进行存储及有效的管理。对上图由下往上:node
1)最底层为块设备,即存储硬盘,如PATA, SATA和AHCI等;linux
2)不一样硬盘对应不一样的驱动模块,在Linux系统中,对不一样硬盘所提供的驱动模块通常都存放在内核目录树drivers/ata中,对于通常通用的硬盘驱动,也许会直接被编译到内核中;算法
3)通用块设备层,不一样的硬盘驱动会提供不一样的IO接口,为方便管理,内核把这些接口抽象造成一个统一的对外接口,这样任意硬盘驱动对外所提供的IO接口都一视同仁的被看做块设备来处理;缓存
4)文件系统层,对应到具体格式化硬盘的真实文件系统,每一个文件系统实现导出一组通用接口供 VFS 使用。缓冲区缓存会缓存文件系统和相关块设备之间的请求。例如对底层设备驱动程序的读写请求会经过缓冲区缓存来传递。这就容许在其中缓存请求,减小访问物理设备的次数,加快访问速度;安全
5)虚拟文件系统层,一样地,不一样的文件系统对应的结构和操做函数存在差别,VFS就把这些不一样的文件系统作一个抽象,提供统一的API访问接口,这样用户空间就不用关心不一样文件系统中不同的API了。Vfs中还有两个针对文件系统对象的缓存(inode 和 dentry)。它们缓存最近使用过的文件系统对象,减小从硬盘读取的次数;网络
6)系统调用层,提供统一的API访问接口给用户。数据结构
2 硬盘存储(了解)架构
现代计算机大部分文件存储功能都是由机械硬盘这种设备提供的。(如今的SSD和闪存从概念和逻辑上都部分继承自机械硬盘,因此使用机械硬盘来进行理解也是没有问题的)机械硬盘能实现信息存储的功能基于:磁性存储介质可以被磁化,且磁化后会长久保留被磁化的状态,这种被磁化状态可以被读取出来,同时这种磁化状态还可以不断被修改,磁化正好有两个方向,因此能够表示0和1。因而硬盘就是把这种磁性存储介质作成一个个盘片,每个盘片上都分布着数量巨大的磁性存储单位,使用磁性读写头对盘片进行写入和读取。
磁头读写文件的时候,首先是分区读写的,由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操做系统支持不少不一样的文件系统,好比ext二、ext三、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和其余可表示为三、五、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)首先明确硬连接和软连接的概念:软连接也称为符号连接。连接为 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