当前位置: 首页 > news >正文

深入理解Ext2:Linux文件系统的基石与它的设计哲学

                                        

                        🔥海棠蚀omo:个人主页

                        ❄️个人专栏:《初识数据结构》,《C++:从入门到实践》,《Linux:从零基础到实践》

                        ✨追光的人,终会光芒万丈

博主简介:

目录                                  

Ext2文件系统

1.宏观认识

2.Data Blocks(数据块)

3.Block Bitmap(块位图)

4.Inode Table(i节点表)

5.Inode Bitmap(inode 位图)

6.GDT(Group Descriptor Table)

7.Super Block(超级块)

8.扩展知识

8.1Super Block的扩展知识

8.2格式化的简单理解

8.3增删查改文件的真实过程

8.4关于inode编号和datablock编号

9.目录与文件名

10.路径缓存

11.挂载分区

12.软硬链接

12.1软链接文件

12.2硬链接

在上一篇中我们讲解了硬盘相关的知识,而在文章的最后我们引出了文件系统的概念,但是上一篇并没有讲清楚到底什么是文件系统,它又有什么什么作用等等,这些问题将在今天这篇文章得到解决。

首先我们要先了解什么是文件系统,回忆一下我们在上一篇的最后讲到了分区的思想,分区就是将我们的硬盘划分为不同的区域,例如:C盘,D盘,E盘等等,那我们要不要对每个区域进行细致化的管理呢?

答案肯定是要的,而文件系统就是充当这样的角色,我们把这几个区域比作一块地皮,那么文件系统就负责规划如何在这块地皮上进行建设和管理

并且在大多数情况下,每一个分区都会有一个文件系统的,你没听错,是每一个分区都有!!!

而不同的操作系统也有不同的文件系统,同一个操作系统也会有不同的文件系统,对于Linux操作系统我今天就以Ext2操作系统为例,带领大家来看看文件系统是如何来管理" 地皮 "的。

Ext2文件系统

1.宏观认识

虽然我们在上一篇中讲到了分区的思想,但是一个分区就有几百个G,甚至更多,也不好进行管理啊,那么针对这种问题,linux又在分区的基础上引入了块组的概念,那么何为块组呢?

如上图所示,我们把一个300GB的分区给划分一下,一个块组就占10GB,这样就可以将300GB划分为30个块组,这样我们就把一个分区管理好转变为把一个块组给管理好,而块组这种数量也不是很多,容量也不是很大,管理起来就刚刚好。

而我们今天的任务就是研究这个块组,把这个块组给研究明白了,管理起来就很容易了,下面我们就来看看一个块组中都有些什么东西:

上面的结构也就是我们一个分区的结构,最上面就是不同的分区,下面就是我们的文件系统,最后就是我们下面要讲的块组。

我们可以看到,在一个块组中分为不同的区域,分别是:Super Block,GDT,Block Bitmap,inode Bitmap,inode Table,Data Blocks,下面我们就一一讲解这几个区域。

2.Data Blocks(数据块)

我们首先来讲Data Blocks,叫做数据块,我们现在都知道:文件 = 文件属性 + 文件内容,并且linux中文件的内容和属性是分开存储的。而这个区域就是用来保存文件的内容的。

而OS文件系统中,和磁盘进行IO的基本单位是4kb,也就是一个块,所以在这个区域中就分布着大量的块,文件的内容就保存在这些块中

并且在一个组中,Data Blocks区域占据大头,也就是大部分的空间都属于Data Blocks这个区域,这点想必大家都能理解,毕竟文件的内容加在一起是很多的,所以这个区域空间大也在情理之中。

而一个文件可能内容为空,所以一个文件可能对应0个或多个数据块

而在这里要输出一个结论:每一个数据块,都有唯一的编号!!!

这个结论我们后面会用到,那么我们来思考一个问题:如何判断Data Blocks中哪些数据块被使用过了,哪些是未被使用的,处于空闲的块呢?

可能有人会说,那我们遍历一遍这块区域判断一下不就行了,但是数据存储在磁盘上是以二进制存储的,也就是一堆0和1,你该如何判断呢?

所以说这样是判断不了的,那该如何判断呢?

答案就是下面要讲的Block Bitmap。

3.Block Bitmap(块位图)

这块区域叫做块位图,听名字我们就是到这是运用了位图的思想,在这块区域中我们把Data Blocks中的每一个块当作一个比特位,比特位的位置就对应着数据块的位置,而比特位的内容【0,1】就表示这个数据块是否被占用,0就表示未被占用,1就表示被占用了

