基础IO 九

111 阅读9分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情

一、理解文件系统

💦 inode

  • 500G 的磁盘空间抽象成每个元素是 512byte 的数组,那样非常大,不易管理,所以操作系统还要对这 500G 的数组进行拆分,比如这里拆分成了 100G、100G、150G、150G,所以这里只要管理好了第一个 100G 的空间,然后把管理的方法复制到其它空间,其它的空间也能被管理好。这里我们把拆分的过程叫做分区,这也就是我们的电脑上为什么会有 C 盘、D 盘、E 盘。至此我们仅仅是对空间进行划分,要把空间管理好,还需要写入相关的管理数据,比如把中国 960 万平方公里,划分了不同大小的省份,你要管理好一个省,我们不考虑地质差异等因素,只要一个领导、一个团队他们把一个省管理好了,那么他们的管理方法就可以复制到其它省,同样的,刚刚我们分区的工作只是把中国划分成不同的省份,接下来我们还要分配每个省的省长、省中每个市的市长、市中每个镇的镇长等,以此来管理一个省。这里我们把分配的过程叫做格式化过程,所谓的格式化在计算机中就是写入文件系统,也就是说我们要把文件系统写入某个分区中,这个文件系统的核心包括数据 + 方法,数据就类似这个省有多少人口、粮食等,方法就类似这个省有生育政策、耕种政策等。同样文件系统包含的数据就是文件系统的类型等,方法就是操作各种文件的方法。

    当然不同的分区当然可以使用不同的文件系统,Linux 下就使用五六种不同的文件系统,Linux 可以支持多种文件系统,包括 Ext2、Ext3、fs、usb-fs、sysfs、proc。这就好比,各个省份需要因地制宜的分配不同的团队。我们今天谈的都是 Ext 系列的文件系统,另外也不谈其它的文件系统如何,我们就认为磁盘上不同分区的文件系统是一样的。

    因为一个省也很大,为了更好的管理,还要分配市长、镇长等,同样的分区后的 100G 空间还要再划分,比如这里划分了 10 组 10G 的空间,然后把它看作成一个一个的块组(Block group),一个块组中又有多个 4kb 空间,而磁盘存储是有块基本单位的,文件系统认为一块是 4kb,我们只要把一个块组管好,整个文件系统内的块组就能管好,所以问题又转换为怎么把这 10G 的空间管好,所以接下来划分的才是文件系统写入的相关细节,也是我们要研究的,这个区域的信息,大家都有,可能略有差异。

    在这里插入图片描述

    这里 Linux 文件系统以 Ext 系列的为话题, 因为不同的文件系统可能略有差异。在块组之前,有一个 Boot Block,它是启动的意思,一般一个磁盘的 0 号分区的 0 号块组上的第一扇区存储着一些启动信息,这里不是重点。这里我们重点谈一个块组细分下来的后四个信息:

    A) Super Block 是文件系统的核心结构,用于描述文件系统的属性,包括文件系统名、文件系统版本、块组中有哪些使用和未使用,一般计算机启动时,Super Block 会被加载到操作系统,其中每一块组好像都有一个 Super Block,但实际可能 10 个块组中只有两三个有 Super Block。

    B) Group Descriptor Table 是块组描述符表,Super Block 描述的是整个块组相关的信息,这里描述的是一组的信息,每一个块组都必需要有一个 Group Descriptor Table。

    C) 我们说过文件 = 内容 + 属性。这里的内容和属性采用分离存储,属性放在 inode Table 中。一个组中可以放多少个 inode 是一定的,基本上,一个文件或目录一个 inode,inode 是一个文件的所有属性集合,属性也是数据,也要占用空间,所以即便是一个空文件,它也要占用空间,这里的属性集合包含文件权限、大小等,但不包含文件名,这个下面再说。

    内容放在 Date blocks 中。比如这里的块组是 10G,那么inode Table 占 1G,Date blocks 占了 8G, 一个 inode 是 512byte,粗略的算一下,1G 大概 42 亿多字节,除以 512 大概也有几千万,所以这样一个块组能保存几千万文件的 inode 信息,这里 inode Table 和 Data blocks 的划分可能会出现你用完了,我没用完,你没用完了,我用完了的情况,这种情况并没有有效的方法解决。

    Date blocks 相当于一个数据块集合,以 4k 为单位,对应的数据块属于哪些文件,是由 Data Blocks 和 inode Table 维护的。如下图,inode Table 包含了若干大小相同的块,这些块有不同的编号,对应就是文件的属性,Data blocks 也包含了若干大小相同的块,这些块也有不同的编号,对应就是文件的内容。此时新建文件或目录,就给文件申请 1 号 inode,并把文件的各种属性写入到 1 号 inode,1 号 inode 中包含了一个数组 block b[32],比如 1 号 inode 需要 2 个数据块,所以 [0] = 2,[1] = 3,所以 1 号 inode 就可以找到对应的数据块。换言之,要在磁盘上查找一个文件,我们只需要知道这个文件的 inode 是多少,至此,我们知道真正标识文件的不是文件名,而是文件的 inode 编号。既然 inocde 大小是确定的,万一文件是 10 个 T,此时数据块就不够了,文件系统的处理策略是数据块不仅可以保存数据的内容,还可以保存其它数据块的编号,它类似于 b+ 树。换言之,对于保存较大的文件,可能就需要多级索引的形式。

    在这里插入图片描述

    这里 ls - i 就可以查看文件或目录对应的 inode 了,可以看到这里的 inode 并不是连续申请的,它依然能看到文件名,是因为我们需要识别。

    在这里插入图片描述

    如何理解目录 ❓

    我们知道程序员定位一个文件,是通过绝对路径或相对路径定位的,但不管是绝对路径还是相对路径最终一定是要有一个目录。目录当然是一个文件,也有独立的 inode,也有自己的数据块,目录中的 block 数组能找到对应的数据块,目录的数据块维护的是文件名和 inode 的映射关系。换言之,在目录下创建文件时,除了给文件申请 inode、数据块之外,还要把文件名和申请创建成功之后文件的 inode 编号写到目录的数据块中。所以现在就能理解为什么大多数操作系统下同一个目录中不允许存在同名文件。所以只要我们找到了目录就可以找到文件名,根据映射然后可以找到文件 inode,通过 inode 读取文件的属性,也可以通过 inode 中的数组读取文件的内容。所以 ls -l 时就可以读到文件的属性信息,它是在当前目录对应的 inode 下找到对应数据块中文件名和文件名映射的 inode,再去找对应文件的 inode,此时就看到文件的属性了。所以 echo "hello world" > file.txt 是先启动进程,这个进程当然知道自己所在的目录,所以它就可以拿着 file.txt 文件名找它对应的 inode,把数据追加到对应的数据块中。所以我们说 inode 不存储文件名,只是往目录的数据块中写入文件名和文件对应的 inode。

    D) Block Bitmap 和 inode Bitmap 是位图,就是用比特位 0 1 来表示。Block Bitmap 用来标识数据块的使用情况,inode Bitmap 用来标识 inode 的使用情况,每个比特位都对应一个块。换言之,当你新建文件时,它并不是遍历 inode 区域,这样太慢了,它只需要在系统启动时,将 Blok Bitmap 和 inode Bitmap 预加载到系统中,你要新建文件,就把 inode Bitmap 的比特位由 0 至 1,文件需要多少数据块,就把 Block Bitmap 的比特位由 0 至 1。所以我们可以通过位图,可以快速的完成 inode 的申请和释放,同时也能确认当前磁盘的使用情况。但是位图依然还是需要去遍历哪些使用和未使用,以及做位操作等,所以这里通过 Group Descriptor Table 来管理。

    如何理解删除文件 ❓

    之前我们说过,计算机中删除一个文件并不是真正的删除,而是把那块空间标识为无效,就像房子上的 “ 拆 ” 字。而现在理解的是不用把 inode 属性清空,不用把 inode 对应的数据块清空,只要把两个位图中对应的比特位由 1 到 0,再把所在的目录下中的对应的映射关系去掉,此时空间就是无效的,下一次再新建文件时,就可以直接把无效的空间覆盖。

    按上面这样说,删除后的文件当然可以恢复,Windows 下的回收站就是一个目录,当你删除时就是把文件移动到回收站目录下,移动时就是把其它目录下数据块中的映射关系移动到回收站目录下的数据块中。Windows 下就算把回收站的内容删除也是能恢复的,Linux 下,如果要恢复删除的文件是有一些恢复工具的,但有可能在恢复过程中,创建各种临时文件,可能就会把想恢复的文件的信息覆盖掉,你想自己恢复删除的文件,就需要更深入的了解文件系统原理。