linux操作系统 | 理解文件系统

        前言:本篇内容讲解文件系统的细节问题。 在本篇内容中, 我们在学习文件系统的过程中, 我们可以理解inode的原理, 理解如何在文件系统的概念下新建文件, 删除文件, 查找文件, 修改文件等等问题。 下面开始我们的讲解吧!

        ps:本节内容适合了解一些磁盘知识的友友们进行观看。

目录

磁盘逻辑结构与分区分组概念

boot block

操作系统的对磁盘的管理方式——分而治之

组里面的内容

data blocks

inode table

寻址的细节

inode bitmap

block bitmap

group descriptor table

Super block

磁盘的初始化

文件操作细节

新建一个文件

删除一个文件

查找一个文件

修改一个文件

理解目录文件

如何找到目录文件的inode


磁盘逻辑结构与分区分组概念

本片的内容我们都以一个800G大小的磁盘为例, 下面是这个磁盘的逻辑结构

这800G磁盘因为太大, 所以操作系统就可以把他们分成一块一块的, 就如同下图:

        上面的一块一块的空间, 就相当于我们使用的电脑上面的C盘D盘。 这些分区对于操作系统来说, 如何分?

其实就是一个结构体, 如下:

struct partion
{
    int start;
    int end;
}

        这是一个分区的结构体, 指向一个分区的起始地址和末尾地址。

        假如想要分5个区, 那么就可以定义一个数组: struct partion part[5]。 这个数组就代表着分成了五个区。 我们可以把这个叫做分区表。

         这样做了以后,操作系统以后管理分盘就可以先拿到分区表。然后就知道了每一个盘的起始,末尾和大小。想要管理某一个盘只需要定位即可。

         ps:我们的分区的概念其实和上面讲解的CHS寻址方法并不冲突——CHS寻址看的是盘面个数, 每个盘面的磁道个数, 每个磁道的扇区个数。

        另外, 每个分区也有更加详细的划分,如下图:

boot block

        传统的MBR分区表bootblock是在磁盘的第0面,第0号磁道, 第0号扇区上面。 里面主要保存了三个字段:

        第一个字段:系统的开机信息

        第二个字段: 分区表

        第三个字段: 签名

        现代的GPT分区表使用的是.efi文件保存系统的开机信息, 但是仍然保留了bootblock, 这是为了让兼容性, 实际上bootblock里面的信息没有意义。

操作系统对于磁盘的管理——文件系统

        操作系统要对磁盘进行管理, 使用的是文件系统。 文件系统就是一个规则体系, 包含于分区中。只不过有的分区不使用文件系统, 比如swap分区、裸设备。

        分区是分区表进行划分的, 更底层。 而文件系统是存在于分区内的一种规则体系, 只不过这种规则体系覆盖了整个分区。 

        文件系统里面有两大核心成员: 第一个成员叫做元数据, 第二个成员叫做存储数据

        元数据:super block、group describetor table、block bitmap、inode bitmap、inode table

        存储数据: data blocks

        如果把分区比作一本书, 那么文件系统的元数据就是这本书的目录和格式, 存储数据就是这本书的正文页, 是空白页还是写了字的页取决于这个data blocks里面存没存储文件。

操作系统的对磁盘的管理方式——分而治之

        上面的每个分区会分成一个一个地小分区, 然后每一个分区都是一个block group。 那么, 以前大分区是250GB, 那么分成小分区后, 每个小分区可能就是10GB。那么管理好一个小分区10GB, 就能管好所有的小分区, 管理好所有的小分区, 就代表着能管理好一个大分区250GB, 那么就能管理好所有的大分区。 那么就变成了对整个磁盘地管理。

        如此, 我们就将对整个磁盘800GB的管理转化成了对10GB的管理。

        这种思想就叫做“分而治之”。

组里面的内容

        我们通过上面知道了, 每一个文件系统都把自己分成了许多个小分区,即block group。 这里面包含了文件系统的元数据和存储数据。 下面这些字段一个一个的进行解读:

data blocks

       文件系统中保存文件数据的最小单元就是data block。 就是一个块, 一个块的大小可能是1kb, 2kb、4kb。这个块大小是固定的。 

        如果我们想要在磁盘中写1字节的数据, 我们就要在某一个分区里面找一个组, 然后在里面申请4kb大小的块空间。

        这个规则如果不想明白可能会觉得非常浪费空间。 但是真的会非常浪费空间吗?——答案是不会浪费很多空间, 因为每一个文件都只会浪费一个块, 为什么? 这里可以想一下, 如果一个文件几百mb, 那么文件里面的数据将会占满许多的块空间, 然后在最后一个块空间可能会浪费一点空间。 如果是一个1字节的文件, 那么也只会浪费一个块空间。 

inode table

        inode table是inode的数组。 

        inode里面保存了单个文件的所有属性信息,比如:文件的类型或者权限、文件的大小、文件占用的块的数量、文件的块的位置等等。

        每一个文件都有一个inode保存着它的信息, 两者是绑定起来的。        

        一个inode的大小一般是128字节在ext2下, ext4下为256字节。

        inode的编号是全局唯一的, 计算方式就是先算自己在哪一个组, 然后看在这个组里面的inode table里面的哪一个下标处。 得到inode编号。 

        所以, 综上。 inode table就是一个inode对象数组。 inode对象与唯一一个文件对应。 里面包含了这个文件的各种属性信息。这些信息包括但不限于:

#define NUM 15
struct inode
{
    //inode number;
    //文件类型
    //权限
    //文件的大小
    //文件占用了块的总数
    //文件占用的块的位置
    //引用计数
    //拥有者
    //所属组
    //ACM时间
    //int blocks[NUM]
}

        注意:linux中标识文件不是用文件的名称。在linux系统里面标识文件用的是inode编号!!!因为inode编号唯一, 文件名不唯一。

下面是blocks[NUM];

        

        BlockNum是一个存储文件所占用块指针的数组。本质上存储的是指针。  

        因为一个文件可能非常大, 那么这个文件就有可能不仅仅只占用15个块。 解决方案就是让块里面去保存其他的块的指针。文件系统的解决方式如下:

  •          前十二个元素指向的块直接存储数据, 就是一级指针。被称为直接指针。——这十二个元素指向的块保存了48kb的数据。
  •         blockNum[12]里面是二级指针, 被称为一级间接指针。 这个块间接的保存了1024个块, 相当于保存了4Mb的数据。
  •         blockNum[13]里面存放的是三级指针, 被称为二级间接指针, 这里面间接保存了1024 * 1024个块, 相当于保存了4GB的数据。

        blockNum[14]里面存放的是四级指针, 被称为三级间接指针, 这里面间接保存了1024 * 1024 * 1024个块, 相当于保存了4TB的数据(如果有这么大的磁盘空间的话)

        在上面这些块里面, 我们如何知道那些块是被使用的, 哪些块是没有被使用的呢?

        所以我们就有了一个block bitmap。 这个block bitmap的占用空间非常小。 本质上有多少个块, 就有多少个比特位。 有4GB的块, 也就是1024 * 1024个块, 就有1024 * 1024 / 8个字节来开辟block bitmap。 而1024 * 1024 / 8等于131 072字节。 131 072字节 / 4kb = 32, 相当于4GB的空间只需要32个块来标记。
        所以, 以后一个块怎么查看被没被使用只需要查看block bitmap中对应的比特位有没有置为1, 为1就是代表使用过了,否则就是没有被使用过。  并且以后一个文件如果想要删除, 也不用把文件里面所有的块都清空, 只需要把文件里面所有的块在block bitmap对应的比特位置为0即可。 

寻址的细节

        我们需要知道的是, 无论是inode, 还是data block, 到最后一定可以被操作系统解释成为一个个的扇区编号, 因为inode table有对应的编号, data block也有对应的编号。 那么根据特定的算法, 就能得到最后的扇区编号。 ——这个过程是操作系统必须做的。 ——寻找的算法:

        一个块4kb, 一个扇区是512字节。 而一个inode是128字节, inode在组里面是连续分布的, 那么就说明一个扇区里面有4个连续的inode, 那么一个块就是32个inode。 那么我如果知道了加载到磁盘当中的inode编号, 那么我们根据——》编号 / 32以及编号 % 32就能知道当前inode在系统的哪个山区当中。 而对于data block, 一个块是4kb也就是8给扇区, 那么知道了块编号, 就可以计算扇区编号。

