目录
3. 分组中的inode Table 与 Data blocks
4. 分组中的inode Bitmap 与 Block Bitmap
我们在基础IO中讲的都是进程与被打开文件之间的关系。那么,如果一个文件没有被打开,它又该如何被OS管理呢?
我们首先要知道:当文件没有被打开的时候,它们只能静静的在磁盘上放着。
总的来说,由于磁盘上面有大量的文件,所以,它们其实也必须要被静态管理(也就是指把各个文件放在合适的位置)起来,方便我们随时打开。这部分工作其实就是由文件系统来做的。
其实,我们在基础IO里面讲的打开的文件和这里没有被打开的文件,它们都属于文件系统。
就像是你作为一个学生,你去教室上课了,是处于正在上课的状态,学校的老师肯定也要管理你。当你躺在宿舍打游戏或睡觉的时候,肯定也是需要被管理的吧。因为这些老师他们做的就是学生管理工作。所以说,无论你是躺着还是动着,你都要被管理。所以,这就叫做文件系统。
我们之前讲的虚拟文件系统(vfs),对应的其实是文件被打开的情况。而我们这里讲的其实就是文件静静躺着的情况。
那既然我们这里要弄清楚文件是如何在磁盘中存储的,肯定要先认识一下磁盘吧。
所以说,我们这里先讲一下磁盘的物理结构,然后再讲磁盘的存储结构,再然后来讲磁盘的逻辑结构。
一、磁盘的物理结构
先来看看磁盘长什么样。
通过上面的图片,我们会看到:我们的磁盘里面会包含很多的盘片。并且,每一块盘片它的两个盘面都是光滑的。在每一面盘面处都会有一个磁头(也就是说,如果有五个盘片,就会有十个盘面,十个磁头,每一面都可以存数据)。在盘片之间有一个马达,当我们的磁盘被充电的时候,盘片是会逆时针高速旋转的,磁头也会来回的摆动。此外,磁盘里面其实还有自己的硬件电路。我们可以用硬件电路组成磁盘的伺服系统,然后可以给磁盘发二进制指令,让磁盘去定位或寻址某个特定的区域,进行从磁盘中读取数据。
此外,磁头和盘面其实是没有接触的。它们之间的距离是极小极小的。当磁盘高速旋转的时候,磁头就会漂浮起来。
【因为盘面和磁头距离很近,所以,磁盘就必须要防止抖动。如果磁盘在高速旋转的时候,磁盘不小心被抖了或者摔了一下,这个磁头就会上下摆动,一旦磁头上下摆动了,就有可能挨着这个盘面,就有可能将盘面刮花。而盘面上面存的都是二进制数据。所以,如果盘面被刮花了,就极有可能导致数据丢失。所以,我们的笔记本里面就很少装磁盘,而是固态硬盘。】
【大家可以去搜一搜具体的磁盘运作视频,可能会更清晰的了解它。】
二、磁盘的存储结构
先来几张图。
我们已经知道:我们的磁盘是有很多块盘片的,每块盘片又有两面。我们这里可以以一面为例。
当我们俯视的去看这一块盘面的时候,我们会发现:我们的磁面有很多的同心圆,它们其实就是磁道。
当磁盘在寻址的时候,基本单位不是 bit,也不是 Byte,而是扇区,扇区的大小一般为 512Byte。也就是说,当我们读取磁盘的时候,最基本也要读取 512Byte。
所以说,我们可以这样认为:磁盘的基本单位划分就必须划分为块状结构,每一个块状结构,在我们目前看来(即纯硬件层面上看来),它就应该是 512Byte。所以说,我们过去讲 Linux 下一切皆文件时说过:Linux下存在一种文件类型叫做块设备类型,即块设备文件。我们会发现:磁盘其实就是典型的块设备文件。
通过上面的图片我们也就清楚地知道了扇区所在的位置。假设我们以上面的一片扇区为例,它里面包含4个扇区,每一个扇区都是 512Byte。【虽然每个扇区大小不一样,但是我们可以控制它们各自的存储密度来存储这512Byte】
那么,在一个盘面面上,如何定位一个扇区呢?
我们只需先确认该扇区在哪个磁道,然后再确认它在对应的磁道的哪个扇区就可以了。
那么,我们怎么定位磁道以及怎么定位在磁道的哪个扇区呢?
其实,磁头来回摆动的时候,就是用来确认在哪一个磁道的。
盘片旋转的时候,就是在让磁头定位扇区。
所以,这就是为什么磁头要来回摆动,盘片要不断旋转的原因。
我们上面说的都是在一面的情况下。可是,我们的磁盘是有一摞的。
所以,我们就产生了柱面的概念。
在磁盘的多个盘片中,具有相同编号(即相同半径)的磁道会形成一个圆柱,这个圆柱就被称为磁盘的柱面。它其实等价于磁道。
而我们要知道:其实我们的磁头是连在一起的。所以,我们磁盘里的所有磁头它们其实是共进退的。也就是说,一个磁头到了哪一个磁道,所有磁头就都统一的到了那一个磁道。
所以说,我们的计算机在寻址的时候,它不是拿着一个磁头在一面上寻址的,而是拿着一摞磁头在所有面的磁道处去寻址。也就是说,我们的磁头在一起寻址的时候,磁头指向的位置就是我们柱面边界的位置。
那么,如何在磁盘中定位任何一个扇区呢?
首先,我们应该先定位在哪一个磁道(柱面)【磁道确认了,磁头就可以不动了】,然后再定位在哪一个磁头【目的是确定在哪一个盘面】,最后再定位在哪一个扇区。
而柱面对应的英语单词为 cylinder,磁头对应的英语单词为 head,扇区对应英语单词为 sector。
所以,磁盘中定位任何一个扇区,采用的硬件级别的定位方式为:CHS定位法。
那么,如何在磁盘中定位任何多个扇区呢?
如果我们需要定位多个扇区,我们可以给磁盘发送多个CHS的地址。然后让磁盘帮我们去找,找到之后帮我们读取出来。这样就实现了定位多个扇区。
三、磁盘的逻辑结构
我们这里继续以一面为例。
相信大家应该都见过磁带吧。
我们应该也知道这个磁带里面是会有一大圈条带的,它上面可以存储数据。
我们会发现:当磁带卷在一起的时候也是一种同心圆结构,而将其扯出来拉直之后,就变成线性结构的了。
我们就可以将磁带里的一圈圈同心圆类似于磁盘当中的一圈圈磁道。
【将卷起来的磁带视作磁盘的物理结构】
然后,假设我们可以将磁盘给拉直,我们就可以把磁盘逻辑抽象成线性结构的了。
【将拉直后的磁带视作磁盘的逻辑结构】
我们这里假设磁盘空间为500GB。那么,对应的该磁盘的线性结构也就是500GB。然后,我们假设这个磁盘一共有2片,包含4面,每个面有3个磁道,每个磁道里有一些扇区,每一个扇区可以存储 512Byte 的数据。我们就可以把整个磁盘从逻辑上看作为一个 sector arr[n] 数组。即:将磁盘视作从0号位置开始,有n个元素,每一个元素都是 512Byte 的数组。
这样就做到了:对磁盘进行管理被转化为了对数组进行管理。这就是先描述,再组织的思想。
假设我们这里有 1200 个元素,0-300 是属于第 0 面的所有的扇区, 301-600 是属于第 1 面的所有的扇区,601-900 是属于第 2 面的所有的扇区,901-1200 是属于第 3 面的所有的扇区。每一个磁道有 100 个扇区。
如果我们想要找第 123 个扇区,我们就可以让计算机拿着这个 123,先找到第 0 面,然后找到第 1 号磁道,最后再找到这个磁道里面的第 23 个扇区就可以了。
可是,我们上面曾说过:我们在硬件结构上定位一个扇区采用的是 CHS定位法。即:先找到在哪个柱面,再找到在哪个磁头,最后找到在哪个扇区。
而我们这里将磁盘看做成了一个数组。那么,我们现在如果要找磁盘当中的某个扇区又该如何找呢?
其实,我们只要知道这个扇区的下标就算定位了一个扇区。
而对于上面的这个下标,在操作系统内部,我们称这种地址为LBA地址(Logical Block Addressing),即逻辑块地址。也就是说,只要我们找到了某一个扇区,我们就找到了该扇区的逻辑块地址。
那么,如果我们操作系统内部拿着的 LBA地址为123,当其拿着这个地址在我们的数组里面找到对应位置后,怎样才能找到对应的物理磁盘上的位置呢?【即:如何将LBA地址转换为对应的CHS地址】
我们只需拿着这个123,先让其除以一面的扇区个数(假设为300),找到在哪个盘面。然后让其再除以一个磁道的扇区个数(假设为100)。最后再让123去和一个磁道的扇区个数取余就可以找到在该磁道所对应的哪个扇区了。
123 / 300 = 0 ;
123 / 100 = 1;
123 %100 = 23。
所以,123 号地址应在 第 0 面的第 1 号磁道的第 23 号位置。
对应的 0 应为第 0 号磁头,所以为H;
对应的 1 应为第 1 号柱面(磁道),所以为C;
对应的 23 应为第 23 号扇区,所以为S。
这就实现了将LBA地址转换为对应的磁盘能认识的CHS地址,进而直接去访问磁盘对应的位置。
也就是说:在操作系统内部,它看待所有磁盘的空间,全部是以一个LBA地址为基本单位的。
那为什么操作系统要进行逻辑抽象,而不直接使用CHS呢?
- 便于管理;
- 不想让操作系统的代码和硬件强耦合。【底层硬件无论是磁盘还是固态硬盘,在操作系统看来全是LBA。具体底层用的是磁盘还是其它方法不重要,无论是把LBA地址转换成CHS地址或者其它虚拟方式都可以。所以,操作系统不和硬件强耦合,硬件底层发生变化的时候,并不会影响操作系统】
至此,我们现在看待一个磁盘,它就是一个线性的数组。
而我们的磁盘是用来保存文件的,它又该如何保存我们的文件呢?
我们首先要知道:虽然磁盘的访问的基本单位是 512Byte,但是依旧很小。
就比如:操作系统如果要将 4KB 大小的文件从磁盘加载进内存里,它就至少要经历 8 次 IO,8 次访问磁盘。而每次访问磁盘的时候,磁盘就会得到一个CHS地址。盘片就需要不断旋转,磁头也需要不断地来回摆动,然后去定位。而盘片在高速旋转的时候,我们又不能保证磁头能够一下子就找到对应位置。
所以,大部分情况下,当我们要访问外设(磁盘)的时候,我们需要等磁盘准备好。也就是说:当我们将需要访问的地址给磁盘时,磁盘还需要帮我们找。所以,这就需要等。而当进程在等待的时候,它就需要将自己的运行状态(R)设置为等待状态(S),此时就阻塞了。阻塞之后,它就需要等操作系统识别到数据,然后把数据从磁盘拷贝到内存里,当数据拷贝完成后,此时,该进程才被唤醒,然后,该进程才可以去访问这些数据。
所以说,这就是为什么进程在访问外设的时候会比较慢,原因就是要等磁盘查找我们的数据。
而磁盘作为 外设+机械结构 ,相比于许多其它设备而言,它就注定是很慢的。所以,如果我们还是以 512Byte 的方式去读取它,就会极其影响效率。
所以,操作系统内的文件系统会定制的进行多个扇区的读取。它不是以 512Byte 为基本单位的,而是以1KB、2KB、4KB(最常用)为基本单位。
所以,即使我们只想读取 / 修改 1bit ,也必须将 4KB 的数据加载进内存,来进行读取 / 修改。如果必要,再写回磁盘。
所以,实际上,我们的内存是被划分成为了一个个 4KB 大小的空间,这一个个4KB 大小的空间其实就是页框。
此外,磁盘中的文件尤其是可执行文件,它们其实是按照 4KB 大小划分好的块。而我们以前在虚拟地址空间曾说过:编译器会把我们的可执行程序按照虚拟地址空间的方式将我们的地址编好。它其实不仅会把我们的地址编好,它其实还会按照 4KB 大小把我们的数据做一个归类,然后方便我们分块加载。这个 4KB 的块其实就是页帧。
所以,我们在将数据从磁盘加载进内存的时候,其实就是将页帧里的数据放在页框里。这就是文件系统与内存管理之间的耦合。
我们至此就可以将磁盘的基本单位视作 4KB,而不是一个扇区大小。
四、分区、格式化、分组
我们知道:现在的磁盘空间都是很大的。所以,要对其进行整体管理是很困难的。而且,我们有时候也想在磁盘的不同区域安装不同的内容。所以,我们一般在进行操作的时候,首先要将磁盘划分成不同的区域。
我们这里假设磁盘有 500GB 空间。
因为每个区域的管理方法是一样的(都是管理二进制)。所以,要管理好这500GB空间只需要管理好这100GB就可以了。【这个行为我们称之为分区。】后面空间的管理方法直接拷贝复制就可以了。这样就可以把每个区域管理好了。
【在磁盘分区之后,会对新创建的分区进行格式化,以便操作系统能够识别和使用该分区。格式化过程会在磁盘上创建文件系统,使得该分区能够存储文件和目录。】
可是,即使我们给这个磁盘分了区,它还是太大了。
所以,我们就还需要分组。
那么,我们要管理 100GB,只需要管理好 5GB 就可以了。 【这种行为我们称之为分组。】
所以,我们这里讨论文件系统,就变成了如何把这 5GB 给管理好。
五、一个分组内部的情况
我们这里具体来讨论一个分组内部的情况:
首先,会有这样一张图:
【注】我们上面说的一个组有5GB是我们随便写的,不同的文件系统,它的分组大小是不同的。
1. 分区中的Boot Block
这里我们会看见一个 Boot Block,它叫做启动块。
一般一个分区的最开始有一个 Boot Block 。计算机在加电之后,首先会加电自检,通过硬件的方式检测相当多的硬件的健康状态。(比如:如果磁盘出故障了,操作系统起不来了,笔记本就会直接黑屏。)当它识别到我们的磁盘包括识别到我们的盘符后,它就会从特定的盘符处,比如C盘或者Linux的根目录,开始从 0 号柱面的 0 号磁头的 1 号扇区,这个扇区有 512Byte 的空间,它上面加载的是我们的分区表和操作系统所在的位置。当我们在进行 BIOS 开机自检的时候,通过 BIOS 启动,找到我们的分区表(分区表我们一般叫MBR),这个分区表可以识别我们磁盘的分区情况,只要找到分区了,就可以根据分区找到操作系统所在的区域,进而代码跳转到操作系统的镜像的解压和加载的代码。至此,就把操作系统加载进来了。操作系统完全被加载进来之后再去加载图形化界面或者命令行解释器,启动对应的服务,然后就允许我们登录了。其中,操作系统开机加电启动的时候,相关的信息全部都在 Boot Block 这个区域。我们这里不考虑它。
我们这里主要讲开机之后的情况。
我们现在已经给磁盘做好了分区,然后又做了分组,所以,我们这里以一组为例来谈我们的文件系统。其它组都是一样的。
2. 分组中的Super Block
对于 Super Block ,它表示超级块,它里面保存的是整个文件系统的信息。比如:整个分区一共有多少个组,起始块号是多少,结束块号是多少,每一个分组从哪里开始,从哪里结束,每一个分组现在已经被使用了多少,没有被使用的又有多少,这个组的使用率又是多少,包括整个分区的健康状态,以及关于文件系统的一系列操作方法,它们全都在 Super Block 里面。
可是,我们会发现:这个 Super Block 居然在 Block group 0 里面。而我们上面说过这个超级块里面保存的是整个文件系统的信息。它既然这么重要,那按照道理来说,它是不是也应该和 Boot Block 一样,放在整个分区最开始的位置呀,可是它为什么在 Block group 0 里面呢?
实际上,在我们大部分的文件系统里面,可能有一定比例的 Block group 里面都是以 Super Block 开头的。也就是说,对于我们的分组,它里面的 Super Block 其实是可以没有的。有也可以,没有也没事。
我们这里将多个 Super Block 放在多个块组(Block group)的原因就在于多备份几个。如果某一天我们的某一个文件系统或者块组坏掉了,这都是小问题。因为一个块坏掉了最多就几个G,但是,如果一个分区坏掉了,那就是几百G了,这就会出大问题了。
所以,我们将 Super Block 保存在不同的分组里面,内容数据是一样的,同步都会同步。
如果有一天我们常用的那一个 Super Block 坏掉了,只需要把其它分组里面的 Super Block 拷贝过来就可以了。至此,我们就完成了所谓的文件系统的恢复。
所以,对于一个分组,我们只考虑后面 5 个部分。
3. 分组中的inode Table 与 Data blocks
我们都知道:文件 = 内容 + 属性。
但是,在Linux中,它的文件属性和文件内容是分批储存的。
其中,我们把保存文件属性的称作 inode ,inode 在不同的文件系统的的大小是不一样的,有的是 128Byte,有的是 256Byte。但是,inode 是固定大小的。
但是,文件名是不会在inode中存储的。
而对于文件内容,它在磁盘当中其实采用的是 data block(数据块)的形式来存储的。当应用类型(如文本文件,音视频文件,源文件等)的大小发生变化的时候,data block 的大小也会跟着变化。
我们这里先讲 inode。
对于inode,它其实就是一个文件的属性集合。而一般情况下,一个文件就有一个inode。所以,inode为了进行彼此的区分,每一个inode就都会有自己的ID。我们用这个inode来标识一个文件。
而我们这个分组有5GB,它里面肯定还是会保存很多文件的。
很多文件就意味着有很多 inode,那么,我们怎样才能知道我们的一个分组里到底能够存下多少inode呢?
所以,在我们的分组里就存在一个 inode Table ,它表示 inode表,它里面保存了分组内部所有的可用(已经使用 + 未被使用)inode。【如果inode的个数为100个,那么,这个inode Table的大小就为128 * 100Byte 或 256 * 100Byte】
这个 inode Table 的大小是在分组的时候就确定好的。
所以,我们要创建文件的时候,首先就需要先在 inode Table 里面找到一个未被使用的 inode,然后把这个文件的绝大部分属性填充到这个inode里面,然后,这个文件的属性就都有了。
然后,分组里面还有一个 Date blocks,它里面保存的是分组内部所有的数据块。这些数据块以 4KB 为单位。
所以,分组里就可能存在大量的数据块。其中,哪些块是属于哪些文件的,我们是有办法标识它的。总之,一个文件它有哪些块,它都可以去 Date blocks 里面找到。
所以,我们会发现:
当我们想在Linux中创建一个文件时:
- 首先就是去 inode Table 里面找哪些 inode 没有被使用。找到没有被使用的 inode 之后,我们就可以将属性写进去了。
- 然后就是去 Date blocks 里面找哪些数据块没有被使用,然后我们就可以依据具体需求去申请一些数据块,最后再把数据写到数据块里。
- 最后,这个文件的属性和内容就都被写进去了。
4. 分组中的inode Bitmap 与 Block Bitmap
我们会发现,无论是查找 inode 还是查找 data block,我们都需要一个查找功能。
所以,这就有了 Bitmap。
其中,inode Bitmap 表示 inode对应的位图结构。位图中比特位的位置和当前文件对应的 inode 位置是一一对应的。1 就表示 inode 被占用,0 就表示 inode 未被占用。
所以,当我们在创建文件时,首先就是在 inode Bitmap 里面找,找到第一个不为 1 的比特位。找到之后再将这个比特位由 0 变为 1。同时拿着这个比特位的偏移位置到 inode Table 里面去找,找到对应的 inode,然后把属性添加进去就可以了。
同样地,分组里面还有一个Block Bitmap,它表示数据块对应的位图结构。跟上面一样,如果我们有 1w 个块,那么这个 bitMap 就会有 1w 个比特位。哪一个块被占用,那一位就由 0 变为 1。哪一个块被释放,那一位就由 1 变为 0。
所以,位图中比特位的位置和当前文件对应的 data block 对应的数据块的位置是一一对应的。文件系统会计算文件所需的数据块数量,通过位图找到空闲的比特位(0),并将它们改为1,表示这些磁盘块已被使用。这个块就相当于给这个文件了。这时,只需要将对应的数据填到对应的数据块就可以了。
5.分组中的Group Descriptor Table
这里还有一个问题:
对于一个分组,我们要知道这个分组有多大,这个分组的 Block Bitmap 和 inode Bitmap 里一共有多少个 data block 和 inode,它们已经被使用了多少个,还有多少个没有被使用。对于这些问题,我们肯定是可以通过计算得到的。但是,只要是计算,那么,它的效率就一定会很低。所以,这就产生了一个 Group Descriptor Table。
Group Descriptor Table(简称GDT),指的是块组描述表,它里面保存的是对应分组的宏观的属性信息。
至此,一个分组内部的所有内容,我们就讲解了完了。
六、查找文件与读取文件内容
我们如何查找一个文件呢?
首先,当我们查找一个文件的时候,统一使用的是 inode 编号。
【注】:在同一个分区的不同的组里面,inode是不一样的。即:在一个分区内部,每个组的inode是统一编的。也就是说,inode 是可以跨组的。但是,inode是不能跨分区的。即:不同的分区使用不同的文件系统和各自的 inode。
如:假设这里的 Block group 0 的起始 inode 为1000,Block group n 的 inode 为 10000。那么,在 Block group 0 里面的 inode 表里面,第一个 inode 就为 1000,第二个 inode 就为 1001 …… 所以,如果我们有一个 inode 编号,我们是可以确认这个 inode 是属于哪一个块组的。因此,我们就可以不考虑这个 inode 到底属于哪一个组。
那么,我们这里假设我们有一个 inode,它在 Block group 0 里面,我们需要查找这个 inode。
首先,我们就需要先到 Block group 0 里面的 inode Bitmap 里面查找这个 inode 是否有效。即:在 inode Bitmap 里面找到该 inode 对应的比特位,看它是 1 还是 0,如果是 1,就表示这个 inode 是有效的。
其次,如果这个 inode 是有效的。就去 inode Table 里面查找这个 inode,如果我们要查找的文件的 inode 和 inode Table 里面的 inode 是一样的,说明我们就找到了对应的文件。相应地,我们就拿到了这个文件的属性。
那么,我们如何才能读取到这个文件的内容呢?
其实,查找文件的内容也是通过 inode 来查找的。
在 inode 里面有一个 blocks 数组,它里面有 15 个元素。【元素个数不唯一,不同文件系统的方案不一样】
这个数组里面存放的就是数据块的编号。
其中,这个数组的前 12 个元素会直接存放对应的数据块。
我们要读取一个文件的内容时,我们只需要:
- 拿着 inode 编号,并且确认这个编号是有效的(通过 inode Bitmap);
- 然后通过 inode Table,找到对应文件的 inode 及其 inode 内部对应的属性(如果有需要);
- 再在我们对应的属性里查找这个 blocks 数组。
- 当我们查找这个数组的时候,如果在数组里面发现了对应数据块,就将其按顺序取出来。
- 将这个文件的每一个数据块按顺序取出来,就实现了把这个的内容读取出来。
比如:当查找这个 blocks 数组时,发现里面只有前 4 个元素有数据,也就说明这个文件只有 4 个数据块。那么,我们就可以按这个数组的顺序,依次找到对应的数据块,然后将这些数据块依次取出来,这个文件的所有数据就都找到了。(此时的数据块顺序为:15,43,68,80)
可是,如果这个 blocks 只能存15个数据块,也就只能存 4KB * 15 = 60KB 大小的文件,这存储量明显还是太少了。
所以,对于下标为 12 的这个元素,它虽然指向的也是数据块,但是这个数据块里面保存的是其它数据块的地址。即:我们下一次索引时,要找的数据块。【一级索引】
此时,对于数组下标为 12 的这个元素所指向的数据块就可以存 4096Byte / 4Byte = 1024 个地址。1024个地址对应于1024个数据块,一个数据块的大小为 4KB。所以,这个数据块就可以保存4MB 的数据。
4MB 还是太小了。所以,对于数组下标为 13 的这个元素,它可以指向一个数据块,和 12 一样,13 所指向的块也可以指向其它的块。但是,这个被指向的块里面依旧不是数据块,而是其它块的地址。【二级索引】
此时的大小:1024*1024*4KB = 4194304KB,4194304KB / 1024 = 4096MB,4096MB / 1024 = 4GB。所以,对于13,他就可以存储 4GB 的数据。
同样地,对于 14,按照上面的推演,让它进行三级索引,就会发现它可以保存 1024GB 的数据。
所以,我们就会发现:我们的inode表里面其实是可以保存特别大的文件的。
到此,我们就讲了:在磁盘上创建一个文件的主要过程 和 在磁盘中查找一个文件的主要过程。
七、删除文件
我们如果要删除一个文件呢?
和上面一样,我们要删除一个文件也是通过 inode 来做的。
我们只需要拿着 inode 编号到 inode Bitmap 里面去找到对应的比特位,然后将比特位由 1 改为 0,这个文件就被删除了。
当 inode Bitmap 中对应比特位由 1 变为 0 了就意味着这个文件的 inode 被删除了,其所对应的属性自然也就无效了。当属性被删除后,就是去 Block Bitmap 里面将这个文件的数据块所对应的比特位由 0 变为 1。此时,这个文件的数据也就被删除了。
因此,我们会发现:一个文件被删除后,我们是可以将其恢复过来的。
我们只需要先找到被删除文件的 inode 编号,然后通过这个 inode 编号,将 inode Bitmap 里面对应的比特位由 0 变为 1。再到对应的inode Table 里面找这个文件的映射表。映射表找到之后,就找到了这个文件曾经使用过哪些数据块。找到这些数据块之后,再将 Block Bitmap 对应的比特位由 0 变为 1 就将这个文件恢复过来了。
八、目录的数据块内容
我们会发现:我们在上面讲的创建文件、查找文件、读写文件、删除文件等都是使用的是 inode。
可是,我们在创建、查找、读写和删除文件的时候,我们都是用的文件名,没有用 inode 呀。
而且,当我们使用这个 inode 编号,会发现这个 inode 并无法使用。
这是为什么呢?
我们要知道:目录其实也是文件,它也有自己的属性(比如:目录的大小、创建时间、权限等), 它也有自己的内容。即:目录也是普通文件,它也有自己的 inode。只要有自己的 inode,它也就有自己的数据块。而目录的数据块里面放的就是当前目录下的 文件名和inode的映射关系。
所以,这就是为什么 inode 里面不会存放文件名,因为不需要,它是存放在目录的数据块里的。
所以,当我们使用 “ls +文件名” 查找文件的时候,首先要做的就是查当前目录下对应的数据块,把它的文件名和 inode 提取出来。【而文件名在一个目录下拥有唯一性,自然就可以将其作为key值,然后通过这个文件名索引到对应的 inode,有了 inode,就可以实现对文件的增删查改了】
所以,我们曾经在讲linux权限的时候曾说过:
- 要在一个目录下新增文件,必须具有写入权限。
因为当我们想要新增文件的时候,此时一定是向当前目录下的数据块里面写文件名和inode的映射关系,所以,自然就需要写入权限。
- 要罗列一个目录下的文件名,必须具有读权限。
因为当我们想罗列一个文件名时,需要根据文件名,找到对应的 inode,此时才可以读取到文件对应的属性。即:要有读权限的原因就在于我们使用“ls + 文件名”的时候,使用的是文件名,而要将文件对应属性读取出来,就必须要访问目录的数据块。找到对应的映射关系,以便找到底层的 inode,将属性读取出来。