从0开始linux(24)——文件(5)磁盘文件系统
欢迎来到博主的专栏:从0开始linux
博主ID:代码小豪
文章目录
- 分区
- 磁盘文件系统
- inode号
分区
我们现在的磁盘动辄512GB,1TB。甚至还有更大容量磁盘可用,那么我们的操作系统是如何管理磁盘空间的呢?
首先,系统会将一个磁盘进行分区,分为1个区,或者多个区,比如在windows系统当中,我们就可以将一个磁盘的空间分为C盘,D盘等。对于每个磁盘分区,都会有有一个磁盘文件系统进行管理。
而一个分区,又可以划分成多个组,如果我们的系统能将一个组管理好,那么其他组也能用相同的方式管理,我们就这种方法称为分治法。
那么对于组,linux是采用怎么样的文件管理方法呢?
文件数据=文件属性+文件内容
linux将文件的文件属性放在一个结构体当中,该结构体称为inode,一个inode的会保存一个文件的权限,inode号,文件大小等信息。我们可以使用ls -i看到文件对应的inode号。
每个文件都有一个独一无二的inode号,一个inode结构体,其大小为128字节。而根据我们上一篇文章所述,一个磁盘会分为很多个块,而每个块都有4kb的存储空间,因此在一个块中,我们可以存储32个inode结构。
一个分区分为多个组,一个组当中的数据分为好几个部分,其中inode会统一的放在一个表结构当中,这个表结构我们称为inode table。
而文件内的数据,则会在组中的另外一个区域放置,我们将其称为data block,这也就说,linux的文件属性,和文件内容,是分开管理的。至于属性和内容是如何连接在一起,我们在后续的文章当中再说。
比如我们当前分组当中有10000个文件,那么inode table就负责存储着10000个文件的inode结构,而着10000个文件的数据,则保存在data block当中
每个文件都有其对应的inode,而inode会保存在inode table当中,那么在inode table当中(我们可以将inode table视为一个数组),肯定就会有一下inode被使用,一些inode处于空闲状态,最明显的表现就是给文件分配inode号时,不能与其他的文件的inode号相同(inode号相同,可以视为数组元素的下标相同,这肯定不行)。那么linux解决这个问题的思路,就是设计了一个inode bitmap来管理这些inode的分配情况。
bitmap在linux系统当中的应用非常常见,其主要作用是监视资源的使用情况。bitmap其实就是一种数据结构,里面的成员不是某种类型的对象,而是二进制位。
如果这个bitmap指的是inode bitmap,那么其中的二进制1,表示该inode在inode table当中已被占用,二进制0,表示没有被占用。
比如我们现在创建了一个新的文件,此时文件系统就要创建一个inode,以保存该文件的属性,而inode是放在inode table当中,不能与已被使用的inode重复,否则就会将原有的inode覆盖,导致数据丢失,那么文件系统又要如何查看inode table当中那么inode没被使用呢?答案就是查看inode bitmap当中,哪个位的二进制数为0,比如上图当中inode bitmap的第5位为0,则说明inode table当中的第五个inode还未被使用,因此新建文件的inode就会被放在inode table的第五位当中,此时由于inode table的第五个inode被使用,inode bitmap的第五位也会置为1。
而文件数据会被放在磁盘当中的data block当中,data block是一个一个块(block)组成的,在前一篇文章当中提到了,在linux系统当中,一个block的大小是4kb。因此文件数据放在data block当中,会导致当中的block被用来存储数据,一些block被使用了,一些block没有被使用,新建文件的数据肯定是要放在未被使用的block当中。那么data block当中哪些block被使用了,哪些没有,文件系统又要如何知道呢?答案还是使用bitmap,block bitmap就是为了解决这种问题设计出来的。
原理和inode bitmap类似,如果一些block被使用了,在bitmap上的对应位置就会被置为1,没有被使用则会置为0。文件系统通过查看block bitmap,就能找到没有被使用的block,用于保存文件数据。
那么使用bitmap的好处是什么呢?我们假设一个组的inode table可以保存10000个inode,有100000个block可以被使用,那么inode bitmap只需要10000个bit就能体现出10000个inode的使用情况,大小仅为10000/8/1024≈1kb。而block bitmap的大小也就10kb左右,非常的节省空间。
group descripe table,又称为块组描述符,我们简称为GDT,用于记录块组空间的使用情况,比如当前组的最大inode数量是多少,当前被分配的inode有多少,最大的block数量是多少,已被使用的block又有多少,通过GDT,文件系统可以轻松的查看到当前磁盘的使用情况。
super block,又称为超级块,它是存放文件系统本身的结构信息。记录的信息主要有:bolck 和 inode的总量,
未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的
时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个
文件系统结构就被破坏了。
super block记载的数据整个磁盘分区的数据,但是为什么记录磁盘数据的结构,要放在组当中呢?这是因为我们一个分区,可以被划分为多个组,而每个组的数据结构都是一样的,也就是说每个组都会保存一份super block,这些super block记录的的是磁盘分区的使用信息,因此它们都是一样的。但是super block是有可能损坏信息的,如果一个分区的super block损坏了,那么整个分区都无法正常使用了,只能通过格式化让super block变回正常才能继续使用,但是如果我们每个组都有一个super block的备份,如果某个组的block出现了损坏,将其他组的super block拷贝过来,该分区又能正常使用了。
磁盘文件系统
我们现在已经知道磁盘的每个组的情况了,磁盘文件系统就是根据每个组的信息,进行文件操作。博主将文件操作分为增、删、查、改四种,来阐述磁盘文件系统的操作原理。
首先,我们先来看看一个磁盘文件系统是如何查找到一个文件的。首先我们要清楚block和inode到底是怎样分布在一个组当中的。首先我们要知道,一个分区会分为多个组,而每个组的inode数量,和block数量是一定的。我们可以假设一个组最多有10000个inode,100000个block,那么这些数据都会记录在GDT当中。而我们要注意,这些inode号和block号,都是全局的。如何理解这个全局呢?即在一个分区当中,每个inode号不能重复,block号也不能重复,假设该分区有3个组,那么情况如下图:
begin表示起始,end表示结束,比如组1的inode号的区间就是[1,10000),block号的区间则是[1,100000)。这些数据都会保存在GDT当中。
如果磁盘文件系统想要找到一个inode号为12345的文件。那么文件系统就会在对应的分区,访问每个组的GDT,组1的GDT表示我组的inode号区间在[1,10000),因此这个文件不在组1,接着访问组2的GDT,发现组2的inode号区间在[10000,20000),因此磁盘文件系统确定,这个inode号为12345的文件,保存在组2当中。
接着,磁盘文件系统要查看组2的inode bitmap,以确保inode号对应的inode bitmap的位数为1,表示该文件存在,我们要注意,一个文件的inode号=该组inode起始位置+inode bitmap的位数
,比如inode号为12345,组2的起始inode为10000,那么inode bitmap的2345位,则是1。
磁盘文件系统发现bitmap的2345位为1,因此确定了inode号为12345的文件存在,接着则是在inode table当中找到对应的inode,我们在前面说了,文件的inode结构,负责保存的是文件信息,如果我们要查看文件数据,则是要找到文件对应的block。现在我们已经知道磁盘文件系统找到文件的inode的方式,那么我们接下来要怎么找到文件的数据,即文件的block呢?
inode会保存文件的属性,其中就包括文件的数据在哪些block号。这些block号我们可以视为是一种类似于指针的东西。
直接快指针,直接指向保存文件数据的block,我们都知道一个block的大小是4kb,因此12个直接块指针,可以保存48kb的数据,如果文件大于48kb,则需要用到一级间接块索引表指针。
而一级间接块则也是一个block,但是这个block不保存文件数据,而是保存文件的直接块指针。一个直接块指针的大小我们可以视为一个int类型(即4b),那么4kb的block,可以保存1024个4b的直接块指针,每一个直接块指针都能保存4kb的数据,因此一个一级简介快索引表指针,可以保存4kb×1024=4MB的数据。
如果文件大于4mb,那么我们就要用上二级间接块索引表指针了,这个二级间接块索引表指针,不保存文件数据,而是保存一级间接块索引表指针。由于一级间接块索引表指针的大小也是4b,因此一个二级间接索引块可以保存1024个一级索引表指针。而一个一级索引表指针可以保存的数据为4MB大小,因此一个二级间接块索引表指针的大小为4mb×1024=4GB。
以此类推,三级间接块索引表纸质可以保存的数据为4GB×1024=4TB,即linux当中,一个文件可允许最大容量为4tb,这么看还是很能存的,至少我们的家用pc完全不用担心文件数据太大无法保存,因为我的磁盘压根就没有这么大(haha)。
通过这些索引指针,磁盘文件系统就能通过文件inode,找到文件的block。这样就相当于查找到一个文件啦。
至于新增一个文件,无非就是创建一个inode,以及对应的block,这点已经说过了,而修改文件,则是先查找到文件的位置,然后将文件的数据读入到内存当中,在内存当中修改文件的数据,然后在写入到磁盘上,因此修改文件的最重要的操作,其实还是如何查找到文件。
那么删除一个文件是怎么做的呢?首先磁盘文件系统会拿到文件的inode号,找到对应的inode bitmap,将对应bit位的数置为0,而文件对应的inode和data block则置之不理,因为只要将inode bitmap置为0,其对应的inode table和data block就会进入空闲状态,下一次如果有文件用到了相同的inode号或者data block,就会将原有的数据覆盖。
由于文件被删除时,只修改了bitmap,inode和data block的数据是依然存在于磁盘当中,只要inode和data block没有发生覆盖。那么这个文件是可以恢复的,只需要将被删除的文件inode bitmap,重新置为1,那么该文件的inode和data block又会被重新启用,至于如何恢复文件,需要用到恢复文件的工具。windows系统当中其实也有类似的操作,我们delete掉一个文件,其实并没有将该文件完全删除,只要我们没有清空回收站,那么文件其实是可以从回收站里恢复的。
inode号
既然有了文件的inode号,就能让磁盘文件系统找到对应的inode,那么怎么拿到文件的inode号呢?根据前面的说法,inode号保存在文件的inode当中,有了inode号,就能找到文件的inode,但是如果要拿到inode号,又要先找到文件的inode,这样就变成了先有鸡还是先有蛋的问题,因此我们不能把解决问题的思路,放在文件本身上。
我们的文件都是放在哪呢?是放在目录文件下,而目录文件也是文件,文件=属性+内容,也就是有inode和data block,目录文件也不例外。那么目录文件的内容是什么呢?我们可以使用vim,查看目录文件的内容。
可以发现目录文件记录的是当前目录下的所有文件(无论是目录文件,还是普通文件)的信息。其中就包括这些文件对应的文件名,而文件名与inode号是有映射关系的(比如ls -i就能找到文件名与其映射的inode号)。
因此我们可以知道,如果我们想要查找一个文件,首先是操作系统会先访问该文件的目录,然后在目录中找到文件对应的inode号,这样不就拿到文件的inode号了吗?拿到了文件的inode号,我们就能找到文件的inode,通过inode,也就能找到文件的数据,即data block。
那么现在问题又来了,既然目录本身是文件,那么我们想要查看目录的内容(data block),是不是也要拿到目录的inode号?而目录的inode号保存在上级目录的数据当中,那么想要访问上级目录的数据,则要拿到上级目录inode号。如此循环,好像也不是一个好的解决办法。
我们以/home/lysb666/code为例。现在我们想要查看code的内容,首先要拿到code的inode号,而code的inode号保存在lysb666的内容当中,想要查看lysb666的内容,就要拿到lysb666的inode号,因此要访问home的内容,而想要访问home的内容,则要拿到home目录的inode号。
home的inode号,保存在/(根目录)的数据当中,但是根目录没有上级目录了,因此根目录的inode号,其实是固定的,而非随机分配,因此操作系统不需要访问任何目录文件,都能找到根目录的inode号。而其他文件的inode号都保存在上级目录当中,这也是为什么我们对文件进行操作时,需要告诉操作系统,文件所在的路径。实际上是通过路径,一步步的拿到文件的inode号,最后找到文件的数据。
但是这么做还是不够优雅。因为我们访问目录文件的inode,data block,实际上是让内存与磁盘做交互,而在前面博主就不止一次提到,内存与磁盘做交互的速度是很慢的,因此为了提升运行速度,操作系统会尽可能的减少内存与磁盘的交互。因此,我们访问一个文件时,其实并非每次都是像上面这样子操作的。因为操作系统会将我们打开的文件信息,缓存下来,因此我们最近打开的文件,就不需要再去访问磁盘,拿到inode号了,而是在内存当中拿到inode号,这样拿到文件inode的效率会得到提升。
我们经常或者最近打开的文件,其文件信息会保存在一个叫dentry的结构体当中,该结构体主要是保存下文件的文件名与inode号,而dentry会根据文件关系,形成一个多叉树的数据结构。这样我们下次访问某个文件时,会根据路径,在dentry树上找文件,这样就不用通过频繁的访问磁盘,拿到文件的inode号了。