当前位置: 首页 > news >正文

揭秘磁盘存储:从物理结构到文件系统

Ext系列文件系统

以上我们了解的是被打开的文件,被打开的文件属于基础IO的学习,被打开的文件是处于系统内存的!
那么没有打开的文件呢?
我们被打开的文件最多几百到几千的数量,但是相比于被打开的文件,未被打开的文件数量远多余被打开的文件,数量到了成千上万的程度!

所以未被打开的文件被存放在那里?——磁盘!
文件存放在磁盘上,如何被我们找到???

目录结构组织,目录结构是树状的,路径(绝对和相对)
而文件存放在磁盘上,最基础的诉求是:文件能被用户找到,找不到不就相当于释放吗?
那么是谁来完成这个最基本的诉求呢?——文件系统的工作!

接下来就来好好了解文件系统,我们从硬件开始谈!(未来提高理解,对于软件的学习,有硬件学习的辅助,往往事半功倍!)

理解硬件!

磁盘、服务器、机柜、机房

磁盘是机械设备,是计算机内唯一的机械设备,计算机是电子,而磁盘是机械的!
磁盘属于外设,并且远离于CPU,所以效率相对低!但是容量大,价格便宜

企业为什么用磁盘呢?因为磁盘容量大还便宜!但企业使用存储设备的准则是与功能有关,像多年前是冷数据,被调用的少,通过磁盘存储,综合性价比高!而经常被使用的热数据!就不能用磁盘存储,IO效率太低了,就需要用高效率的存储设备!


右边图片是磁盘拆开的结构,其中的光面是用于存储数据的区域,光面的中心处有一个马达!这个马达带动着光面高速旋转(8k转/s、2w转/s)!而图中,像笔一样的东西为两部分组成!头部叫磁头!长的部分为摆臂

磁盘光面类似于光盘,但是光盘只能被读取但是不能修改!
而磁盘的光面类似于,只是磁盘的既能被擦除又能被写入!

接下来这个为服务器!服务器只有网卡,不用其他外设!服务器的内部是可拔插的盘,内部有很多!这里是24块盘,一块盘差不多4TB,这个服务器的容量差不多为96TB!,这些盘的背后会有很多网口!进行IO操作!

接下来的是机柜,由多个服务器组成!

机柜背面由很多的线!做相应的链接

而多个机柜链接起来就是机房了!

磁盘的光面有其最小的存储单元,这个最小单位我们可以想象成磁铁,一个光面上可能有成千上网的磁铁,打磨光滑组成在一起,而磁铁是由两级的状态,我们可以推定南极为0,北极为1,从而可以通过调节磁盘光面上最小颗粒的磁极,来表示对应的数据,向磁盘中写入数据,相当于是向磁盘光面做磁极的调整!
计算机只认二进制,而不同的设备要通过不同的方式来表示二进制!磁盘就通过南北极,内存可能通过电极的充放电!
磁头具有电极,可以通过电流对磁极进行调整!

如果要清理磁盘,由一个最简单粗暴的方法!即火烧!磁铁被火灼烧一段时间,磁铁会退磁,这样磁盘的数据就被清除!

磁盘物理结构


主轴就是上面说的马达!带动磁盘高速转动!
我们上面说的光面就是这里的盘片!
磁头臂会来回摆动!
这个是物理的存储结构

磁盘的存储结构

接下来了解一下存储结构!

物理结构是表面,内部的实际结构要进一步了解!

盘片并不是一个整体,而是一系列同心圆,每个同心圆就称为磁道
而磁道并不是连续,一个磁道又是多个扇区组成的!
磁道有宽度,也有长度,这一段就组成类似扇形的区域,就是扇区,而一个扇区可以存储512字节的数据,即有512个小磁铁!


磁盘存储的基本单位:扇区(512字节)

计算机OS要修改磁盘中一个扇区的一个比特位,OS都要把这个扇区全部数据都加载到内存,将对应比特位修改,再写回对应的扇区!即读写磁盘的基本单位为扇区!

磁盘在读写数据的基本单位为扇区!

我们了解磁盘是俯视查看,我们可能会默认认为磁盘只有一个盘片,但实际上,磁盘内部有3个这样的盘片!有6个这样的盘面!即三片6面

每面都会有自己的磁头!磁头在磁头臂摆动的带动下,共进退!传动臂的方向,长度一直都是统一的!

如果此时,一个磁头在一个盘面的一个磁道上,那么其他5个磁头都在对应盘面的对应磁道上!那么这个6个磁道,在逻辑上共同构成一个结构,叫柱面!磁道和柱面是二维到三维的关系!

磁盘写入的时候,是向柱面进行批量写入的!

磁盘要写入一大批数据,这些磁头可以同时工作,也可以局部性的工作!

问题:512字节,写入到某一个扇区中,磁盘应该如何运动?

磁头延伸找到对应的扇区的磁道或柱面,磁头就可以不用移动了!磁头就直接在旋转的盘面上,找到对应磁道的对应扇区!找到对应的扇区就可以做对应的写入操作

所以磁盘的旋转和磁头的摆动,就可以解释的清除了!磁头用于定位到数据在磁盘中的哪一个磁道或柱面中,找到就可以不用移动了!此时磁头位置扇区不一定是目标区域,就可以等待盘片旋转到对应的扇区!

定位一个扇区

我们此时就要最上面和最下面的磁道,所以要如何确定拿到那一面?

我们定位需要确定柱面(Cylind)、磁头(Head)、扇区(Sector)
1、传动臂定位到确定的柱面
2、选择需要的磁头,选中的磁头,即代表需要当前磁头所以在面的扇区!
3、等待盘面旋转,找到对应的扇区