这块区域有多大呢?下面我们举个例子:

现在Data Blocks区域中有1000000个数据块,也就对应着1000000个比特位,那么:

这1000000个比特位也就是125000个字节,我们接着换算:

将其换算成kb,我们就可以得出125000个字节就是122kb,占据的空间很小,这也是位图的特点。

4.Inode Table(i节点表)

那么上面介绍了关于文件内容的部分,而文件 = 文件属性 + 文件内容,那文件属性保存在哪里呢?

在linux中,通过结构体struct inode来表示文件属性!!!

/*
* Structure of an inode on the disk
*/
struct ext2_inode {__le16 i_mode; /* File mode */__le16 i_uid; /* Low 16 bits of Owner Uid */__le32 i_size; /* Size in bytes */__le32 i_atime; /* Access time */__le32 i_ctime; /* Creation time */__le32 i_mtime; /* Modification time */__le32 i_dtime; /* Deletion Time */__le16 i_gid; /* Low 16 bits of Group Id */__le16 i_links_count; /* Links count */__le32 i_blocks; /* Blocks count */__le32 i_flags; /* File flags */union {struct {__le32 l_i_reserved1;} linux1;struct {__le32 h_i_translator;} hurd1;struct {__le32 m_i_reserved1;} masix1;} osd1; /* OS dependent 1 */__le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */__le32 i_generation; /* File version (for NFS) */__le32 i_file_acl; /* File ACL */__le32 i_dir_acl; /* Directory ACL */__le32 i_faddr; /* Fragment address */union {struct {__u8 l_i_frag; /* Fragment number */__u8 l_i_fsize; /* Fragment size */__u16 i_pad1;__le16 l_i_uid_high; /* these 2 fields */__le16 l_i_gid_high; /* were reserved2[0] */__u32 l_i_reserved2;} linux2;struct {__u8 h_i_frag; /* Fragment number */__u8 h_i_fsize; /* Fragment size */__le16 h_i_mode_high;__le16 h_i_uid_high;__le16 h_i_gid_high;__le32 h_i_author;} hurd2;struct {__u8 m_i_frag; /* Fragment number */__u8 m_i_fsize; /* Fragment size */__u16 m_pad1;__u32 m_i_reserved2[2];} masix2;} osd2; /* OS dependent 2 */
};#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)

上面就是ext2文件系统中对应的inode源码,在这里面我们能看到不少熟人,比如:mode(文件权限),acm(文件相关的三个时间)等等,但是在inode结构体中并没有保存文件名这个属性,关于文件名的相关知识在文章后面会讲。

并且每个文件所对应的inode中的成员变量是一样的,只不过每个成员变量所对应的内容不同,也就是inode的大小是固定的,既然inode的大小是固定的,那一个inode结构体有多大呢

答案是128个字节或者256个字节,当然在我们今天讲的ext2文件系统中,inode是128个字节,上面我们也说了OS读取文件,一次读取4kb的数据,而4kb就相当于32个inode,也就是OS一次会读取32个inode。

那么这个时候就有一个问题:每个文件都有其对应的inode,那要怎么保证文件的唯一性呢?

这个问题的答案就和上面的Data Blocks中的数据块是一样的,既然每个数据块都有自己的编号,那么文件的inode也当然有自己的编号,这个编号在inode中保存,叫做:int inode_number,上面ext2中的inode可能没有或者在别的地方,因为ext2属于比较早的文件系统了,我们知道会有这个编号就行了。

那么上面讲的和Inode Table有什么关系呢?

答案就是每个文件的inode都会保存在Inode Table这块区域中,也就是:

就如上图所示,这里面就像表一样展示出了每个inode结构体中的各种属性,一目了然。

但是此时又会衍生出一个问题:既然文件的属性和内容是分开存储的,我们未来可通过inode号找到对应的文件属性,但是怎么找到文件相对应的内容呢?

这点linux当然也为我们考虑到了,我们来看:

在上面的inode结构体中,有这样一个数组,这个数组就是能从inode -> block的,这个数组中保存的就是的当前文件的内容所对应的数据块的块号,或者说编号,这也是Data Blocks中每个数据块都有相应的编号的原因。

我们未来通过inode编号,就能找到文件对应的inode属性,它的内部具有和数据块的对应关系,就能进一步找到文件的内容了。

但是,在结构体的下方:

在这里定义了上面数组的大小为12,后面有对其加了3个1,也就是这个数组最终大小也就是15,15个块号也就对应15个数据块,也就是60kb大小的空间,但我们一个文件的大小肯定不止60kb,那该如何保存文件内容所对应的块号呢?