inode bitmap

        inode table 里面保存了文件的编号, 有1w个文件就有1w个inode。 但是, 对于整个的inode table数组, 我们怎么知道里面的哪个索引处被占用, 哪个索引处没有被占用呢?——这个时候就要用到inode bitmap。 inode bitmap是用来将比特位的位置和inode编号映射起来, 来判断对应的inode是否有效的!!!

block bitmap

        在这张表中, 其实block bitmap的占用空间并不大。 因为假如我们有10w个块, 一个块4kb, 十万个块就是4G, 也就是说, 如果一个区200GB, 分成20个组, 那么一个组对应10G的内容里面最多只有25w个块。 但是这是不可能的, 因为还要给其他的内容分配空间。 这里假如有20w个块, 那么block bitmap就要有20w个比特位映射这20w个块。 20w / 8 = 22500, 也就是22500b。22500 / 1024 ~ 22kb, 也就是说, block bitmap最多也就占用22kb, 五六个块的大小。 inode bitmap会比block bitmap占用的空间更加的小。 因为inode的个数是更少的。 所以block bitmap, inode bitmap占用的空间在整个磁盘中几乎可以忽略不记。

group descriptor table

        

        描述的是整个分组的使用情况——比如我们block bitmap的起始地址, 使用了多少空间、inode bitmap的起始地址, 使用了多少空间。 inode table的起始地址、data block的数量, 空间块数量等等。 以及我们在分配inode的时候或者分配block的时候, 我们的inode或者data block的编号下一个要从哪里开始分配。这个也就解决了我们想要拿到块编号和inode编号来定位bitmap中比特位的问题。 

        这里之所以没有使用bitmap来直接计算block和inode的使用情况是因为计算的过程效率不如直接使用统计效率高。 所以这里直接使用了Group Descriptor Table。

Super block

        文件系统的基本信息——也被称为超级块,相当于文件系统的”大脑“,保存了整个文件系统的基本信息。

        super block里面还包含各种字段, 比如: 块大小、块总数、inode总数,空闲块数, 空闲inode总数等等。 

        super block字段只有在第一个block group, 即block group 0里面存在, 存储在该组的起始位置。因为操作系统要节省空间。 但是为了防止磁盘损坏导致数据丢失, 所以在其他block group的起始位置也可能星星点点的备份一些super block。这样更新成本更低, 并且一旦superblock挂掉, 其他分组内的super就会对整个文件系统进行修正。

        super block里面有一个字段叫做魔数, 是一个随机值, 这个随机值用来标识当前的块是不是superblock。它在super block当中的位置是固定的。 当操作系统在读取磁盘中的某个块的时候, 只需要在特定一个块的待定位置读取这个数字, 这个数字必须等于我们规定好的这个魔数。 等于的话, 才会认为这个是一个super block。 

磁盘的初始化

        对于这个图里面的信息, 除了inode table 属于文件的, data blocks属于文件块的, 这两个空间只需要预留好即可。 前面的super block、group descriptor table、block bitmap、inode bitmap四个中的super block、group descriptor table是需要提前初始化信息的block bitmap、inode bitmap是要清空的

        每一个分区在被使用前, 都必须提前将部分文件系统的属性信息提前设置进对应的分区中, 方便我们后续使用这个分区或者分组。 ——前面的四个信息, 是对后面两个部分的属性的描述。而这种提前将文件系统的信息写到文件中的过程叫做格式化

        其实我们电脑内部只有一块物理磁盘(硬盘), 而之所以会有C盘、D盘、F盘, 其实这些盘就是硬盘的分区。 而一旦格式化, 就会将两个bitmap全部清零, 并且向super block、group descriptor table初始化文件信息。

文件操作细节