有了上述的三个参数,这样就可以定位确定的具体扇区的方法叫做:CHS
所以磁盘的效率,与磁盘的转速,摆动臂的摆速,有关系!

磁盘的容量 =磁头数 X 磁道(柱面)数 X 每道扇区数 X 每扇区字节数

文件在磁盘中的存储

文件 = 内容 + 属性
内容和属性都是数据!那么都需要被存储在磁盘中!所以文件存储,在磁盘角度来看,在磁盘中,把文件存储在多个扇区中!我们有对应扇区的CHS地址,我们就可以找到对应的扇区,把文件拼接出来!

磁盘的逻辑结

理解过程构


这个东东是磁带!
这个磁带卷起来不就是圆形吗?同心圆!磁带拉出来,就是一个线性结构
磁盘的盘面不就是类似的吗?盘面由一系列同心圆构成的!这就是卷起来的磁带吗!
那我们把这个同心圆都拉出来,不也可以抽象为一个线性的结构吗!
而这样每个面都抽象成一个线性结构并链接起来!这样磁盘不就抽象成一个线性结构吗!

即 磁盘,在我看来,就是线性结构!

而磁盘抽象为线性结构,这个线性结构的基本单位扇区
现在定位一个扇区,用几个数据?——一个!
通过数组下标找到扇区!
以此,OS对磁盘的管理,一瞬间就变成了对数组的管理!(先描述再组织)
但是对应磁盘来说,查找扇区还是要用CHS来,所以要在内部封装CHS!但是逻辑上我们磁盘就是线性的,找扇区就是通过数组下标!

磁盘被抽象为一个基本单位为扇区的线性结构,这样每一个扇区,就有一个线性地址(其实就是数组下标),这种地址叫做LBA(Logical Block Address)

LBA和CHS之间要能够相互转换!

而OS也不需要保存每一个扇区的LBA地址,因为在开机的时候OS获取磁盘的容量,就可以找到磁盘中所以扇区的LAB地址了!总容量 / 512,获得LBA的下标最大值,[0,总容量/512]这个地址范围,类似于虚拟地址空间,虚拟地址空间是通过页表找到对应的实际地址,而LBA是通过LBA和CHS的转化来找到对应分区的CHS,再有硬件完成!

真实过程

我们知道传动臂上的磁头是共进退的!
柱面就是每一面上相同半径的磁道组成逻辑层面的柱面!

所以物理磁盘可以分为很多面!纵向以柱面为单位的面!
所以逻辑上,磁盘整体是由多个柱面卷起来的!

所以磁盘的真实情况是:
磁道
某一盘面的某一磁道展开:

一个磁道展开,就是一个一维的数组!

柱面
整个磁盘的所盘面的同一个磁道,即柱面展开:

柱面上的每一个磁道,扇区个数都是一样的
即柱面展开是一个二位数组

整盘

整个磁盘就是多张二维的扇区数组吗?(就是一个三维数组)
以柱面的视角把磁盘抽象为一个三位数组!

所有,寻找一个扇区,我们先找到那个柱面(Cylinder)!再确定那个磁道(等于确定磁头Head),再确定那个扇区(Sector)!所以就是CHS!
这个查找的顺序,与三维数组结果相对应!CHS就是这个三维数组的下标!

对于C++来说!无论几维数组,其实都是一维数组!

相比于之前以扇区为单位的线性排列,现在以柱面为单位的线性展开更加好!
我们不再需要讨论磁盘有多少个盘面,而是考虑磁盘有多少柱面!
现在数组的下标就是LBA!也叫线性地址!

OS只需要使用LBA,但是LBA地址与CHS地址之间谁转换呢?答案是磁盘自己,固件(硬件电路,伺服系统)

CHS && LBA 地址

LBA转成CHS

OS向磁盘提供了一个LBA地址,磁盘要把LBA地址转换成CHS地址!如何操作呢?

1、要先计算LBA地址在那个柱面上!

  • 柱面号C = LBA // (磁头数 * 每磁道扇区数)【就是单个柱面的扇区总数,//表示除法取整】

    类似于此图,LBA地址除于柱面扇区总数,获得为1,说明在1号柱面!

  • 磁头号H = (LBA % (磁头数 * 每磁道扇区数)) // 每磁道扇区数

    LBA % (磁头数 * 每磁道扇区数),这个是计算,从当前柱面开始到目标扇区的个数!如上图,是柱面1的下标5位置! 在// 每磁道扇区数,可以获取第几号磁头(第几号盘面!),本图可以知道在1号磁头位置!
  • 扇区 S = (LBA % 每磁道扇区数) + 1
    ( +1 的原因是扇区号从1开始,LBA地址从0开始)

CHS转成LBA

磁头数每磁道扇区数 = 单个柱面的扇区总数
`LBA = 柱面号
单个柱面的扇区总数 + 磁头H * 每个磁道扇区数 + 扇区号S - 1`

对于现在扇区的存储方式,查找一个扇区,对于这个扇区所在的柱面,其之前的柱面内部的扇区都有编号,而在柱面中,这个扇区之前,可能还编入了不同的磁道,也都有编号,找到扇区所来的磁道,这个扇区和磁道之间,还有扇区,所以还是需要记录!

扇区号通常是从1开始的,而在LBA中,地址是从0开始的
柱面磁道都是从0开始编号的

总柱面,磁道个数,扇区总数等信息,在磁盘内部会自动维护,上层开机的时候,会获取到这些参数!

