磁盘与库之间的结构关系
磁盘
说实话现在磁盘已经有点落后了,因为硬盘比它更优秀,容量更大价格也更低,但是我们还是从磁盘开始说起。
这东西就是磁盘和它的内部结构,我们主要来看它的内部结构
磁头所覆盖的范围就是磁盘的半径,当主轴转动的时候磁头臂就会摆动磁头读取数据
上面年轮一样的圆我们叫磁道,再把磁道等分以后叫扇区,同时磁盘可能也并不是就一个盘,可能有多个盘,而每一个盘还分为上下两面,每一面都有磁头能读取数据。
也正是因为一个磁盘所能存储的数据非常大,所以字节对于磁盘来说已经不能当最小单位了,所以它等分了磁道,把扇区作为最小单位,这样即使我们只想修改一个字节的内容,我们也需要找到对应的扇区,将整个扇区都加载到内存,然后修改完再写入磁盘里。
就拿这种三盘的磁盘举例, 我们简单的把相同面的盘归为一类,这样就把磁盘的正反两面分为两类,我们称为柱面。同时磁头的位置不论正反都是一样的,不会谁偏左谁偏右,那么问题来了,我们如何知道我们要找的数据是在哪里呢。
首先扇区其实被等分以后是有编号的,所以我们要找数据磁盘可以直接知道它在哪一个扇区存着,那么知道扇区之后就可以知道在哪一个柱面,而柱面就可以控制数据所在的磁头了,这样机械臂把磁盘旋转起来磁头就可以读取数据了。
柱面 Cylind 、磁头 Head 、 扇区 Sector 只要我们知道这三个信息,我们就可以在磁盘上找到数据,这种方法被称为CHS定址。
当我们把磁道拉直,这不就是个数组吗,那么我定位扇区只要通过下标就可以了,这被称为LBA 。它不需要CHS的三个参数。那么,它们之间就有了转化的关系。
首先我们先理解,柱面磁头扇区其实就是一个三维数组的属性,但是再底层数据都是线性存储的,所以实际上找起数据来是通过 / % 来找的,所以CHS转成LBA就是把柱面和磁头的数量加上当前扇区的下标得出来的,LBA转CHS就是逆着来一套。
文件系统
通过上面读磁盘的理解,我们可以抽象的把它看成这样。
而到了文件系统里,它访问磁盘它就觉得扇区的读写效率太低了,所以它又把多个扇区组合成了一个新的最小单位 “块” ,它的大小一般是4kb,大概是连续的八个扇区,操作系统也允许我们根据需要手动调整块的大小,总之默认一般是4kb。这也是为了对所谓的磁盘分区做考虑,因为分区的最小单位是柱面,所以4kb的大小是前人多次试验的适用值。
假设分区分的是三盘900G的磁盘,那么我们一个盘分300G(区),我们只需要有管理300G的办法就行,然后把它复制黏贴给其他600G,而管理好这300G我们可以再分成管理30G(组)的方法,这样分置下去就可以减轻操作系统的压力了。
所以接下来我们就谈谈 "组" 的结构构成。
Data Blocks & Inode Table
文件 = 内容 + 属性 而内容就存放在 Data Blocks 里,以4kb大小的块为单位存放。属性则存放在 Inode Table 里,这个inde表大小固定为128字节,它是一个结构体,以固定的结构体成员记录这个文件的信息,但是它记录的内容并不包括文件名,因为文件名会导致它的大小有浮动,而它固定128字节的特性决定了它不管文件名。
inode虽然说是结构体,但它在底层还是以数据存储的方式记录的,所以它也是用块为单位存储,块4kb = 4096字节 而inode结构体128字节 4096 / 128 = 32 也就是说一个数据块能存储32个inode。
而读取的时候往往是一个块一次性读取的,也就是说它一次就读入32个inode了,又因为连续的空间里数据关联性较强,所以大大提升了效率。
//inode是会被编号的
ls -li //可以查看文件的inode编号
Block Bitmap & Inode Bitmap
这两位我们看名字就知道是位图了,假设我们的 Data Blocks 有10w个块存储数据,那Block Bitmap就会有10w+的比特位存储Data Blocks的使用情况,100000 / 8 = 12500字节,
12500 / 4096 = 3 块,最多四个块就可以记录我们Data Blocks的使用情况了,Inode Bitmap也是同理只会更少。
所以我申请空间就是把Block Bitmap对应的比特位置1,删除就是置0,而置0则代表对应块的空间可以被覆盖,所以说删除的效率比申请快,并且删除并不是真的删除了,至少当时是这样,只是它可以被覆盖了。
GDT & Super Block
GDT我们称它为块组描述符,它的作用就是记录前面四位从哪里开始到哪里结束的区间以及这个组内的一些关键属性。
Super Block叫超级块,它本身就代表了文件系统,和GDT管理组内成员不同,它并不是直接管理组内成员,它所管理的是这一整个分区,和我们当前的组关系其实不大,它所管理的是这一个分区内有多少组,这些组的使用情况,以及这些分组的位置区间等信息等。
那么肯定会有疑问,既然超级块和组的关联不大,那么是不是每一个组都有呢,不是的,虽然不是每个组都有但也不是只有一个,因为如果GDT出问题只是影响一个组,但是Super Block出问题就是一个分区的问题,所以虽然不至于每一个组都给一个,但是也不能只有一个,所以它会在多个组里存在,也就是备份,一旦出问题,系统就会有其他完好的Super Block的信息来恢复损坏的Super Block。
组总结
在磁盘被分区之后,每个分区又有多个分组,分组之下又有多个inode和数据块,这些inode和数据块都被编号了,且在一个分区里编号是唯一的,也就是说如果组1有一个编号1,那么在这个分区里就不会再出现第二个编号1。但是如果出了分区,别的分区是可以有的,所以node和数据块不能跨分区。
而上升到操作系统使用时,操作系统必然要对分区进行管理,那么操作系统就会把各个分区的Super Block加载到内存,然后以链表的形式进行管理,这些链表又会对每个分组的GDT进行管理,最后操作系统对文件的管理就变成了对链表的增删查改。
那么我们回归最初始的问题,文件名存在哪里。
文件名存在目录里,而目录它的结构也是inode,目录的属性我们大概知道就是存储这个目录的文件信息,那么目录的内容存什么呢,存的是inode和文件名的映射关系,所以每次我们使用文件名,其实就是通过路径找到当前目录的inode和文件名的映射关系,然后通过映射关系(文件名->inode)然后使用inode来对文件进行查找,这也是为什么文件名不能重复。
文件目录的路径
OK,上面的结构我们理清楚了,但是又衍生出来了一个新问题,咱都是靠路径来找文件的吗?是的,其实根本就没有什么目录,这些目录都是以多叉树的方式存在的,而路径就像是最短路线,可以直接帮我们找到,这些路径就存在PATH变量里,同时每个由一个叫做dentry的结构体维护,这个结构体记录了这个文件的路径信息,同时它也包含了下一层叶子节点,这些叶子结点或为空或为文件,但是一定存在。
这样就关联起来了,我们通过这棵多叉树,能一直找到我们文件所对应的inode,然后再通过inode进行管理,那么新的问题又来了,我们知道路径,知道inode,这样看似已经可以找到对应的文件了,但是我们需要记住这只是一个分区,在别的分区可以拥有同样路径和inode的文件,那么我们如何来区分是哪个分区呢?
挂载
实际上在Super Block对分区初始化以后,我们还是不能直接使用这个分区,我们需要用目录和这个分区进行绑定的方式来使用这个分区的存储空间,所以上面的问题迎刃而解了,我一开始使用我就知道我是哪个分区的。
理解了挂载之后,我们再来回头看看inode,前面说的inode存了很多信息,这当然也包括了这些inode所对应的数据块,由一个数组构成,可是inode的大小是固定的,数据太多了咋办,所以inode对数据的重要性做了区分,重要的常用到的它就直接存在块里,次一点的就在块里存它们的地址,再次一点的就地址里存地址,然后再存内容,以此类推这样就大大提高了存储的空间。