新建一个文件

        新建一个文件, 那么我们知道这个文件一定是在某一个路径里面, 这个路径, 就可以帮助我们确定是在哪一个分区里面。 然后就是确定我们在分区的哪里去创建我们的文件。 ——分区里面的inode假如是连续的, 比如第一个group里面的inode是从0~1w, 第二个的inode是1w~2w, 以此类推。 那么我们创建文件的时候, 加入第一个组有空余的空间, 那么我们这个文件就可以保存在第一个组里。 那么如何确定保存的位置?——只需要查看GDT, 查看inode和块有多少空间占用率, 再查看inode bitmap、inode table扫描位图结构。

        找到一个最近的没有被使用过的编号。 如果找到的是5,那么如果分到了第一个分组就是0 + 5, 那么如果分到了第二个分组里面就是1w + 5。

        未来就可以根据inode范围来确定哪个分组里, 如果确定了5后就把inode bitmap里面5的位置置为1, 再将文件信息放到inode编号5的结构体里, 至此, 这个文件算是创建成功了。 

删除一个文件

        一个文件, 一定是处于某个目录的, 根据这个目录, 然后我们就可以确定这个文件是哪一个分区中。 然后根据inode范围,确定哪个分组的, 然后把我们这个inode编号减去起始inode编号。 进而, 我们就可以在inode bitmap里进行索引了, inode进行索引的时候, 只要删文件, 那我们就可以根据我们的inode编号, 找到我们对应的inode文件属性, 根据文件属性里面的block, 找到对应的block bitmap里的比特位置, 并将其置为0. 而其中的块则不去动它, 然后在我们的inode位图里, 把对应的1置为0, 将属性干掉即可。

        如果我们不小心删除了一个文件, 那么这个文件如果是一个不重要的文件, 那么就可以不管这个文件了。 但是如果这个文件是一个很重要的文件, 那么此时最好的处理方法是什么都不要做(这些block是可以覆盖的), 可以去找专业的人士进行文件恢复。

查找一个文件

        查找一个文件就是拿到这个文件的inode编号, 然后根据编号确定inode bitmap里面的对应的比特位是否为1, 那么就可以确定对应的inode属性是否有效, 拿到inode属性后, 就找到inode属性里面的block NUM, 然后就能查找到文件了。

修改一个文件

        我们修改一个文件, 比如说创建一个文件, 修改一个文件等等。就会拿到这个文件的inode, 拿到这个文件的inode之后, 就可以根据inode确定bitmap里面的比特位是否为1, 是否有意义。 然后拿到inode的文件信息, 找到里面的block NUM, 然后再找到对应的数据块, 修改这些数据块, 就完成了文件的修改。

理解目录文件

        目录也是文件, 也有自己的inode, 目录也要有自己的属性, 但是, 目录有内容吗? 我们知道, 文件 = 文件内容 + 属性——》这里, 目录有没有内容的本质, 其实就是目录有没有数据块。

        答案是目录也有数据块, 但是这个数据块应该存什么呢? ——存的是该目录下文件的文件名和对应文件的inode的映射关系!!!

  •        为什么同一个目录下不能有同名文件——因为文件名和对应文件的inode映射关系被保存在数据块中!!!
  •        目录下, 没有w, 我们无法创建文件, 这是为什么——因为即便是创建下来, 但是这个文件的文件名和inode的映射关系无法写到对应的数据块里。
  •        目录下, 没有读权限,我们无法查看文件, 为什么?——因为我们在读取文件的时候, 要读取文件的inode, 然后根据inode读取块数据, 而我们没有读权限, 我们无法读取文件的inode, 更加无法读取块数据。
  •         目录下, 没有执行权限, 我们就无法进入这个目录, 为什么?——因为我们使用inode, 进入这个目录的时候, 要对环境变量做一下更新, 但是今天我们如果没有执行权限, 我们就不让他进行更新了。

如何找到目录文件的inode

        目录是文件, 同样有inode, 我们可以通过拿到目录的inode找到里面的数据块, 进而找到目录里面的各个文件。 那么我们如何找到目录的inode呢? ——我们知道, 我们的目录一定是上一级目录里面的文件那么我们就一定能够通过上一级目录的inode, 找到其中的数据块, 进而找到这一级的目录的inode 而我们一级一级的追溯, 我们最终会追溯到根目录, 而好在我们有根目录的inode, 也就是说, 我们要先递归的找到根目录, 然后再一步步返回。

        但是, 一步步访问太慢了, 所以linux操作系统会一般把我们最常访问的目录文件保存起来, 以及他路径上的所有的inode以及路径信息缓存起来。 这个缓存, 就叫做dentry缓存。 

----------------------------------------------------------以上, 就是本节的全部内容, 下面是本节笔记:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值