综上,OS访问磁盘,只需要使用LBA地址即可!从现在开始,磁盘就是一个元素为扇区的一维数组,数组的下标就是每一个扇区的LBA地址,OS使用磁盘,就可以用一个数字访问磁盘扇区了!

引入文件系统

引入"块"概念

其实硬盘是典型的"块"(block)设备,块设备支持随机读写!块设备支持以块为单位读取

块设备相对的是字符设备(键盘,显示器),区别是字符设备不支持随机读取

OS读写磁盘并不会一个一个的读取扇区,而是喜欢一次性读取多个扇区,即一次性读取多个块,一个一个读取扇区的效率还是太低了!

OS 文件系统访问磁盘,不以扇区为单位,而是以""为单位,一般是4KB的(可以调整)

这4KB(4096)是连续的8个扇区!

为什么要设置块呢?这样做一次IO访问一次外设,就可以一次访问8个扇区了!
所以计算机是可以通过扇区去访问磁盘的,但是一旦要以文件级别的访问磁盘就要以块为单位去访问了!

上图是上面两个图片,第一个是我们通过柱面抽象出来的磁盘结果,我们通过转换把CHS地址转化成LBA地址,第二个图就是LBA地址的线性结构

而下面的第三个图,是我们以块为单位的块地址线性结构,8个连续的扇区组成块,一次类推
而块地址如何计算呢?我们使用LBA地址 / 8就等于块号!

那么块地址如何转化为LBA地址呢?
公式:块号* 8 + [0,7] = 任意扇区LBA,块号* 8 = 块起始LBA地址

使用文件系统,使用磁盘块,是以4KB为单位的!!!

使用OS向磁盘读取一个块,这个块就会交给磁盘8个扇区,块地址* 8就可以获得这个块扇区的LBA地址了!

所以在OS中,这样有了一个保存文件数据的块了!文件系统中对文件数据的管理转化成了对块的管理!块是否被占用,这就是文件磁盘存储数据的基本容量!

引入"分区"概念

现在我们引入了块的概念,我们现在假如要管理800GB的磁盘空间,我们通过块管理也是力不从心啊,500GB / 4KB个块,数量过大了,量级过大,我们不如缩小区域,把800GB的内存分为三个区域!300,300,200三个区域,
而管理的都是块,我们只要有管理其中一个区域的方法,其他区域也可以用同样的方法来管理!
这样OS对800GB的管理就转化为管理300GB了,降低了管理难度 !这个操作我们称之为分区!

我们自己的C盘,D盘,E盘都是磁盘的分区!!


所以我们现在暂时不管的磁盘管理,我们只需要了解磁盘这300GB空间的管理即可! 剩下的空间也会管理了!
接下来,我们来了解Linux的管理方式!

我们现在管理磁盘的300GB,但是我还是觉得这300GB好大啊!
✨这个时候,我们就对300GB进行分组! 每个组管理30G,那么我们管理好这30G的空间,CtrlCV到其他组中,这个分区不就管理好了吗?以此类推 ,其他几个分区不就管理好了吗?即这样这个磁盘不就管理好了!

所以现在是把管理磁盘的任务转化为管理一个30GB的分组!

这个管理思想就是分治
所以现在管理30GB,不就容易多了吗?

那一个组里面都有些什么东西呢?

上图有简单表述!
我们来简单说说!

引入"inode"概念

文件 = 内容 + 属性
Linux下,内容和属性是分开存储的!

即便是分组了但是基本单位还是4KB!,而上面讲的分区分组,OS会在磁盘中记录分区的起始块号和结束块号到分区表中!

上图中的Data Block其实占用了分组很大一部分空间,我们文件写入的内容都是存储到这个里面的! 这个Data Block内部有很多块组成!我们写入多少KB的数据,就会在内部 以 空间/4KB个块存储!

那文件的属性存储在那里? 在inode Table
Linux中,任何正常文件,都要有自己id属性集合!
文件的属性值肯定会不一样,但是文件都要有对于的属性!
所以在Linux中,文件的属性本质上是一个结构体,内部会记录文件对应的不同属性!文件的属性不一样,但是都要有这些属性!这个结构体的名字就叫inode!
即属性 --> 结构体!
而结构体的内存大小往往是固定的!一般是128字节!
属性的结构体定义好了就插入到inode Table中!
我们这里讲个大体,接下里就讲解细节!

ext2 文件系统

文件系统的基本单位是以分区为基本单位的!比如一个300GB的分区是使用我们ext2文件系统的,另外几个分区使用其他的不同的文件系统,可能是不一样的!
文件系统的载体是分区

所以我们接下来主要讨论一个分区!分区再分治到分组!所以需要了解分组!

文件 = 内容 + 属性在Linux中,文件的内容和属性是分开存储的!

内容是存储在Data Blocks 中的,其中一组中主要内存就是Data Blocks,以4KB为一块为单位存储组内的文件的内容!
为什么这个大小是4KB呢?不适用扇区的512字节不仅仅是因为效率,还和硬件与软件解耦有关系!万一磁盘技术更新了,这样的结果就会带来OS也要一起修改!

而任何文件,不论是什么形态的文件,又会有相同类型的属性!而这些类型的属性,的值是可以不一样的!所以文件的属性就是从内存中产生的一个属性集合struct inode的结构体!之后以二进制的形式写入到磁盘中!
文件的属性的大小是固定的!128字节!

inode 文件属性集合

  • 文件名不会作为属性,保存再文件的inode中!

    属性集合中的类型往往是一个固定的类型,像文件内存是longlong型的,而文件权限为int型的!如果是文件名称,是字符串,字符串的长度难以确定,若设置过大,会造成内存浪费!小了有不够使用!