在这个数组中,前面12个确实直接指向的保存文件内容的块号,但第13个它也对应一个块号,但是这个数据块中并不保存文件内容,它保存的是保存文件内容的块号,而一个块号是一个整数,也就是4个字节,一个块是4kb,也就是4096个字节,换算下来,这样的一个块就能保存1000余个保存文件内容的块号!!!

而这样的一个块就叫做一级间接块,通过这样的方式不就形成了多叉树的结构了吗?

当然,即使一千个块对应的大小也并不大,所以就有了后面的二级间接块,二级间接块中保存的是一级间接块的块号,那么换算下来,一个二级间接块就可以保存1000余个一级间接块的块号,也就对应1000000个保存文件内容的块号!!!

要是还不够,还有三级间接块呢,换算方式就和上面是一样的。 

而Inode Table也同样有和Data Blocks一样的问题:我们如何判断Inode Table中那些块已经被占用了,哪些是未被使用的,或者说空闲的块?

答案也和上面是一样的,Inode Table也同样有自己的位图表。

5.Inode Bitmap(inode 位图)

这个位图的道理和上面是一样的,同样将Inode Table中的每一个块都当作一个比特位,比特位的位置就是inode的位置,比特位的内容【0,1】,就表示对应的inode是否被占用,0就表示未被占用,1就表示被占用了。

说了这么多inode相关的话题,想必大家肯定会有一个疑问:我们平常没见过inode号啊,这个inode号到底是多少呢?下面我们就来看看:

我们通过ls -li就能看到每个文件所对应的inode号,这个i就是inode的意思。有了这个inode号,OS就能在磁盘中找到对应组中的文件属性和内容。

6.GDT(Group Descriptor Table)

这个区域叫做块组描述符表,是用来描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符表。每个块组描述符表存储⼀个块组的描述信息,如在这个块组中从哪⾥开始是inode Table,从哪⾥开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷⻉。

也就是说GDT是用来描述块组的一块区域,我们来看看它里面有什么:

// 磁盘级blockgroup的数据结构
/*
* Structure of a blocks group descriptor
*/
struct ext2_group_desc
{__le32 bg_block_bitmap; /* Blocks bitmap block */__le32 bg_inode_bitmap; /* Inodes bitmap */__le32 bg_inode_table; /* Inodes table block*/__le16 bg_free_blocks_count; /* Free blocks count */__le16 bg_free_inodes_count; /* Free inodes count */__le16 bg_used_dirs_count; /* Directories count */__le16 bg_pad;__le32 bg_reserved[3];
};

可以看到里面就有上面我们讲过的block bitmap等字眼。

7.Super Block(超级块)

这个听名字就感觉不一般,这块区域是干什么的呢?

这块区域是用来存放⽂件系统本⾝的结构信息,描述整个分区的⽂件系统信息。记录的信息主要有:bolck 和 inode的总量,未使⽤的block和inode的数量,⼀个block和inode的⼤⼩,最近⼀次挂载的时间,最近⼀次写⼊数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息

也就是说这块区域是用来描述整个分区的信息的,并且Super Block的信息被破坏,可以说整个⽂件系统结构就被破坏了,它的重要性不言而喻。

下面我们来见一见这个超级块:

