个人主页:Lei宝啊
愿所有美好如期而遇
磁盘
磁盘的机械构成
磁盘的物理存储
每个磁盘的盘面,磁头,扇面,扇区都有唯一的编号,同时,一个扇区的大小是512字节(扇区的大小也有4KB的,或者其他大小,我们这里介绍的是512字节的),磁盘IO的基本单位是扇区。
每个扇区都是由一个个我们肉眼不可见的小磁块构成,我们平时理解的比特位的1或0,在这个小磁块上的表现就是磁性,南极或者北极,由此表示。如果我们想寻找磁盘上的某一个扇区,首先应该确定是哪个磁头,也就是在哪个盘片上,盘片正反面都是可以存储数据的;接着就是确定在哪一个磁道(柱面)上;最后确定在哪个扇区。
磁盘的逻辑存储
磁带中一圈一圈的存储着数据,如果我们把它扯出来,可以拉成一条直线。
我们可以想象一下,将磁盘上一圈圈的磁道也拉成一条直线,那么我们就可以抽象出,在逻辑上,它存储数据就是线性的。
我们可以根据这个线性地址,通过一系列除法和求余运算计算出CHS地址,线性地址到CHS是由磁盘自己计算转化的。(CHS定位法,cylinder磁道,head磁头/盘面,sector扇区)
所以我们其实也可以理解,文件不就是由多个扇区承载的数据吗?我们可以通过线性地址转化成CHS地址,找到一个扇区,那么我们当然也可以找到多个扇区,也就可以找到文件。
操作系统认为,内存和磁盘一次IO 512个字节太小了,所以他规定出一次IO的基本单位为4KB。
操作系统为了更好的管理磁盘,对磁盘进行了分区,也就是说,我管理好一个分区这100GB的空间,那么其他的分区我是不是也可以管理好,那么同理,为了更好的管理这100GB空间,对他进行分组,假如说一个组有10GB,那么操作系统管理好这10GB,是不是其他组也可以管理好?
这里我们先要明白:
Linux磁盘文件特性:文件 = 内容 + 属性,而我们这里要说的是,内容和属性是分开存储的,内容的大小是可变的,属性的大小是固定的,同时文件名不属于属性,系统中标识一个文件用的不是文件名,而是inode。
inode Table && inode Bitmap
什么是inode?struct inode {文件属性,inode编号};这个结构体的大小固定是128个字节,一个数据块可以存储32个inode,inode Table里是操作系统分配的数据块,连续存储着inode,同时我们要知道整个分区内inode的数量是固定的,由操作系统分配。
那么,我们怎么知道在哪个位置存储了inode,同时哪个位置没有存储inode?这就需要位图,我们的inode Bitmap的bit位就表示第几个inode,这个inode是否空闲可用,我们通过遍历位图,可以找到最小的空闲的inode去存储我们新建的文件的属性,然后将这个位置bit位从0改为1,同时记录下这个bit位是第几个bit位。
Data Blocks && Block Bitmap
Data Blocks里就是操作系统分配的只存储文件内容的数据块,每个数据块都有自己的LBA地址,大小为4KB,inode里有一个属性是int block[15],存放着文件内容的数据块的LBA地址,我们可以根据他来将文件的内容和属性对应起来,但是block看起来只能存储60KB的内容?文件不是可以很大吗?这个数组有15个下标,从0~14,其中0~11存储的LBA地址对应的数据块里,存放的就是文件内容,而下标为12,13的LBA地址对应的数据块里,存放的不是文件内容,他存放的仍然是LBA地址,而这个地址再去索引找到的数据块里,存放着的才是文件内容,我们也将这个过程叫做二级索引,但是即使这样,文件的内容依然不是很大,也就扩大了1024倍,也就是60KB*1024,那么最后一个下标14,他是三级索引,这样就再次扩大了文件大小,我们可以依据这样去存放更大的文件。Block Bitmap的bit位就表示哪个数据块被占用,哪个数据块没有被占用。
Group Descriptor Table && super Block
Group Descriptor Table,块组描述符,记录着自己这个分组的各种信息,比如分组的起始inode编号,这个分组有多少个inode,inode Bitmap的使用情况,下一个inode将要分配到哪里等等,我们也将其叫做GDT,GDT是每个分组都有的,如果一个组的GDT损坏了,那么这个组的所有信息都无法找到,文件全部丢失。
super Block,超级块,存放文件系统本身的结构信息。记录着整个分区的各种信息,比如block和inode的总量,未使用的block和inode数量,最近一次写入数据的时间等等,这个一但损坏了,整个分区就挂了,整个文件系统结构就被破坏了。那么这个块是所有分组都有的吗?不是,系统会有选择性地其中几个分组,这样如果一个分组中的超级块信息被破坏,还可以根据其他分组中的超级块进行恢复,所以,我们也应该知道,这几个分组中超级块的数据是同步的。
这里我们还要介绍一个东西:
我们选中一个盘右击鼠标,可以看到一个选项叫做格式化,这是做什么的呢?
我们上面介绍的文件管理方式,是分区一创建好就有的吗?当然不是,而是格式化写入了一个新的文件系统,我们上面介绍的文件系统是ext*(2),不同的分区可以有不同的文件系统,而super block就是存储着文件系统的信息,多个分区也就有多个super block,操作系统将他们组织起来,那么,对于分区的管理也就变成了对super block的增删查改。
几个点
到这里,我们需要提出来几个点。
第一点
首先,inode里存着inode编号,什么是inode编号?在整个分区中,他唯一标识着一个文件,有了inode编号,我们就可以找到这个文件,我们如何通过他来找到这个文件?
从Block group0开始,第一个分组中的inode Block,起始编号就是1,同时这个分组通过GDT记录着这个组中有多少inode,假如说有10000个inode,那么下一个组,他的inode起始编号就是10001,同时他的GDT中也记录着有多少个inode。
我们新建一个文件时,先在inode Bitmap中找到最小的空闲的inode,然后记下他的偏移量,直接去inode Block中索引到位置存储文件属性,然后根据分组的起始inode编号,加上在位图中的偏移量,就是这个文件的inode编号,然后将这个编号返回。举个例子,我们在inode Bitmap中假设找到第二十个位置是空闲的,偏移量就是20,然后当前分组的起始inode编号是1,那么这个新建的文件的inode编号就是20 + 1 = 21。
那么我们查找一个文件时,他的inode编号是3,那么我们通过第一个分组的起始编号和第二个分组的起始编号就可以确定这个文件是在哪个分组内的,找到分组后,通过他的编号在inode Bitmap中找到 inode的偏移量,然后在inode Block中找到inode,inode中找到文件的属性以及block[15],通过他再去Data Blocks中找到这个文件对应的内容。
第二点
我们还需要知道的是,inode Block和Data Blocks中数据块的数量是操作系统分配的,会出现两种情况,一种是inode Block空间用完,Data Blocks还有剩余,另一种就是反过来。
第三点
再一个,我们如何通过inode寻找文件?我们诚然可以将文件的属性和内容对应起来,但是真说到要找文件,该如何寻找?
通过inode编号去寻找,可是去哪里找inode编号?直接看到吗?我们使用者关心过吗,没有,使用者关心的是文件名,而系统关心的是inode。
在linux中,我们新建一个文件,系统要做什么?
linux中,一个文件对应着唯一的一个inode,每一个inode都有自己唯一的inode编号(inode的设置是以分区为单位的,不可以跨区),当我们新建一个文件时,系统会存储好他的内容和属性,同时为他分配一个inode,而文件名是不属于文件属性的。
但是我们知道,任何一个普通文件,都一定在目录中,同时我们也知道,目录也是文件,也是inode+目录的内容,那么目录的内容保存什么呢?保存的是文件名和inode编号的映射关系!
那么我们之前给目录加的rwx权限,对文件的增删查改怎么理解呢?
首先是写权限,我们创建文件时,由用户输入的文件名,以及系统要为其分配的inode和返回的inode编号,要建立映射关系,然后存储在目录的内容当中,但是如果不给写权限,那么就写不进去,自然这个文件也就无法创建。
读权限,我们读文件的属性和内容,首先需要文件名去目录的内容中进行匹配找到自己的inode编号,然后确定分组,减去分组的起始inode编号,然后在inode Block中找到文件属性,再找到文件内容,但是如果没有读权限,那么也就无法知道inode编号,自然就读不出来文件的属性和内容了。
我们再谈到文件的删除,我们平时有没有注意到一个现象,就是拷贝文件花费的时间比删除花费的时间要长,比如所几个G的文件,我们删除就很快,而拷贝就要花些时间,这是为什么?
因为我们删除文件时,系统只是清空了文件对应的inode Bitmap,这样做,表明这个文件无效了,同时文件对应的Block Bitmap也做了清空。
所以我们删除文件时,如果没有写权限,也就无法删除目录内容中文件名和inode编号的映射关系,也就无法删除这个文件。
第四点
目录是不是文件呢?是的,那么目录的inode编号我去哪里找,是不是又要去找这个目录的父目录?父目录是不是文件?父目录的inode编号去哪里找?这样一直往上找,我们最终也就找到了根目录,而根目录的inode编号是确定的。
在我们打开一个目录时,操作系统会缓存下这个目录的路径,每打开一个目录,就缓存这个目录的路径,文件也是同理,当我们切换目录以及寻找文件时,就会快很多。
当我们打开一个目录,缓存下这个目录的路径,同时我们想要查看这个目录下的文件的属性和内容,那么就会根据缓存下的路径递归般的找到根目录,从根目录开始解析文件,找到一个个的inode,从而找到他们的属性和内容。
由此,我们可以得到一个结论:查找一个文件,在linux内核中,都要递归般的获得根目录,由根目录进行路径解析,得到inode。
但是,这和我们的分区有什么关系,文件不是存储在分区中吗?怎么和根目录扯上关系了?
一个被写入文件系统的分区,要想被linux操作系统使用,需要将这个分区“挂载”到对应的目录中!分区的访问,都是通过“挂载”的目录进行访问的,操作系统在打开目录和文件时,会在内存中缓存下来文件信息和文件路径信息,形成一个叫做dentry的数据结构,而文件系统的管理信息就保存在super block中,也是一个struct结构体,将写入文件系统的分区挂载到对应的目录下,其实就是两个数据结构的联系,这样我们再访问根目录下的文件时,就相当于访问了分区下的文件。
那么我又怎么知道我的文件在哪个分区下?首先,分区和挂载的目录是对应的,而我们要找一个文件,需要他的路径,当我们知道他的路径后,匹配他的路径前缀,就可以知道分区挂载的目录,从而找到这个分区。
第五点
在磁盘中,没有目录和路径这样的概念,只有文件的概念,包括我们平时说到的视频,图片什么的。但是因为我们每打开一个文件,需要对他们进行管理,于是在内存中才有了目录和路径这个概念。
那么我们对文件的理解也就是未被打开的磁盘文件,以及已经打开的内存文件。
当文件未被打开时,就是我们上面讲的磁盘对文件的存储和管理,当文件打开时,加载到内存,则会被进程管理,然后由进程对文件做出一系列增删查改的操作。
总结:
我们将磁盘从物理结构抽象成线性的逻辑结构,并将这个线性空间进行了分区,现在我们知道,访问分区其实就是访问它所挂载的目录,因为只有挂载后,才能访问这个分区。
而一个分区对文件的管理方式并不是分区后就有的,还需要写入文件系统,这个文件系统的管理信息存储在super block中。
为了对对一个分区更好的管理,又将分区进行了分组,每一个分组都按照写入的文件系统管理方式进行管理,我们上面讲解的是ext*(2)文件系统,GDT,inode block,data block,inode bitmap,data bitmap。
当我们希望新建一个文件时,touch + 新建文件名,这是个进程,而进程的路径是确定的,每当我们跑起来一个进程时,他的路径就确定了,我们也可以通过ls /proc/进程pid查看到cwd,也就是进程的路径,进程路径拼上这个文件名就是文件的路径,操作系统根据这个路径逆向递归找到分区挂载的目录,从而知道在哪个分区创建文件,然后在分组中的inode bitmap中遍历寻找最小的还未被使用的位置,由0改为1,记下偏移量,在inode block中对应位置填充新建文件的信息,如果这个文件还有内容,那么就再遍历data bitmap,再对应data block,将他的编号填入inode的数组中,做完这些,操作系统返回inode编号,就是文件所在分组的inode起始编号加上他在inode bitmap中的偏移量,将编号返回后,操作系统会将这个inode编号和文件名建立映射关系,将他写入到这个文件的父目录的data block中,这样我们的文件就创建好了。
当我们希望查找一个文件时,ls + 文件名,使用进程的路径拼上这个文件名,就是文件的路径,然后操作系统根据这个路径逆向递归找到分区挂载的目录,而这个目录的inode编号是确定的,从这里再开始对文件的解析,找到文件的inode,从而找到文件的父目录,再通过文件名在父目录内容中存储的文件名和inode编号映射关系,找到文件的inode编号,这个文件的分区我们也通过他的路径确定了,那么就去这个指定分区中根据这个编号去寻找,最终得到他的属性和内容。
我们在通过进程访问文件时,如果以绝对路径的方式去访问,那么我们就提供绝对路径去访问,如果我们以相对路径的方式,那么就会使用进程的cwd拼上我们对应的文件名,然后才到文件系统层面,文件系统再给他路径做解析。
路径其实都是由进程提供的,dentry也是通过进程缓存下来的,这就是为什么我们进程启动的时候,要把自己的路径保存一下,这就是为什么我们打开文件时,需要提供一个路径,我们系统里虽然有各个路径,在磁盘中有文件,有目录文件,但是它并不是以目录的形式组织的,所以我们需要提供路径,由操作系统去解释。另外,操作系统在没有解释之前,操作系统也可以预先把一部分路径提前预先加载进内存中,形成一个目录树,这就是为什么我们可以看到自己的根目录和其他目录,所以,当我们cd进入一些目录,如果这个目录已经缓存,就直接进入,如果没有就按照路径解析去进入,这也就是为什么我们自己进入到哪一个目录下,需要我们自己提供路径,不管是绝对还是相对,都需要我们去提供。
若有理解错误,欢迎各位指正。