一个分组内有这么多文件,那么文件属性集合inode结构体要如何管理呢?
在一个组内有一个inode Table结点列表来存储管理文件属性集合! 在分组中,都是以4KB为数据块存储数据,所以在inode Table中也是以4KB的数据块组成的!存储文件属性就是存储数据!
4KB / 128 = 32 ,则一个数据块可以保存32个inode节点!
那么OS从磁盘拿文件属性,那不是要一次性拿32个吗?是的!
OS就是一次性从磁盘中拿32个文件属性,挑需要的文件属性即可!
这也有一定的局部性原理的体现!两个文件的属性集合在同一个数据块中,代表着文件是在短时间内同时被创建的,之后使用就不需要再加载一遍了!

方便在inode table中管理,inode内部会有一个inode_number!inode编号区分文件唯一性!

inode_number

我们要查看文件的inode编号就使用命令ls - li

上图红框就是code.c文件的inode编号!

而inode Table中,每个4KB的数据块是一个二维数组,每一行都存放了一个inode节点!依次排列!

现在分组的管理做完了吗?
并没有!现在只有存储属性和内容的空间,但是没有做任何管理的操作,使用接下来讲解在分组中是如何管理的!我们需要管理数据来管理文件的内容和属性!

Block Bitmap

文件的内容都保存在Data Blocks中!我们要向Data Blocks中写入数据!我们这么知道内部那些数据块是空的那些数据块有数据呢?
我们就需要Block Bitmap来操作!

Block Bitmap是 块位图,其中记录着Data Block 中哪个数据块已经被占用了!哪个数据块没有被占用!

假如我们Data Block 中有100000个数据块!那么我们的Block Bitmap中至少有100000+ 的比特位!100000 / 8 /4096 = 3,所以Data Block只需要3个数据块就可以表示100000个数据块的使用情况!

Data Block中哪个数据块的被占用,对应比特位就为1!未占用就未0!

所以文件内容的写入就是向Block Bitmap对应位置的比特位置1,释放就是将对应的比特位置0

所以Block Bitmap的的大小是根据Data Block的大小来定!

inode Bitmap

inode Bitmap 是inode位图,每个比特位表示一个inode是否空闲可用!用法和Block Bitemap相似!

所以未来申请一个文件,需要申请inode 和数据块,其实做Block 和 inode 的Btimap操作即可!

小问题

我们在计算机中,下载数据缓慢,但是删除却非常快!
下载这些数据,就是文件写入,文件的属性和内容都需要写入到对应的空间中,而下载的数据往往会计较大,几个G,文件创建,inode 的bitmap在新占用的数据块位置置1,而在Block 的bitmap,可能需要几千个数据块置1!
而我们在删除的适合,并不需要去释放inode table和Data block的数据!只需要将对应bitmap的对应数据块的比特位为0即可!之后会覆盖的写入到之前使用过的地方! 即可覆盖的!
即数据根本没有动过!把管理信息删除掉了就行了!
这也是为什么删除文件后还可以回复文件!恢复文件就是将管理信息恢复!

这样,一个组内就可以合理的将文件的属性和内容都存储到对应的合法区域,并且有对应的管理信息来获取对应空间数据块的使用情况!方便管理!

GDT(Group Descriptor Table)

还有一些信息我们没法知道!块组的大小等信息无法获取!
GDT是块组描述符,描述块组的属性信息!我们分区有进一步分组分为一个个的块组!而块组需要一个集合来描述块组的使用情况!

每个块组描述符存储一个块组的描述信息,块组从什么地方开始是inode Table!什么地方开始是Data Blocks,空闲的inode和数据块数量!

Super Block

超级块!这个是什么???

存放文件系统本身的结构信息!会描述整个分区的文件系统信息,主要记录的信息有block和inode的总量,以及未使用的数量!

super Block是描述整个文件系统的信息情况!

绿框数据依次代表的是分区内总共的inode和block数量,空闲的inode和block的数量,第一个block的位置,每一个组内block和inodes的数量!

所以有了super block,我们知道每个组内的inode和block数量就决定了所有信息!

因为组大小是相同的, 每组inode和block也进一步决定了每个分区有多少个组!每个组从什么地方开始
每个组内通过super block中记录每组内的block和inode的大小,进一步确定数据块的大小以及对应的bitmap的大小,GDT的内容!

所以super block代表着整个文件系统的信息!文件系统基本载体为分区,所有super block超级快是代表分区整体情况!

之后我们就可以通过super block 计算出 GDT内属性的信息,根据GDT进一步把他们的bitmap大小计算出来!inode table 和 data block的大小也知道!
所有我们使用一个新的分组的时候,可以通过super block 和GDT把推出对应的管理信息!inode table 和 Data block为空,对应bitmap为0,那这么说我们一个分组要怎么做,那一个分区内的所有分组都可以这么做!我们直接在每个分组内提前拷贝好super block和GDT,这样管理信息以及空间大小都可以设置好!这个操作叫格式化!
所以格式化本质是写入文件系统的管理信息

我们知道格式化后我们的数据都没了!
这是因为我们将管理信息置为0,空间都被清空!

问题

super block 不是被设置在分组的最前面,而是在个别分组内拷贝super block!内容都是一样的!
为什么?
这是为了防止因为硬件损坏而导致数据丢失 ,若是分组内的管理信息丢失损坏,那么也只是单单丢失这一个信息,影响这一个块组!但是损坏的的是super block,那就是影响整个文件系统
所以super block数据安全要求高,只有一份不安全,所以一般会在多个分组内进行拷贝!一旦一个super block损坏了,就可以去其他块组内进行查找修复!
所以super block这么做,就是因为它太重要了

