Ext系列文件系统

发布于:2025-05-27 ⋅ 阅读:(24) ⋅ 点赞:(0)

目录

CHS寻址方式

磁盘的逻辑结构

LBA地址

CHS转LBA

引入文件系统

分区

inode

ext2文件系统

BLOCK GROUP

块组内部构成

超级块( SUPER BOLCK)

块组描述符表 GDT(Group Descriptor Table)

块位图(Block Bitmap)

inode位图(Inode Bitmap)

inode节点表(Inode Table)

数据块(Data Block)

inode逻辑结构 

目录与文件名

路径解析

路径缓存

原理


扇区:是磁盘存储数据的基本单位,512字节,块设备

我们访问文件时是访问具体某一个扇区的内容

磁盘容量=磁头数×磁道(柱⾯)数×每道扇区数×每扇区字节数

CHS寻址方式

定位文件只需要确定磁头(高),柱面(自内而外第几个),扇区(某一磁道的具体位置)即可。

磁盘的逻辑结构

我们可以想象扇区是一个个连起来的,那么我们就可以通过下标去访问了

某一磁道展开就可以形成下面的一个个连起来的扇区
 

某一柱面展开,就可以形成这种[盘面某磁道][扇区]的二维数组

然后再加上许许多多的柱面,我们就可以形成:

[某一柱面][某一磁道][扇区]

这样多张二维数组组成的三维数组

因此CHS寻址方式即为:柱面(Cylinder),磁道(就是磁头位置)(Head),扇区(Sector)

LBA地址

所以,每⼀个扇区都有⼀个下标,我们叫做LBA(Logical Block Address)地址,其实就是线性地址。

具体转换LBA地址转化CHS地址这件事会由硬件自己完成,操作系统只需要使用逻辑地址(LBA)即可

CHS转LBA

LBA=柱⾯号C*(磁头数*每磁道扇区数)+磁头号H*每磁道扇区数+扇区号S-1
 

扇区号通常是从1开始的,⽽在LBA中,地址是从0开始的
 

柱⾯和磁道都是从0开始编号的

引入文件系统

硬盘是块设备,但是每次一个扇区这样读取,效率实在是太低了,因此操作系统读取设备时会一次性读取多个扇区,一次性读取的多个扇区简称“块”。

硬盘的每个分区是被划分为⼀个个的”块”。⼀个”块”的⼤⼩是由格式化的时候确定的,并且不可
以更改,最常见的是4KB,即连续八个扇区组成⼀个”块”。”块”是⽂件存取的最小单位。
 

分区

其实磁盘是可以被分成多个分区(partition)的
分区从实质上说就是对硬盘的⼀种格式化。
 

柱⾯是分区的最小单位,我们可以利⽤参考柱⾯号码的方式来进⾏分区,其本质就是设置每个区的起始柱⾯和结束柱面号码。

inode

我们使用ls -l 指令除了看到文件名还能看到文件的详细信息

依次为:模式、硬连接数、拥有者、所属组、大小、最后修改时间、文件名

ls -l命令为读取磁盘上的文件信息并显示出来

除了ls命令,我们也可以使用stat命令获取文件的更详细信息

既然文件数据都存储在块上,那么一定有一个地方用来存储文件的属性信息,例如上文的创建者,文件大小等等

通过-i,我们可以发现前面多了一项信息,这个数字即为inode(号)

inode为储存文件原信息的区域,中文为“索引节点”

每个文件都会有对应的inode

注意:
linux下文件的内容与属性是分开的

linux下保存文件的属性集合叫做inode,每个文件都有inode,每个inode内都有一个唯一的标识符inode号

ext2文件系统

linux系统中最常见的文件系统为ext2文件系统,虽然后面有新的ext3,ext4,但是由于核心没有发生变化,因此我们这里仍然采用ext2做介绍

ext2将整个分区划分为若干大小相同的块组,只要能管理一个分区,那么就能在此基础上管理所有的分区,包括在此之下的所有磁盘文件

启动块(BootBlock/Sector)的大小是确定的,为1KB,由PC标准规定,⽤来存储磁盘分区信
息和启动信息,任何⽂件系统都不能修改启动块。启动块之后才是ext2⽂件系统的开始。
 

BLOCK GROUP

文件系统会根据分区的大小划分成若干的块组,每个块组都有相同的结构

块组内部构成

超级块( SUPER BOLCK)

超级块存放整个文件系统本身的结构信息,描述当前整个分区的信息,如block,inode总量,未使用的block和inode个数,一个block和inode的大小等,超级块被破坏,那么整个文件系统结构就被破坏了

