深入理解文件系统(二)

📅 2026/7/10 23:17:36
深入理解文件系统(二)
个人主页:小则又沐风个人专栏:数据结构竞赛专栏C语言CLinux座右铭悟已往之不谏知来者之可追目录前言块的概念分区的概念inode : 文件属性的身份证相关的inode的属性ext2⽂件系统Super Block超级块块组描述符(Group Descriptor Table)块位图Block Bitmapinode位图Inode Bitmapi节点表(Inode Table)Data Block简单阐述文件的创建过程inode和datablock映射路径解析(包括路径缓存)挂载前言在上一篇的学习中,我们简单的了解了文件是怎么在磁盘中存储的,我们简单的知道了,文件的存储逻辑结构就是一个三维数组.具体名称就是LBA地址.我们在之前只是了解一下硬件的知识,今天我们来了解一下文件系统中的软件的部分块的概念磁盘读写最小的单位就是扇区,在通常情况下,一个扇区的大小是512字节的,可以知道的是,如果在读取信息的时候是按照一个一个扇区进行读取的话,这样的效率难免太过于低下,所以这时候就引入了块的概念在实际的情况下,操作系统在读取硬盘数据的时候,并不是按照一个一个扇区读取的方式进行读取的,而是采取了更加高效的方法------多个扇区一起读取.(在通常的情况下,读取的时候是8个扇区进行读取的,也就是4kb)这时候我们就称8个扇区为一个块因为在之前的讲解中,我们认识到的是我们可以把磁盘看作一个长长的一维数组,所以我们得到一个LBA地址,是可以计算出来这个地址所对应的块区的LBA//8 就是对应的块区知道块号LAB块号*8n.(n是块内第⼏个扇区)分区的概念什么是分区呢????其实我们在使用我们的电脑的时候我们就已经使用了分区了因为电脑中的C,D,E,盘就是分区但是在Linux下是怎么进行分区的呢?在磁盘的物理结构中,有一个天然的结构,他是由多个扇区组成的,他就是柱面因为一个柱面是有多个扇区的,也可以利用柱面来区分扇区,所以在通常的情况下,使用柱面作为分区的最小的单位,inode : 文件属性的身份证为什么称作inode时文件属性的身份证呢?在我们的认知中文件的组成是:文件属性文件内容那么显而易见的就是inode是和文件的属性相关的相关的inode的属性一个文件对应一个唯一的inodeinode中没有存储文件的文件名但是平时在使用文件的时候我们都是用的是文件名,而不是inode这是因为文件名和inode建立了连接(建立连接的媒介是目录---目录也是文件)inode的⼤⼩⼀般是128字节或者256我们后⾯统⼀128字节任何⽂件的内容⼤⼩可以不同但是属性⼤⼩⼀定是相同的来查看一下inodestruct ext2_inode { __le16 i_mode; // 文件权限如0644 __le16 i_uid; // 所有者ID __le32 i_size; // 文件大小字节 __le32 i_atime; // 最后访问时间 __le32 i_mtime; // 最后修改时间内容 __le32 i_ctime; // 最后变更时间属性 __le16 i_gid; // 所属组ID __le16 i_links_count; // 硬链接数 __le32 i_blocks; // 占用块数 __le32 i_block[15]; // 块映射指针12直接1一级间接1二级间接1三级间接 };怎么查看文件的inodels -li现在我们来简单的做一个总结读取的基本的单位是块.每一个分区中是包含着很多的块的块是怎么进行组织的?inode是文件属性的身份证,拥有一个文件的inode就可以知道他的相关的属性,那么具体是怎么使用这个inode来访问到文件的内容的?解释上述的疑问需要我们简单的了解一下Ext系列文件系统就以Ext2文件系统为例ext2⽂件系统在这个的文件系统中的核心就是块组Block Group结构.具体的就是在一个分区中,我们把相同数量的块分为成一个个的组,然后对一个个的组进行管理的话,管理的成本就会相对的降低,然后将这个管理的方式推广到一个个的分区的话,就实现了对文件系统的管理在一个块组中包含的结构都有什么呢?Super Block超级块超级块是记录在这个分区中的信息的比如:块的大小inode的大小inode空闲的数量块的闲置数量,以及块的总数量最近挂载时间、最近写入时间、最近磁盘检查时间在每一个块组中,都包含了一个超级块的结构,看起来是冗余的,但是这是在对超级块的信息进行备份.块组描述符(Group Descriptor Table)在这个结构中存储的就是有关当前块组的信息了,关键的信息就是当前的块组的块位图的起始地址,inode位图的起始地址,inode表的起始地址,空闲的inode和数据块还有多少个等等一个块组就有一个相应的块组描述符块组0: | 超级块 | GDT | 块位图 | inode位图 | inode表 | 数据块 | 块组1: | 超级块备份 | GDT备份 | 块位图 | inode位图 | inode表 | 数据块 | 块组3: | 超级块备份 | GDT备份 | 块位图 | inode位图 | inode表 | 数据块 | 块组5: | 超级块备份 | GDT备份 | 块位图 | inode位图 | inode表 | 数据块 | 块组7: | 超级块备份 | GDT备份 | 块位图 | inode位图 | inode表 | 数据块 | ...块位图Block Bitmap在这里面就是记录那些块是没有进行使用,那些块被使用了具体的实现我们可以联想一下在进程调度中的位图每一个块是有一个对应的比特位的,这个比特位是0就是空闲,1就是被占用了inode位图Inode Bitmap相同的是这个inode位图是记录哪一个的inode号是空闲或者是使用的i节点表(Inode Table)在前面的认识中我们知道的是inode实际上就是一个结构体,在这个表中存储的就是一个个的inode结构体,这一个个的结构体就是一个个的文件的属性.我们可以依靠我们在GDP中得到的Inode Table的起始地址,然后拿着inode号也就是偏移号.访问我们inode对应文件的属性文件的属性中是包含着文件数据存储的位置的,我们可以拿着这个数据的地址,就可以对文件内容进行访问了Data Block这个是块组中的最大的部分了,在这个区域中存储的是文件的内容对于普通文件是存储文件的内容对于目录文件存储的就是文件名和inode的对应关系简单阐述文件的创建过程依靠我们上述的知识,我们就来实战一下(一个文件的创建过程)首先就是给文件进行分配inode号通过查看一个分区中的超级块查看是否有空余的inode和块在合适的分区中,遍历inode位图,找出第一个空闲的inode号,将这个inode号置为1然后在inode表中的inode结构体中填写将要创建文件的属性(文件的权限,拥有者,创建时间等等)然后在属性表中文件所占用的块的数目进行初始化为0遍历块位图找到空闲的块,将文件的内容写入到块中,并在位图置为1在inode结构体中建立块映射,更新占用块的个数在所处的目录中建立文件名和inode的映射这样的看起来文件存储的时候并不是连续的,是依靠inode这个结构体实现对不连续的数据块变成有序的,那么我们删除一个文件就是把一个文件属性中对于文件的数据块的映射关系进行了删除,然后我们对相应的数据块对应的位图中的位置置为0inode和datablock映射在这个图中我们看到的是inode的映射的方式是有三种的分别就是图中的直接映射一级映射二级映射三级映射对于较小的文件,文件的所需要的数据块较少,可以直接进行映射,这样会更加的高效但是对于大文件的话,就需要对文件需要的数据块的数量来进行挑选相应的映射的方式了就以一级映射为例子,首先第一次的映射我们得到的是一个类似目录的结构,在进行映射的话就和直接映射是一样的了,这样的操作就可以关联到更多的数据块了路径解析(包括路径缓存)平常的使用中,我们使用的是文件名,并不是inode,对文件名和inode进行映射的媒介是目录假设我们现在所位于的路径是/home/study/7.9/text.cc我们是怎么找到text.cc的inode呢?答案就是在目录7.9中查找相关的映射但是问题是我们怎么找到7.9这个目录的内容呢?也就是说我们想要找到当前目录中文件名和inode的对应关系的,我们需要找到上级目录中此目录的inode.这时候我们就明白了我们再平常的使用中我们必须带上路径就是因为这个.因为根目录的inode号是固定的所以我们在根目录中找到了home的inode号,在home中找到了study的inode号........这样递归的查找但是这样我们每次打开一个文件就需要这样不停的查找的话,难免会有点浪费资源了,所以操作系统中会记录我们在之前查找的历史,并建立一个缓存所以我们在日常使用的时候我们会发现第一次打开文件的时候会相对的慢一点,但是之后再次打开的时候就会快了,这是因为记录下来这个路径的缓存了挂载我们现在的知识储备是可以支持我们在一个分区中横行霸道的,但是有一个问题就是我们怎么知道所处于的分区呢???因为在每一个分区中inode号是唯一的,所以我们不能拿着正确的inode号走错了分区但是因为对目录进行了挂载,这样的话我们的根目录会在对应的分区建立起联系了,这时候再去用inode的话就会对应到相应分区的了