接着讨论细节!

细节
  • inode和数据块,跨组编写的!

    整个分区中若有10000个inode ,那么分区就会平均的把inode分散到分组中!也就是说分区内inode不会重复!

  • inode和数据块,不能跨分区!!

    这里分区就是一个文件系统,所以不同分区之间有自己的一套inode!即分区之间inode是隔离

所以,在同一个分区内部,inode编号和块号都是唯一的

  • inode编号在每个分组是如何设置的?

    我们inode编号不可能每个分组都从0开始吧?我们知道每个分组的inode编号和块编号多少相同的,每个分组内inode编号按顺序从分组中进行排列,我们对查询的编号除于每个分组中inode的数量!可以知道inode编号在哪个分组中,在以这个数取余,就可以知道是对应分组中inode bitmap中的第几个inode编号!

  • 上述查找inode操作是在OS做的!

    我们开机,可以知道磁盘有多少个分区,说明OS有在管理分区!服务器有多个磁盘,也就是有多个分区,OS是需要管理起来的!
    如何管理?先描述在组织!我们开机的时候,我们OS就把分区分组内的管理信息super Block加载到内存,每个分组都把super block加载到内存,加载到内存的super block结构体节点通过指针链接起来,所以OS对文件系统的管理转化为对链表的增删查改!
    其他的管理信息同样可以加载的到内存中!做文件的删除,我们需要把inode和数据块的bitmap,在内存中修改bitmap在一个合适的时机刷新到外设,也就是磁盘中!

  • 所以我们拿着一个文件的inode就可以找到文件的所有内容!
    获取到inode编号,先进行判断是否合法?在inode bitmap中查看是否为1?合法就可以找到对应的文件属性!
    问题

    我们可以通过inode编号找到对应文件的属性,也就是inode节点,但是如何通过inode找到所有的数据块呢?
    inode节点中有一个datablock[N]这个数组存储该文件所有数据块的编号!

所有我们可以通过一个inode找到文件的属性和内容信息了!

我怎么知道,我在哪一个分组里?
你怎么知道,我在分组中位图的哪一个位置!

文件系统划分组,组内inode和数据块相同,通过除运算和模运算就可以获得在哪个地方!

Linux下如何看待目录?

文件名不会作为属性,保存在文件的inode中!
那么文件名保存在那里呀?

我们平时调用文件,都是调用路径+文件名的啊,那里会调用inode!

文件、图片、视频、数据库都是以上面的方式来存储!那么在Linux下,目录是如何存储的?答案是还是用上面的方法存储在磁盘中!
目录也有inode 和数据内容
目录同样在对应的块组中创建,在inode中确定好文件类型是目录
那目录文件存储什么信息?

目录文件内会存储文件名和inode的映射关系!
所有文件名都是存储在所属目录的数据内容当中!
这也是为什么一个目录下,文件名不能重复,inode值为唯一的,所有二者互为键值!

所以在磁盘中保存文件并没有目录的概念了!在磁盘中保存的对象无非就是inode和数据!
在磁盘中目录和其他文件用一样的存储方案来保存!

而磁盘上的文件系统,只认inode!

我们打开一个文件,是先打开文件所在的路径!读取对应目录里面的数据内容,得到文件名和inode的映射关系!根据文件名映射到对应的inode,根据inode进行文件的查找!

所以我们查找文件属性ls -li,实际上是找到文件所在的路径,读取对应目录里面的数据,根据ls -li是查看当前工作目录下所有文件的属性,所有就根据当前目录内容中的inode值,查找到对应的分组,并在inode bitmap找到对应的inode读取并取出文件的数据打印出来,以此获得文件的属性!

现在又有问题!

那不就是我们打开文件,就需要打开对应工作目录?问题是我们找到对应的工作目录在哪里呀?我们要找到该目录并且要获得该目录的inode编码!不如无法获取目录的内容!

我们要如何找到lesson_23呢?这个目录在磁盘中也是一个普通文件,我们打开这个文件也需要这个文件的inode!
我们要如何找到这个文件的inode呢?lesson_23也是一个文件,inode必然有在这个文件的上级目录中存储!我们找到file.c目录就可以找打lesson_23目录文件,那我们要找file.c就需要找到BtyeNote,找到BtyeNote就需要找到zyx,找到zyx就需要找到home,找到home我们就需要找到根目录/,而根目录是开机的时候就默认给打开的!从根目录开始可以反向的找到我们需要的文件!
所以查找/访问任何文件都 必须要有文件的路径
需要对文件路径进行解析,从根目录开始查找对应目录的inode,以此查找才可以找到的目标文件的inode!
不管是相对路径还是绝对路径!
我们使用相对路径,shell也会通过pwd,把相对路径转化为绝对路径!

所以在Linux中,我们访问文件,实际上是获取文件的的路径,在默认打开的根目录下,根据home找到其inode,在home中根据zyx找到其inode,以此类推找到我们要访问的文件的inode!

从/目录开始,进行路径解析,直到找到对应的文件!

现在的问题是,我们查找文件的路径是谁提供的

我们访问文件,我们要知道OS上,用户做任何操作都会通过进程实现!而进程PCB内部有维护cwd,并且bash也会提供当前工作路径!所以文件名是用户提供,文件路径是进程提供的!

ls code.c