不过从图中可以看到每个块组的开头都会有超级块的拷贝,用来防止发生这种情况(至少第一个块组一定要有,因为超级块的拷贝必须要有用来防止突发情况,后面可没有

为了防止保证文件系统的某一扇区的物理部分遭到破坏导致超级块被破坏,一般都会在多个块组(BLOCK GROUP)中进行备份

块组描述符表 GDT(Group Descriptor Table)

描述块组本身的属性信息,每个块组都有一个,一个分区有多少个块组就有多少个GDT。

每个块组描述符存储⼀个块组的描述信息,如在这个块组中从哪⾥开始是inode Table,从哪里开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷贝

块位图(Block Bitmap)
 

Block Bitmap中记录着Data Block中哪个数据块已经被占⽤,哪个数据块没有被占⽤
 

inode位图(Inode Bitmap)
 

每个bit表示⼀个inode是否空闲可用
 

inode节点表(Inode Table)
 

存放文件属性如文件大小,所有者,最近修改时间等
当前分组所有Inode属性的集合
inode编号以分区为单位,整体划分,不可跨分区
 

数据块(Data Block)
 

数据区:存放文件内容,也就是⼀个⼀个的Block。根据不同的⽂件类型有以下几种情况:
 

对于普通文件,文件的数据存储在数据块中。
对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中,除了文件名外,ls-l命令
看到的其它信息保存在该⽂件的inode中。
Block号按照分区划分,不可跨分区
 

inode逻辑结构 

这下面的块指针以及间接指针大致为

三级-> 二级->一级->块

块指针存放具体的位置,至于剩下几个则按照数组的方式,最终转化为块指针

一级间接指针就是块指针的一维数组

二级间接指针是相当于二维数组

三位间接指针类似

目录与文件名

从上面的文件系统我们可以知道,打开一个文件需要知道一个文件的inode我们才能从数据块中获取文件数据,但是吧我们要怎么获取呢?

linux下"一切皆文件"

目录当然也是文件,但是磁盘上没有目录的概念,只有文件属性+文件内容的概念

目录中保存的数据为文件名与其对应inode号的映射关系

因此我们访问文件,实际上是打开当前工作目录,查看目录文件的内容,找到对应的要访问的文件inode号

路径解析
 

比如说我们现在要访问/root/test/main.cc文件

根据上面我们其实就可以知道,我们要访问main.cc,就要先知道它的inode,要知道他的inode就要访问上级目录test,知道tset的inode,一直向上直到达到根目录

这种类似递归的方式,出口为’/‘,所以我们实际上访问任何一个文件都需要知道一个文件的绝对路径,从根目录开始访问直到访问目标文件的过程,称为linux的路径解析

当然了,根目录’/‘的inode号我们不需要查找,因为开机之后必须知道,否则接下来都没办法进行

路径缓存
 

如果每一次访问文件都要递归向上执行未免太慢了,因此linux会缓存历史路径

Linux中,在内核中维护树状路径结构的内核结构体叫做: struct dentry
 

注意
每个文件其实都要有对应的dentry结构,包括普通文件。这样所有被打开的⽂件,就可以在内存中
形成整个树形结构

整个树形节点也同时会⾪属于LRU(Least Recently Used,最近最少使用)结构中,进行节点淘汰
 

整个树形节点也同时会⾪属于Hash,方便快速查找
 

更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何⽂件,都在先在这
棵树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry
结构,缓存新路径
 

好了,这里让我们详细解释一下这个dentry到底是怎么使用的

原理

首先我们知道,dentry实际上是由内核管理的,所以内核可以直接知道相关信息,而不需要通过文件系统,因为文件系统是用户级的

dentry里面最重要的其实就有以下几个:

1.父节点的dentry指针

2.该dentry所属文件的inode属性指针

3.子文件dentry双向链表struct list_head d_subdirs

4.对应的文件名 struct qstr d_name

假设我们从‘/’开始,‘/’的资源组成了第一个dentry,他的父dentry指针为null,他的inode值也是从一开始就知道的,交给inode指针管理,之后就是从磁盘里获取数据,将‘/’路径的所有数据通过inode映射的方式交给另外一个‘页缓存’机制,这样我们就可以知道‘/’的所有数据了,然后我们就可以往下访问。

我们开始访问子文件,输入子文件名,在struct list_head d_subdirs 中查找是否有这么一个文件,如果有,那么访问子文件的dentry,查看对应的inode访问数据

找不到,我们就访问该目录的数据(或磁盘),查看是否存在该文件,存在就创建一个dentry。

包括将‘/’的dentry指针作为这个子文件的dentry的父dentry指针,从磁盘(内存)中获取这个子文件所有信息,同样通过‘页缓存’保存该文件的inode与数据的映射关系。

通过上面这样,我们就可以实现dentry访问某一文件了

然后可能会发现某一个问题

例如:

当前文件有父目录的dentry,可以很方便的使用父目录

但是如果是访问例如/a/b/c/d/e/f/g/h这样一个路径,中间可能许多的目录都没有创建dentry会怎么处理。这样的话我们就会采用从前往后的顺序访问每一个目录创建dentry直到达到目标文件,这种即是上文提到的路径解析

注意:也有这样一种方式来查找子文件,每个目录都有属于自己的哈希表,这张表维护了该目录底下的所有子文件名,没有找到就与上面同理创建文件名与dentry的映射关系。哈希和双向链表一起维护目录底下的子文件dentry

软硬链接

硬链接

很简单,同一个inode的文件,这说明硬链接的看起来是两个文件,实际上是一个文件,两个的操作同步进行,不过要注意,因为是同一个inode,所以不能在一个文件内部进行硬链接进行该文件

内部采用引用计数的方式,只有当一个inode的文件硬链接被全部删除,引用计数为0再删除掉这个文件相关信息

软链接

快捷方式,是一个新文件,有一个新的inode,与硬链接同理,可以访问同一个inode的文件内容。

不过毕竟是快捷方式,指定的文件被删除了那么就没用了,只要在ln命令基础上加上-s选项就好了。soft(软)。