/*
* Structure of the super block
*/
struct ext2_super_block {__le32 s_inodes_count; /* Inodes count */__le32 s_blocks_count; /* Blocks count */__le32 s_r_blocks_count; /* Reserved blocks count */__le32 s_free_blocks_count; /* Free blocks count */__le32 s_free_inodes_count; /* Free inodes count */__le32 s_first_data_block; /* First Data Block */__le32 s_log_block_size; /* Block size */__le32 s_log_frag_size; /* Fragment size */__le32 s_blocks_per_group; /* # Blocks per group */__le32 s_frags_per_group; /* # Fragments per group */__le32 s_inodes_per_group; /* # Inodes per group */__le32 s_mtime; /* Mount time */__le32 s_wtime; /* Write time */__le16 s_mnt_count; /* Mount count */__le16 s_max_mnt_count; /* Maximal mount count */__le16 s_magic; /* Magic signature */__le16 s_state; /* File system state */__le16 s_errors; /* Behaviour when detecting errors */__le16 s_minor_rev_level; /* minor revision level */__le32 s_lastcheck; /* time of last check */__le32 s_checkinterval; /* max. time between checks */__le32 s_creator_os; /* OS */__le32 s_rev_level; /* Revision level */__le16 s_def_resuid; /* Default uid for reserved blocks */__le16 s_def_resgid; /* Default gid for reserved blocks *//** These fields are for EXT2_DYNAMIC_REV superblocks only.** Note: the difference between the compatible feature set and* the incompatible feature set is that if there is a bit set* in the incompatible feature set that the kernel doesn't* know about, it should refuse to mount the filesystem.** e2fsck's requirements are more strict; if it doesn't know* about a feature in either the compatible or incompatible* feature set, it must abort and not try to meddle with* things it doesn't understand...*/__le32 s_first_ino; /* First non-reserved inode */__le16 s_inode_size; /* size of inode structure */__le16 s_block_group_nr; /* block group # of this superblock */__le32 s_feature_compat; /* compatible feature set */__le32 s_feature_incompat; /* incompatible feature set */__le32 s_feature_ro_compat; /* readonly-compatible feature set */__u8 s_uuid[16]; /* 128-bit uuid for volume */char s_volume_name[16]; /* volume name */char s_last_mounted[64]; /* directory where last mounted */__le32 s_algorithm_usage_bitmap; /* For compression *//** Performance hints. Directory preallocation should only* happen if the EXT2_COMPAT_PREALLOC flag is on.*/__u8 s_prealloc_blocks; /* Nr of blocks to try to preallocate*/__u8 s_prealloc_dir_blocks; /* Nr to preallocate for dirs */__u16 s_padding1;/** Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.*/__u8 s_journal_uuid[16]; /* uuid of journal superblock */__u32 s_journal_inum; /* inode number of journal file */__u32 s_journal_dev; /* device number of journal file */__u32 s_last_orphan; /* start of list of inodes to delete */__u32 s_hash_seed[4]; /* HTREE hash seed */__u8 s_def_hash_version; /* Default hash version to use */__u8 s_reserved_char_pad;__u16 s_reserved_word_pad;__le32 s_default_mount_opts;__le32 s_first_meta_bg; /* First metablock block group */__u32 s_reserved[190]; /* Padding to the end of the block */
};

既然是描述整个文件系统的,它里面的东西肯定是不少的,有着各种各样的属性。

8.扩展知识

虽然讲了前面的内容,但也只是单纯的介绍了他们每个的作用,这肯定还不够,下面我再来为大家补充一些扩展知识。

8.1Super Block的扩展知识

上面在讲解Super Block的时候不知道大家有没有这样一个问题:Super Block既然是描述一个分区的所有分组的情况,那它为什么会在一个块组中呢?

它不应该在文件系统的最前面单独一块区域来存储这个Super Block的相关内容吗

我们正常的思维应该就如上面所说在文件系统的卡面单独开一片区域来存储Super Block,但是如果是这样,大家有没有想过这样一个问题:你的硬盘因为某种原因导致前面Super Block的区域被刮花了,或者直接损坏了,导致整个文件系统的结构被破坏了,该如何呢?

因为只有前面这一块区域是用来存储Super Block的相关内容的,既然被破坏了,那谁来也没招了,这个文件系统,或者说这个分区就报废了,上面我们举的例子可能就几百个G,但要是一个公司几个T甚至更多呢?

这样不就造成了很大的损失吗?所以才没有这样设计,而是在多个块组中都有Super Block这块区域,这样既是一个块组中的Super Block被破坏了,也能通过其他块组中的Super Block来恢复。

但是不一定所有的块组中都有Super Block,但是会有多个块组中存在同样的Super Block!!!

8.2格式化的简单理解

我们新建一个分区,Super Block和GDT一定是有效数据的,因为我们要给特定分区写入管理信息,即写入文件系统和分区分组相关的管理数据,但是文件数据可以没有!!!

这不就是我们常说的格式化吗,没有文件数据,只有Super Block和GDT等的相关数据:

在windows下就有格式化的选项,道理和上面是一样的,格式化后,整个分区或者整个盘就没有文件数据了,只剩下了Super Block和GDT等的相关数据,这里我们说的是完全格式化哈,不是快速格式化

快速格式化并不会将文件数据给清理掉,只是我们看不见了而已,完全格式化才会将文件数据给清理掉。

8.3增删查改文件的真实过程

当我们讲了inode的相关知识后,我们知道了在一个分区内,用inode编号来标识一个文件的唯一性,那我们就来探讨一下新建文件,删除文件,修改文件,查找文件的真实过程

新建文件:既然是新建的文件,那肯定要为这个文件创建一个inode结构体,那么首先就会在Inode Bitmap中找哪个位置是0,也就是哪个比特位是未被使用的,然后将其改为1,并将这个inode中的相关属性给初始化好保存在Inode Table中。