这个命令的潜台词就,从ls进程中获取到当前工作路径!从命令行参数中获取文件名,组成目标文件的绝对路径!从根目录开始分析绝对路径,找到我们需要的文件!

访问任何文件,都需要从/目录进行路径解析吗?(目录结构、路径缓存)

我们tree出来的不就是目录结构吗?

我们做路径解析,路径解析的每一步(访问/目录下的home文件)不都是在做磁盘IO吗?
路径解析,我们做磁盘IO,访问每个目录这些的inode加载到内存里,我们做了大量的磁盘IO就是为了做一次文件访问,之后访问别的文件,需要重新进行路径解析,经过相同路径部分我们还需要再次磁盘IO,这样效率不会很低吗?

所以OS在进行路径解析的时候,会把历史访问的所以的目录(路径)形成一颗多叉树,进行保存!!!
——形成Linux系统的树状目录结构!

结论:
磁盘中不存在所谓的目录,在磁盘中统一以普通文件inode+数据块的方式存储的!但是在路径解析的时候,从根目录打开,会在根目录生成某种结点!访问不同的路径,都会生成一个结点,这些访问过的路径结点将会生成一个多叉树!这个多叉树是内存级的,所以我们就把历史访问过的路径全部缓存起来了!
所以之后访问路径,会先搜索这个缓存的多叉树,如果有对应的结点就直接下一个,没有就做补充完善!

所以Linux中目录结构是内存级的概念!这也叫路径缓存
Linux中目录结构是在内存中动态构建的!
开机的时候,目录结构只有根目录,但之后越访问,目录结构就越完善,效率就越高!

我们来操作看一个现象
我们查看/目录下的某个文件,我们第一次查找,会发现在卡住并且一个一个的搜索,当第二次访问的时候,就直接输出出来了!
这就呈现了目录结构的动态构建!

find / -name test.c

第一次查,目录结构不完善,要多次磁盘IO,而第二次,目录结构完善,直接从内存中获取!效率就高了!不需要再进行磁盘IO!

磁盘中没有目录,只是OS在内存中有构建目录树!

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

d_count 是该目录的引用计数
d_inode 是记录该目录的inode编号

d_lru是淘汰列表

所以这个dentry结点既属于多叉树又属于lru,多叉树提供查找功能,lru是提供淘汰功能!当多叉树结点超过一定范围!就会调用lru算法,找出使用最少的结点,进行删除!

struct dentry是内存级的数据,是路径缓存!
其实每个文件都要有dentry结构,包括普通文件!这样被打开的文件,就可以在内存中形成整个树状结构!

树状结点也属于hash,方便查找!

所以这个树状结构,整体构成了Linux的路径缓存结构!打开访问文件,都要先在这个课树下根据路径进行查找,找到就返回属性inode内容,没找到就从磁盘加载路径,添加dentry结构,缓存路径!

现在我们可以串联起这节课的知识了,我们可以通过文件结构系统找到目标文件的inode,通过inode到磁盘中在对应的分区内找到其块组,找到inode结构体,在inode结构体内的datablock数组找到对应inode的数据块!

如何找到对应的分区呢?

我们知道文件系统的载体是分区,inode不能跨分区使用!每个分区内都有相同inode 结构体!那么我们是如何正确的找到分区呢?

挂载分区

我们现在可以通过inode编号,在分区内找到对应的文件!也可以根据根目录文件内容,找到inode编号,但前提是正确的找到分区!

我们在目录中查找一下我们的磁盘!
我们发现磁盘有几个分区就有几个目录!而这个目录还不能使用,是块目录!

我们的分区,一定是要和特点的一个目录进行关联的!->通过进入这个目录,就相当于进入这个分区 -->挂载

通过一个实验来了解:
伪造一小块空间!

dd if=/dev/zero of=./disk.img bs=1M count=5

if 和 of是in和out的操作,input向/dev/zero文件,output到./disk.img,为一个镜像文件,大小为1M,count为5,说这是一个磁盘块,实际是文件快,一个大文件!
而分区不就是一个大的磁盘空间吗?所有这个文件就可以当一个分区来看待!
我们格式化来看看!

我们做格式化mkfs.ext4 disk.img写入文件系统,即写文件系统的管理信息,分组等操作!

我们disk.img已经成功分区了!
我们要如何访问这个分区呢?
cd disk.img //不允许访问,所以需要挂载

看看系统上的挂载情况!
df -h

看看Mounted on
系统只有一个磁盘vda2,所以vda2挂载到根目录下,所以登录系统就使用vda2这个分区!

df -h 查看系统的文件系统,大小,使用情况 ,剩余量,使用率,挂载的路径

我们如何挂载?mount
命令sudo mount -t ext4 ./disk.img ./dir(-t 表示文件系统 , 挂载还是要以root超级用户权限操作!)

我们再来看看挂载情况

成功挂载了!设备名为loop4这个!成功挂载dir目录下!
我们cd 进入dir目录,在内部touch文件,就是在我们自定义的分区里面定义文件了!

我们想取消挂载呢?
umount ./disk.img即可
此时在查看挂载,已经没有了!

所以,我们使用分区的时候,往往都是会把分区mount打破对应的路径上!

分区要与目录相关联,进入目录相当于进入分区!需要挂载!
我们之前在问,通过路径解析找到目标文件inode,但是我们知道code.c在哪个分区?

我们路径解析的时候,这个文件肯定是在某个分区的,而这个分区肯定会挂载到某个路径下的!而我们根据路径前缀,就可以知道是哪个分区了!
所有的分区都和路径有关!

同一个分区内定义的分区,就是以就近原则来确定分区的!