但是既然是新建的文件,文件中没有任何内容,那么文件系统并不会在Block Bitmap中找一个未分配的比特位给它,也就不会占用任何的数据块。

删除文件:而删除文件就有的说了,它的真实过程和我们想的不太一样。如果我们要删除一个文件,首先会通过这个文件的inode编号找到它所在的块组中,通过inode中的记录的属性找到Inode Bitmap中的相应位置,将其改为0,Block Bitmap也是一样的,但是并不会将Data  Blocks中属于该文件的内容给清理掉!!!

也就是删除文件只改位图,那么这时候就有人问了:为什么不将文件的内容一并删除呢?

那我问你:你误删了文件,想不想将文件恢复过来?

包想的,这就是不删除文件内容的原因,便于我们误删文件后恢复它,这也就是为什么我们在向硬盘中传一个几个G的视频需要几分钟,而将其删除只需要1~2秒的原因,因为它只改位图。

如果要将文件的内容也删除的话,那传输文件和删除文件的时间应该相差无几的。

修改文件和查找文件:这两个就很简单了,既然能通过inode编号找到相应的inode结构体,那么就可以通过这个结构体找到文件的内容了,剩下的工作就很简单了,就是我们前面章节讲的打开文件的过程呗。

8.4关于inode编号和datablock编号

关于这两个编号我要讲的是这两个编号是全区统一分配的,不是只在分组内有效,inode不能跨区域,一个分区,一个文件系统,互相独立

在Super Block中就定义了每个组block和inode的数量,也就是每个组的block和inode数量是相同的,那么每个组的大小也是相同的

举个例子,比如:经过计算,这个分区需要100000个inode编号,那么对于第一个块组而言,它的inode编号就是从0开始计数的,如果这个块组使用了10000个inode编号,那么第二个块组的inode编号就要从10000开始计数,以此类推。

而此时有一个问题:如果一个文件的大小超出了一个组的大小,该怎么办呢?换句话说也就是这个组的dataclock不够用了,该如何呢?

这个问题正因为block编号在整个分区是有效的而得到解决,答案就是可以调用其他组的空闲的数据块来保存文件内容!!!

正因为block编号在整个分区内有效,所以即使用了其他组的数据块,我们照样能通过块号来找到文件的内容。

那么针对上面block和inode数量是有限的,我在问大家一个问题:有没有可能,在一个分区中inode用完了,而block没用完?或者block用完了,而inode没用完?

答案当然是可能的,第一种情况无非就是文件太多,但是每个文件都很小,而第二种情况就是文件不多,但是每个文件都太大了

这也就是为什么有的时候明明这个分区中还有空间,但是我们无法创建文件的原因了,因为inode用完了。

9.目录与文件名

上面我们在介绍inode结构体时,说到文件名这个属性并不在一个文件inode结构体中,那它应该在哪里呢?

 在解决这个问题之前,我想问大家:如何看待一个目录文件?

既然是文件,那根据文件 = 文件属性 + 文件内容,文件属性我们有inode,但是目录文件的内容是什么呢?

我们想一下,一个目录中保存的是什么?

是不是就是一个个的文件啊,那么目录文件的内容的就是文件名和其inode的映射关系!!!

但是我们查看一个目录文件的内容时,看不到其inode,只能看到文件名,下面通过一个例子来带大家看看:

这段代码就利用了系统调用接口来查看一个目录文件的内容,可以看到在目录文件中确实保存了文件名和其inode的映射关系。

所以从今天开始,在文件系统中,从存储方式的角度来看,存储普通文件和目录,有区别吗?

答案没有任何区别,都是文件,一视同仁。

在有了上面的理解后,我们来看两个结论:

结论一:

同一个目录下,文件名不能重复;在指定目录下新建一个文件,对于目录来说,不过就是将文件的文件名和其inode之间的映射关系写到相应的datablock中罢了。

而这就是为什么我们在讲解权限时,没有w权限就不能在一个目录下新建文件和删除文件等操作,没有r权限我们就不能看到目录下有哪些文件,因为这些都是目录文件的内容啊。

没有相应的权限,我们怎么能进行w和r呢?

结论二:

那既然我们要打开一个文件,就需要有其inode编号,那么这东西在哪儿呢?

在它的上级目录的内容中保存着,也就是我们要打开上级目录文件才能查看当前文件的inode编号,那我们要想看到上级目录中的内容,是不是就要打开上上级目录文件啊?

以此类推,也就是我们要从根目录开始,打开一个个相应的目录文件,才能找到我们的目标文件:

也就是要打开一个特定路径下的文件,就要对这个文件所在的路径进行解析,打开前面的目录文件,这就是为什么在linux下,文件路径可以定位文件的根本原因!!!

这也就是为什么我们之前在使用open函数时,必须要有路径的原因!!!

这也就是为什么我们在linux下访问一个文件,都需要路径访问!!!

这也就是为什么一个进程的PCB中,会有cwd的根本原因!!!

一切的一切,都是要对一个文件进行操作时,需要对文件的路径进行解析。

10.路径缓存

但是此时就有一个问题:如果每次要打开一个文件,都要进行路径解析,打开一个个目录文件,这样不慢吗?

如果真是这样,那当然慢了,所以为了解决这样的情况,就引入了路径缓存的概念,而在介绍路径缓存之前我们先了解一部分知识。

当我们在linux中安装了tree命令后,执行可以看到整个目录的树状结构,那么这个树状结构是怎么来的呢?

实际上,在linux的内核中会有一个维护树状路径结构的内核结构体叫做:struct dentry,每当我们打开一个文件,就会在内核中构成一个这样的结构体,我们来看看这个结构体中都有什么:

struct dentry {atomic_t d_count;unsigned int d_flags; /* protected by d_lock */spinlock_t d_lock; /* per dentry lock */struct inode* d_inode; /* Where the name belongs to - NULL is* negative *//** The next three fields are touched by __d_lookup. Place them here* so they all fit in a cache line.*/struct hlist_node d_hash; /* lookup hash list */struct dentry* d_parent; /* parent directory */struct qstr d_name;struct list_head d_lru; /* LRU list *//** d_child and d_rcu can share memory*/union {struct list_head d_child; /* child of parent list */struct rcu_head d_rcu;} d_u;struct list_head d_subdirs; /* our children */struct list_head d_alias; /* inode alias list */unsigned long d_time; /* used by d_revalidate */struct dentry_operations* d_op;struct super_block* d_sb; /* The root of the dentry tree */void* d_fsdata; /* fs-specific data */
#ifdef CONFIG_PROFILINGstruct dcookie_struct* d_cookie; /* cookie, if any */
#endifint d_mounted;unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */
};

而在这个结构体中我们要关注这几个:

其中第一个指向的就是当前文件的inode,而后面的第二个和第三个我们看着是不是很眼熟,有parentchildren,这不就是我们学过的数据结构:多叉树吗?

结构体中有能够指向parent和children,通过这种方式就能构成树状结构,我们来看:

当我们打开的文件更多的时候,上面的结构体就会构成这样的树状结构,而在有了这样的树状结构后,我们后面再打开访问任何文件,都会优先在这棵树中根据路径进行查找,找到了就返回属性inode和内容,没找到就构造一个dentry接入这棵树中

这种树状结构我们叫做:路径缓存结构,其实就是将你曾经访问过的路径给保存下来,下次再访问相同的路径时就会快很多,所以在做路径解析时,只有第一次是最慢的,随着路径缓存结构越来越完善,解析的速度就会越来越快!!!

那么可能有人问了:那如果我们打开的文件越来越多,路径缓存结构越来越复杂,会不会占据的空间过大导致内核装不下呢?

答案是不会的,因为整个路径缓存结构也同时会隶属于LRU(Least Recently Used,最近最少使⽤)结构中,会进行节点淘汰!!!

这样就不会有上面的问题了,并且它还隶属于Hash结构,便于快速查找

一个结构隶属于其他的数据结构我们应该都屡见不鲜,这里就不过多介绍了。

11.挂载分区

有了对上面知识的理解后,最后一个问题:我们上面说了inode编号和block编号只在当前分区内有效,不能跨区,那么在不同的分区中会出现inode编号和block编号相同的情况,此时该如何分辨该文件属于哪个分区呢?

要解决这个问题我们就需要了解挂载分区的相关知识,那么什么叫做挂载分区呢?

我们要知道,一个磁盘必须经过分区格式化,才能具有使用的前提,但是一个分区要想被真正使用,必须挂载到指定的目录下才可以!!!

也就是说当一个分区被挂载到一个指定的目录下的时候,那么在这个目录下进行的关于文件的操作,比如:新建文件,删除文件等,都会被认为是在这个分区中进行的,包括文件的inode编号也是在这个分区中进行分配和计数的。

我们可以通过df -h命令来查看当前的linux系统下可使用的分区,右上角的Mounted on就是挂载的意思,在这里面我们可以看到有不同的分区,我们现在只看这两个:

这里我用的是云服务器,这两个分区就代表我的服务器中的磁盘,也就类似于windows下的C盘,D盘等。

并且可以看到vda3这个分区被挂载到了根目录,而我们日常在新建文件等操作时,其实都是在根目录下的某个路径中进行操作的,所以其实我们就是在vda3这个分区中进行操作的,而上面我们看到的inode编号也只是在这个分区中有效。

同样的道理,vda2分区被挂载到了/boot/efi这个目录下,在这个目录下或者在这个目录的路径中所进行的文件操作,都是在vda2分区中进行的。

所以我们要想知道目标文件是在哪个分区中,需要通过判断目标文件的" 路径前缀 ",与每个分区被挂载的目录进行比较,符合哪个就说明目标文件在哪个分区中。 

下面我们自己来创建一个分区,来带大家更好的去理解:

这第一步我们就需要先创建一个分区,如上图所示,这里我就创建了一个名为disk.img的分区,大小为5MB,通过结果我们也可以看到分区的大小确实为5MB。

而第二步操作通过mkfs命令就是向这个分区中写入文件系统,对其进行格式化的操作,这里我向文件中写入的是ext4文件系统。

第三步操作就是通过mount命令将我们的分区挂载到指定的目录下,完成上面的操作后我们再次通过df -h命令就可以看到我们新创建的分区已经挂在到了上面我们指定的目录下。

这里解释一下为什么分区的名字变成了loop:因为我们本次创建的分区太小了,所以我们所创建的分区被识别为了loop这种设备,叫做循环设备,这点我们不必担心。

此时我们进到hello目录下可以看到有一个lost+found的目录,这个目录是根据ext4文件系统的找回机制相关方面所创建的,这里不用关心它。

这里我们随便创建一个文件,通过ls -li来查看文件的inode,可以看到我们新创建的文件inode非常小,通过比较可以看到在vda3分区中的inode和我们自己创建的分区中的inode还是有比较大的差异的,毕竟这些文件属于不同的分区。

而我们要卸载相应的分区的话可以借助umount命令来卸载:

将相应分区所挂载的目录卸载umount后面就可以卸载相应的分区了,我们再次通过df -h命令查看就会发现已经没有我们所创建的分区的信息了。

12.软硬链接

最后我们谈一点额外知识,这块知识叫做软硬链接,这块是讲什么的呢?

首先我们要知道,在linux中有两种特殊的文件,分别为:软链接文件硬链接文件

首先我们先来见见这两个文件:

通过ln -s target link的命令形式,我们就生成了一个test-soft的软链接文件,命令中的-s就是soft的意思,而生成软链接文件后我们通过名字就可以看出这个软链接文件是链接的哪个文件。

不加-s就默认生成的是硬链接文件

并且我们通过查看它们各自的inode,可以发现软链接是一个独立的文件,有自己的inode,但是硬链接文件和test.c的inode相同,说明它不是一个独立的文件,因为它没有自己的inode

那他们各自有什么特点呢?我们一起来看看。

12.1软链接文件

一个文件 = 属性 + 内容,属性有inode,那么软链接文件的内容是什么呢?

答案就是所链接文件的路径,也就是软链接指向的文件的路径字符串!!!

那软链接文件有什么用呢?或者说它有什么应用场景呢?

举个例子,我们在windows下都会有软件图标,通过双击它们就能打开相应的软件,那大家有没有想过为什么双击图标就能打开相应的软件?

我们查看其属性,发现在目标这一栏中存的就是相应软件可执行文件的绝对路径的字符串,讲到这里相信大家就知道我想说什么了。

没错,软链接的应用场景之一就是快捷方式,上面的快捷方式就是一种软链接文件,我们通过双击快捷方式,其实在底层就会启动文件内容所存的路径中可执行文件,这样就会打开相应的软件了。

如果没有快捷方式,我们每次启动软件都需要去相应的路径中自己去启动,比较麻烦,并且有时候我们都不知道软件下到哪儿了,有了快捷方式就会很方便。

12.2硬链接

从上面的现象中我们发现硬链接并不是一个独立的文件,那它是什么呢?

直接说结论:硬链接本质是在指定目录下,建立新的文件名和目标inode的映射关系,并没有在系统层面创建新的文件

意思就是本来一个文件名对应一个inode,而有了硬链接后,就可以多个文件名对应一个inode,这有什么作用呢?我们来看一种现象:

上面我直接把test.c文件给删了,但是这个文件真的消失了吗?