所有在fopen的时候,未提供文件,我们就从bash获取工作目录cwd!OS对cwd获得的文件路径,根据struct dentry多叉树进行路径解析,一个一个的打开目录,找到目标文件,找到文件的inode,根据inode编号找到对应inode结构体,读取并获得文件的属性和内容,在内核创建file结构体,创建struct inode,创建文件file的缓冲区!创建文件操作表,把inode属性进行填充,磁盘属性加载到内存,文件内容全部或部分的加载到文件的缓冲区内,将file结构体的地址分配给文件描述符表,给用户返回文件描述符! 以此用户可以获取文件的内容,属性,缓冲区等信息了!

我们做路径判定并不是每次都要根据路径,还有其他的方法!
我们文件的每个结点被打开,都会创建一个struct dentry结点!这个结点内会有一个super block的指针!super block就会代表着对应的分区!

内核代码

我们在struct file内找到了 struct dentry 的指针!

这个指针就是指向这个文件的dentry结构的!

我们就来看看dentry结构体!

找到dentry就可以找到这个文件的inode!

我们看看inode的结构体

所有一个文件的inode可以通过file结构体内的dentry来找到inode!

我们来实际的操作验证!

我们来验证一下目录文件内容是存储文件名和inode的映射关系!

我们使用opendir 打开一个目录!通过readdir读取dir,用struct dirent结构体来获取readdir返回的信息!
entry从目录中获取的信息,对信息进行判断,对需要的信息,进行,文件名和ino的打印,我们可以看到和ls -li的数据一样吗,所有目录文件存储着文件名和inode的映射关系!

我们又有加载进内存的inode和写在磁盘内的ext2_inode的!我们不需要把磁盘inode的全部属性都加载到内存中!

而在磁盘inode中有一个数组i_block[]数组,这个数组就是inode结点和数据块的映射关系!数组大小默认为15!

一个文件并不是只用这15个数据块存储,才60KB大小的空间呀,肯定不够啊!
这个是如何解决的呢?
我们15个数据块,前12个是直接填写数据块的编号!
后面三个依次是一级、二级、三级索引!我们在这个4KB的数据块中不保存数据块编号的内容,保存的是下一个数据块的地址,每个地址都指向于一个数据块,这样4KB的数据块就可以存储1024个数据块了!

一级有1024个,二级有10241024个数据块,三级就有10241024 * 1024个数据块!
这样文件保存数据的容量就足够大了!

在一个组内,一个文件太大,一个组装不下,可以跨组存储,inode和数据块编号在一个文件系统中是唯一的,全局的!所有可以跨组存储!

总结

我们通过PCB内的files_struct指针找到文件描述符表,在文件描述符表内找到对应的文件file结构体,在结构体内找到path结构体获取到dentry,有了dentry就可以获取inode !
另外就是PCB内部还有fs_struct指针,会指向file system结构体!内部也有path结构体的path、pwd、root对象,获取到对应的dentry可以找到对应的结点信息!

软硬连接

硬连接

ln code code-hard

我们发现code-hard和code.c的inode值是一样的!

  • 硬连接本质不是一个独立的文件,因为没有独立的inode!

我们可以看到,在软连接的时候,code.c和code-soft文件属性中,权限和所属组之间的数据没变过,都为1,但是这里都为2了!

硬连接本质是一组新的文件名和目标inodenumber的映射关系

创建硬连接,本质上就是在lesson_23下新建一个文件名和目标文件inode的映射关系!

那上面的2又是什么呢?

多了一个新的文件名指向目标文件!,使用这个2叫做硬连接数!这个2在inode怎么记录的,我们inode内有有引用计数!一旦有新文件名,建立新的映射关系,在inode引用计数加1 !

用途:

  • 备份重要文件!


    我们在bin目录下对code.c文件做硬连接!
    我们在lesson_23下,把code.c和code-hard删除了,我们认为已经没有code.c文件了!文件已经被删除了!
    但是我们调用bin目录下的code.c.bak,依旧是code.c的内容
    这是因为磁盘中,code.c文件的inode的引用计数未为0,所以并没有被删除!我们调用code.c.bak还可以看到文件!

    所以硬连接有备份文件的作用!

  • ...

    我们创建一个空目录,我们看到这个空目录的硬连接数为2,为什么?
    我们任何一个目录下都会存在一个.!这个点的含义不就是指当前目录吗?凭什么说这个点是当前目录?
    看inode值!

    所以说这个点不就是一个硬连接吗?所以能表示当前目录!

    我们在当前目录下再创建一个目录!此时目录的硬连接数量为3!
    在上级目录创建目录的本文件,算一个硬连接!
    在当前目录的.文件,也算是一个硬连接
    而在新建目录下,每个目录不仅仅会携带.,还会有..,表示上级目录!,对于新建目录来说,上级目录就是我们的目标目录,查看inode值,也可以确定着下级目录中的..也是当前目录的硬连接!

综上...是硬连接!
所以计算一个目录里面有多少个目录,直接硬连接数-2即可

尝试对目录做硬连接操作,会发现不能成功
即硬连接只能给普通文件进行建立,Linux系统,不支持给目录建立硬连接!

软连接可以连接目录!

软连接算是真正的链接文件,其文件类型为link类型的文件!
而硬连接文件也确实应该是普通文件,因为硬连接存储的是普通文件的属性,所有硬连接的属性必然与普通文件相同!

现在有个问题,我们说的...,是对上级目录和当前目录做硬连接!可以我们说硬连接只能对普通文件做,这里有矛盾啊!
这里就是做了特殊操作!
OS只允许自己对文件目录做硬连接,其他人不能做这个操作!
与其说不允许对目录做硬连接,不如说是不允许用户做这个操作!

为什么?

如果我们运行用户做目录硬连接,容易造成路径环问题!

这样就会形成一个环状的图型结构了!在文件操作上会造成影响!

对应...这样的硬连接, 也会造成局部的路径环,所有OS就做特殊处理即可! ———方便用户命令行操作

而软连接形成的环,所有对软连接做文件类型确定l!我们在做遍历的时候,将文件认为是一个正常的文件即可!

软连接


ln -s code.c code—soft

我们调用软连接,可以输出连接文件的内容!

接下来解释一下!

  • 首先根据inode值知道,软连接有独立的inode值,是一个独立的文件!

文件 = 内容 + 属性

软连接使用场景
我们在lesson_23目录下写的code.c文件与可执行文件code,在当前目录下可以直接./使用!
但是在项目环境下,可执行程序不再当前目录下,路径比较深!
我们lesson_23一下有这个写目录,我们的可执行文件code在execute目录下,假如我们要在lesson_23,调用code程序,我们要./bin/exe/excute/code

这样调用太长了,我们软连接ln -s ./bin/exe/excute/code exe就可以获得一个exe指向code的软连接文件!
我们直接调用./exe即可!

这就相当于Windows下的快捷方式!
我们在计算机上下载的软件往往会在C/D盘的深处,我们为了方便用户快速调用,通过软连接的方式快速访问!

软连接属性和正常文件类似,文件类型为l说明是link文件!
那软连接的内容是说明呢?

内容是保存目标文件的路径!

删除软连接,除了rm,还可以unlink XXX

学习了文件系统,我们也能理解很多问题了!

cd指令,做切换目录的工作!但到文件底层来说!cd指令是打开目标目录文件,更改bash的cwd

新建文件,对于文件系统来说,会在磁盘中处理申请对应的inode和数据块区域,将文件名和路径的映射关系写入目录内容中!

删除文件同理,根据当前文件的inode编码找到磁盘中的inode和数据块的位图!将1置为0,并将文件的工作目录的映射关系删除掉!

修改文件即是修改工作目录内容中的映射关系!

文件系统的形成的原因
1、冯诺依曼体系!2、磁盘是机械设备,随机读写次数多了会影响IO效率! 通过内存,将相关数据一次性写入到磁盘,磁盘中,数据也大概率相邻,之后IO可以顺序查找,效率高了很多!

http://www.jsqmd.com/news/1407429/

相关文章:

  • ONNX模型部署实战:从导出报错到Android端量化部署全解析
  • HTTP断点续传实战:从原理到分块上传与状态管理的完整实现
  • 涉及股权、虚拟财产的多类型财产继承纠纷,专业财产继承律所如何梳理遗产范围 - 好物分享知识传播
  • LLM as Judge与Best of N:构建自优化AI代码生成流水线
  • c语言的常见概念和数据类型及变量
  • 异步任务状态机设计:解决图片生成任务丢失与系统可靠性问题
  • 2026年08月移动式防爆吸尘器品牌评测推荐:三个品牌大比拼,哪个更好? - 工业清洁测评社
  • 跨市场量化实战:使用 QuantDash 快速调取沪深 300 / 标普 500 / 恒生指数作为策略基准
  • MCP协议:AI Agent的TCP/IP时刻,从单机智能到网络智能
  • 学 Simulink—— 三相 PWM 整流器开路故障下的容错控制仿真
  • 手把手教你学 Simulink—— 半导体光刻机工件台永磁直线电机的无模型自适应控制仿真
  • 第 5 章 SVPWM 空间矢量调制:FOC 的最后一块拼图
  • 缓冲区溢出漏洞原理、利用与防御全解析:从栈溢出到ROP攻击
  • 基于 QuantDash 5 分钟 K 线的网格交易策略参数网格搜索寻优实战
  • 2026年8月行业内发泡管供应商推荐,海绵管/PE发泡管/地暖保温管/PP发泡管/泡沫棒,发泡管厂商口碑推荐 - 企业权威推荐大使
  • 涉及再婚家庭的多类型财产继承分割,专业律所如何平衡继子女与婚生子女继承权益 - 好物分享知识传播
  • GPT-5.6 Sol、Terra、Luna 怎么选?3 类任务决策表
  • Steve Brunton | Probability Bootcamp | 笔记 | 第四部分:高级统计 | Lecture 36 | 中心极限定理
  • Python 量化实战:如何高效抓取并分类 A 股主板、创业板、科创板与北交所实时行情
  • 某里RAG三面追问:知识库检索不到怎么办?四层兜底架构与工程边界
  • CentOS服务器性能排查与健康检查:从硬件到进程的完整诊断指南
  • Draw.io 高阶技巧:从绘图工具到架构设计与团队协作的生产力引擎
  • Linux TTY中文显示终极方案:Fbterm字体间距优化与配置实战
  • 戴尔灵越14R拆机清灰与SSD升级全攻略:从工具准备到BIOS设置
  • 宜昌老板找代账踩过的坑,我们都帮你收拾过烂摊子 - 二格
  • 每百万Token值多少钱?Kimi K3在真实业务场景中的ROI测算与模型路由策略
  • 新版 Codex App 无法生图的解决方法
  • 装闭 RenoPit 源码解析(11):AI如何进行装修文档交叉核查
  • 阿里巴巴操作泛化面试,少样本学习做崩一件货的事真发生过
  • OnlyOffice私有化部署实战:从Docker Compose到生产环境调优