我们打开test-hard文件,发现文件内容依旧在,inode号也没有消失,依旧是原来的inode号,所以硬链接的作用已经显现出来了。

没错,硬链接的应用场景就是对文件进行备份!!!

有了对硬链接的初步理解后,我们来看:

在文件前面的属性中,有一个值,当我们创建软链接文件时test.c的这个值并没有发生变化,但是当我们进行硬链接后,这个值就从1变为了2,那么此时想必大家对这个值很好奇,它是什么呢?

不废话,答案就是硬链接数,这个数统计的就是有几个文件名指向我们特定inode,这个作用想必大家都很熟悉,就是我们常说的引用计数啊,这个值只要不为0,那么这个文件就存在。

那么此时就有一个问题:从第一幅图中我们也能发现,我们创建的普通文件,这个硬链接数是1,但是我们创建的hello目录文件,这个硬链接数却是2,为什么呢?我们来看:

因为这个目录中还有" . "文件啊,这个文件就是目录文件的硬链接文件,它们文件名不同,但是指向的都是一个inode,所以此时的硬链接数就为2,我们再来看:

此时在hello目录文件中再新建一个file目录文件,为什么此时hello的硬链接数又变为3了呢?

别忘了,不止有" . "文件,还有" .. "文件呢,file目录中的" .. "文件指向的也是同一个inode,所以此时又会在2的基础上再加1,变为3。

但是在用户层面,是不允许对目录设置硬连接的!!!,我们来看:

可以看到,这里不允许给hello进行硬链接,但是此时有人就会有疑问了:那上面的" . "和" .. "文件不就是hello的硬链接吗?linux自己可以进行硬链接,却不允许用户进行硬链接吗?

你猜对了,linux自己可以进行硬链接,但就是不让用户对目录文件进行硬链接,那为什么要这样做呢?我们来看:

如上图所示,如果路径中的file目录是mon目录的硬链接,那么此时我们想要访问file目录下的某个文件,当进行路径解析时打开file目录,发现打开的是mon目录,那么又会从mon解析到file,然后就会重复上述的过程,这样就会造成路径环路问题!!!

linux系统自己硬链接的" . "和" .. "不会造成路径环路问题吗

理论上是会的,但你猜它俩为什么叫" . "和" .. "?

就是因为当进行路径解析时会忽略这两个文件,这样就不会造成路径环路问题了。

以上就是深入理解Ext2:Linux文件系统的基石与它的设计哲学的全部内容。

http://www.dtcms.com/a/585123.html

相关文章:

  • 泉州网站的建设html网页制作我的家乡
  • PHP 魔术常量
  • 【iOS】音频与视频播放
  • php通过身份证号码计算年龄
  • 基于PHP+Vue+小程序快递比价寄件系统
  • Next.js、NestJS、Nuxt.js 是 **Node.js 生态中针对不同场景的框架**
  • 牛客周赛 Round 114 Java题解
  • PostgreSQL 中数据库、用户、对象关系、表、连接及管理概述
  • 樟树市城乡规划建设局网站爱站攻略
  • Gitblit 迁移指南
  • Git分支管理核心:git fetch与git checkout创建分支完全指南
  • LRU 缓存的设计与实现
  • Linux -- 线程互斥
  • 2.2 Transformer 架构详解:从理论到实践
  • 《Docker+New Relic+Jenkins:开发全链路的工具赋能指南》
  • 2025最新修复的豪门足球风云-修复验证问题-修复注册问题实现地注册-架设教程【豪门足球本地验证】
  • 【Linux笔记】网络部分——数据链路层mac-arp
  • 深圳网站设计公司专业吗外国网站分享代码
  • VB.Net 常用函数
  • 成都哪家做网站wordpress 主题课堂
  • 智慧随访管理系统源码,基于Java+Spring Boot+Vue的随访系统源码,支持二次开发,支持患者信息管理、多类型随访、三级回访机制、问卷模板
  • MQL5 自学路线图:从入门到实战
  • 告别 mysqldump 痛点!用 mydumper 实现 MySQL 高效备份与恢复
  • 【Java 并发编程】线程创建 6 种方式:Thread/Runnable/Callable 核心类全解析
  • Lombok.jar bug
  • 隐藏在字符编码中的陷阱:深入剖析宽字节注入
  • STM32外设学习--TIM定时器--编码器接口(程序)
  • iis 网站关闭陕西省住房和城乡建设厅
  • 【C++】多态与虚函数
  • 洛谷 P9847 [ICPC 2021 Nanjing R] Crystalfly