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

【Linux系统编程】Ext系列文件系统

目录

磁盘文件系统的必要性

认识磁盘结构

理解硬件

磁盘的物理结构

磁盘的存储结构

磁盘的逻辑结构

引入磁盘文件系统

引入"块"概念

引入"分区"概念

引入"分组"概念

ext*系列文件系统

inode、inode Bitmap、inode Table

Block Bitmap、Data blocks

Boot Block

Group Descriptor Table 

Super Block

理解文件系统

分组的初始化

inode与block的映射

目录文件与普通文件

硬件与软件的关联

挂载


磁盘文件系统的必要性

之前说的都是被打开的文件,并从内存角度了解,是内存级文件;没有被打开的文件,是放在磁盘上的,是磁盘级文件。我们将数据保存在文件中,就是为了以后访问里面的数据,也就需要访问文件,访问文件就要打开文件,而打开文件的前提是能找到这个文件。

为了能够找到文件,就必须保证每一个文件都有唯一的路径。只有路径是不够的,有些文件是只读,有些是只写等等。为了能够更好地管理磁盘里的文件,Linux中有一个磁盘文件系统。举一个例子,菜鸟驿站是存放快递的地方,为了方便用户取快递,不能直接将快递堆放在里面,这样不利于取快递,所以里面放置了很多快递柜,并且这些快递柜的带编号的,当用户的一个快递存放在其中后,用户就会得到一个取件码,例如22-7-0909,用户就可以根据这个取件码快速地找到自己的快递。这就是一个快递系统,取件码就是路径,但是这个快递系统只有路径是不够的。若菜鸟驿站的容量是1万个快递,现在来了2万个,要怎么办呢?快递丢了怎么办?1号快递柜总是很满,2号总是很空怎么办?所以快递系统不仅仅要维护各个快递的编号,还要对菜鸟驿站内的所有快递进行安全性、可靠性的各种维护。文件系统也是一样的,可以通过路径找到文件只是一个最基本的情况,若是文件丢了呢?文件被误删了呢?磁盘空间满了呢?磁盘空间中有些空间被占满,有些空间却很空要怎么办?对于这些,文件系统都需要进行管理。

只要文件在磁盘上被管理好了,就可以提供出路径来定位文件,只要能够定位这个文件,在系统层面上就能打开这个文件,然后就能转化到内存上,在内存上对这个文件进行访问和操作。所以,在磁盘上将文件管理起来是后序所有文件操作的基础

现在我们学习的是Ext系列文件系统,实际中的文件系统种类非常多,就像快递系统不只有菜鸟驿站一种一样。

认识磁盘结构

我们将要学习的磁盘文件系统是操作系统用于管理磁盘存储的一种机制,磁盘文件系统是运行在磁盘之上的软件层,负责将原始空间组织成可管理的文件和目录。所以,我们在学习磁盘文件系统之前,是需要先了解一下磁盘的结构的。

理解硬件


上面看到的这个图片就是磁盘,这是机械磁盘。现在笔记本当中使用的大多是SSD固态硬盘。机械磁盘是一个机械设备,并且是一个外设,所以是非常慢的。优先的容量大,价格便宜。


这是磁盘打开盖子后的样子。反光的东西叫做盘片,数据就保存在这里面。


这个是服务器,将这上面的槽打开就可以将磁盘放到服务器内部。一个磁盘大约是4T或8T,一个服务器可以放几十个磁盘,也就是说一个服务器上大约能存放100多T的数据。


这是服务器的机柜,可以将服务器放在其中,一个机柜一般可以放入多个服务器


服务器机柜背后会走线,就会把网线插进来。网线插进来后,未来就可以通过网线来访问服务器


多个机柜组合起来就是机房,机房就可以接入到运营商的带宽当中,就可以从外网直接访问机房了

1.关于机房
机房的面积是很大的,所以一般会建立在地块相对较便宜的地方。机房当中会有非常多的机器,所以就需要考虑散热的问题,就需要在机房中装空调,所以一般会将机房建立在比较冷的地方,并且机房一旦建立,里面的服务器是不会休息的,需要一直开机工作,所以会有非常大的电力成本,所以一般会将机房建立在里发电场较近的区域

2.关于磁盘
磁盘是只采用二进制来存储数据的,我们可以通过磁铁来理解,磁铁是分南北级的,可以将磁盘上的盘片理解成是上百亿个小磁铁构成的,并且向外只展现出一级,可以认为小磁铁的北极是1,南极是0,这样,磁磁盘就可以进行01保存了。所以,计算机只认二进制,但是在不同的硬件上有不同的物理特性。磁盘可以像我们上面那样理解,向磁盘写入就是改变一个小磁铁的南北极。将磁盘清空就是将所以小磁铁设置成南极

我们平时在网上访问各种网页、注册信息、写的评论等,都保存在各个公司的后台服务器里,若这台服务器下架了或者坏掉了,但是里面的磁盘里仍然保存着非常多的数据,国家规定,没用的磁盘必须对里面的数据进行销毁,以防止信息泄露,怎么销毁呢?不能直接对磁盘格式化或者将文件
全删了,因为基于文件系统的删除都是可以恢复的,可以将磁盘退磁;也可以使用软件

磁盘的物理结构


向磁盘进行写入时,磁头就是笔,磁盘就是纸。

磁盘会有两种运动,盘片会在主轴的带动下,顺时针高速运转(也可能是逆时针,主要看磁盘的设计),磁头会在左右方向上来回摆动。摆动、旋转未来是可以支持寻址的。磁头和盘片之间是没有挨着的。所以磁盘在没有打开时必须是真空环境的。并且在这个真空环境下,不能有杂质,就算是一粒灰尘,在高速旋转之下也可能会刮花盘片。若电脑使用的是机械磁盘,不建议经常搬动电脑,因为这样可能会导致磁头来回晃动,从
而刮花盘面,就会导致数据在硬件层面的丢失

磁盘的存储结构


上面说过磁盘会绕主轴顺时针旋转,实际上,磁盘是由一片片平行的盘片构成的,这些盘片绕着主轴一起旋转。每个盘片都会有正反两面。在每个盘面上,都会有一条一条的磁道,每个磁道又被划分成一个一个的扇区。这些扇区就是用于存储数据的。所以,一个盘片的正反两面都是可以用于存储数据的。在图上,一共有3个盘片,6个面,者6个面上半径相同的磁道共同构成了一个逻辑上的结构 --- 柱面。


通电后,磁盘会绕主轴按照顺时针的方向旋转,同时,磁头会左右摆动。磁头摆动的本质是在定位磁道或者柱面;磁盘盘片旋转的本质是在定位扇区。所以,只要通过磁头摆动找到特定的磁道,再通过盘片旋转找到特定的扇区就可以保存数据了。扇区是磁盘存储数据的基本单位,一般是512字节。所以,若想向磁盘中写入数据,本质是向磁盘的一个扇区或多个扇区中写入;那怕只是想修改一个扇区中的一个比特位,也必须将一个扇区512字节的所有内容都读到内存中,把对应的比特位设置好,再将这512字节的数据重新写到磁盘的特定扇区之中,所以,我们在读写磁盘时并不是以字节为单位,而是以512这一个小块为单位,所以磁盘是块设备。之前说过键盘、显示器是字符设备,读写时是以字符为单位的。


对于每一个盘面,都会有一个磁头。所以当要读写某一个文件时,需要先确定读写哪一个磁头,本质就是读写哪一个盘面。注意:磁头有很多个,但是传动臂是只有一个的,所有的磁头都是共进退的,所以读写磁盘时是以柱面为基本单位的。

我们对上面出现的概念进行汇总

  • 磁头(head)数:每个盘⽚⼀般有上下两⾯,分别对应1个磁头,共2个磁头
  • 磁道(track)数:磁道是从盘⽚外圈往内圈编号0磁道,1磁道...,靠近主轴的同⼼圆⽤于停靠磁头,不存储数据
  • 柱⾯(cylinder)数:磁道构成柱⾯,数量上等同于磁道个数
  • 扇区(sector)数:每个磁道都被切分成很多扇形区域,每个磁道的扇区数量相同。扇区是从磁盘读出和写⼊信息的最⼩单位,通常⼤⼩为 512 字节
  • 圆盘(platter)数:就是盘⽚的数量
  • 磁盘容量=磁头数 × 磁道(柱⾯)数 × 每道扇区数 × 每扇区字节数
  • 细节:传动臂上的磁头是共进退的

既然扇区是磁盘存储数据的基本单位,那读写文件时首先需要找到文件,本质就是找到这个文件对应的1个扇区或多个扇区。如何定位一个扇区呢
可以先定位磁头(head),再确定磁头要访问哪一个柱面(磁道)(cylinder),最后再定位扇区(sector),上面这种定位扇区的方法称为CHS地址定位法


也就是说,磁盘内部有多少个磁头,多少个柱面,多少个扇区,在磁盘内部都是会被记录下来的。
我们未来也可以运行操作系统访问硬件,来获取磁盘本身的一些参数,像我们未来想知道的一些参数,在磁盘出场的时候就已经内置了。可以通过磁盘的驱动程序来获取参数

磁盘的逻辑结构


磁带里面的带子就是保存数据的。通过两个圆盘,将一个圆盘上的磁带转动到另一个圆盘上,通过左下角和右下角两个白色转轮将磁带拉直,中间黑色的东西就可以读取带子的数据。我们可以将原先是圆形的带子拉直形成直线型的带子,同样也可以将磁盘中盘片上的磁道从圆形理解成直线型

我们将磁带拉直:

我们将一条磁道拉直:

拉直后,定位每一个扇区就不再需要使用CHS地址定位法了,而是可以直接使用数组下标。注意:磁头和磁道的下标是从0开始的,但是扇区的下标是从1开始的。这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),这种地址叫做LBA定位寻址法。但是,LBA地址只是逻辑上的,真正要定位一个扇区还是需要使用CHS。

上面我们是将一条磁道拉直形成一个数组,我们也可以将多条磁道,甚至是不同盘面上的磁道拉直拼接,这样我们就可以将磁盘上所有的扇区抽象成一个一维数组了

真实过程

我们前面说过,传动臂上的磁头是共进退的。所以,我们在找磁道时,实际上找的是一个柱面。柱面是⼀个逻辑上的概念,其实就是每⼀面上,相同半径的磁道逻辑上构成柱⾯。所以,磁盘物理上分了很多面,但是在我们看来,逻辑上,磁盘整体是由一个一个的“柱面”卷起来的

所以,磁盘的真实结构是:
磁道:某一盘面的某一磁道展开

即一维数组
柱面:整个磁盘所有盘⾯的同⼀个磁道


柱面上每个磁道的扇区个数是一样的。这就是二维数组。
整盘:磁盘是由一个一个的柱面卷起来的

有多张二维数组,就是一个三维数组

在上面,我们通过将一条磁道抽象成一个一维数组,扇区抽象成一维数组中的元素,可以发现一个柱面就是二维数组,整个磁盘就是三维数组。我们在学习C/C++时,会知道并不存在多维数组,全部都是一维数组。所以,我们可以不使用三维数组的下标去定位一个扇区,而是使用一维数组的下标去定位一个扇区。这就是LBA

LBA地址只是逻辑上的,真正访问一个扇区还是需要使用CHS地址。所以需要看两者的相互转化。CHS转化成LBA,LBA转化CHS都是由磁盘自己做的。未来操作系统只需要使用LBA,磁盘再将LBA转化成CHS。现在操作系统访问磁盘,只需要知道磁盘的总容量和一个扇区的大小,操作系统就可以计算有多少个扇区,就能瞬间知道所有扇区的地址。所以,此时可以在操作系统内部维护一张位图,有几个扇区就弄几个比特位,当扇区被占用可以使用1,没用被占用可以使用0,就可以做磁盘管理了。

CHS与LBA的相互转化

CHS转LBA本质上是三维坐标转化成一维坐标,LBA转CHS本质上是一维坐标转化成三维坐标

注意:磁头和磁道的下标是从0开始的,但是扇区的下标是从1开始的。

CHS转LBA:

  • 磁头数*每磁道扇区数 = 单个柱⾯的扇区总数
  • LBA = 柱⾯号C*单个柱⾯的扇区总数 + 磁头号H*每磁道扇区数 + 扇区号S - 1
  • 即:LBA = 柱⾯号C*(磁头数*每磁道扇区数) + 磁头号H*每磁道扇区数 + 扇区号S - 1
  • 扇区号通常是从1开始的,⽽在LBA中,地址是从0开始的
  • 柱⾯和磁道都是从0开始编号的
  • 总柱⾯,磁道个数,扇区总数等信息,在磁盘内部会⾃动维护,上层开机的时候,会获取到这些参数

因为扇区号是从1开始的,LBA是数组下标,是从0开始的,所以最后需要-1

LBA转CHS:

  • 柱⾯号C = LBA // (磁头数*每磁道扇区数)【就是单个柱⾯的扇区总数】
  • 磁头号H = (LBA % (磁头数*每磁道扇区数)) // 每磁道扇区数
  • 扇区号S = (LBA % 每磁道扇区数) + 1
  • "//": 表⽰除取整

所以:从此往后,在磁盘使用者看来,根本就不关⼼CHS地址,⽽是直接使⽤LBA地址,磁盘内部自己转换。所以:从现在开始,磁盘就是⼀个 元素为扇区 的⼀维数组,数组的下标就是每⼀个扇区的LBA地址。OS使⽤磁盘,就可以用一个数字访问磁盘扇区了

引入磁盘文件系统

引入"块"概念

操作系统与磁盘进行IO时,若以扇区为基本单位,一个扇区的大小是512KB,这样单次IO的数据量太少了一些。所以,操作系统在文件系统层面上与磁盘进行IO时,是以1KB,2KB,4KB,8KB等数据块为单位进行IO的,Linux一般是4KB,所以,一个数据块包含8个扇区。在文件系统层面上,将扇区弄成一个一个的数据块,也就有了块号的概念。

当知道了块号,就可以块号 * 8 + [1, 8]来获取一个扇区的LBA地址了。若操作系统想访问块1,块1就会被翻译成8个扇区的LBA地址,将这8个LBA地址交给磁盘内部,磁盘就会转化成对应的CHS地址,就完成了对8个扇区的访问。现在,在文件系统层面上,磁盘被分成了以块为单位的一维数组

引入"分区"概念

假设现在有一个500GB的磁盘,操作系统要如何对磁盘进行管理呢?
磁盘空间太大了,我们可以将磁盘划分成多个区域,称为"分区"。在我们的电脑上,有C盘、D盘就是因为做了分区处理。

现在我们只需要将一个分区管理好,其他的分区都可以按照这个管理方式进行管理了。并且对磁盘进行了分区之后,各个区之间是互相独立的,所以可以给每个区安装不同的文件系统。分区后,一个分区故障后也不会影响其他分区,保证了数据的安全。

引入"分组"概念

就像国家会划分成很多个省,省的下面又会划分很多个市一样,对于每一个分组,又会划分成组。现在,我们只需要管理好一个组,就能将磁盘管理好了。

ext*系列文件系统

通过上面的内容,我们知道了一个磁盘会被划分成多个区,每一个区内又会被划分成多个组。所以,只需要管理好每一个组就能将整个磁盘管理好。并且也知道,一个组内部是由多个块构成的,实际上,一个组内部是会被分成多个区域的,每一个区域由多个块构成,接下来我们要学习的就是了解每一个组内部的各个区域

  • Block Group:ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。
  • 超级块(Super Block):存放文件系统本身的结构信息。记录的信息主要有:bolck和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。SuperBlock的信息被破坏,可以说整个文件系统结构就被破坏了
  • GDT,Group Descriptor Table:块组描述符,描述块组属性信息
  • inode位图(inodeBitmap):每个bit表示一个inode是否空闲可用。
  • 块位图(BlockBitmap):Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用

inode、inode Bitmap、inode Table

我们知道,文件 = 内容 + 属性,而属性也是数据,所以同样需要保存,在ext*系列文件系统中,会使用结构体inode来保存文件的属性。一个文件一个inode,inode是属性数据的集合

一个inode一般是128字节,也可能是256字节,这就与系统有关了,具体看128字节即可。在一个组中,可以能会非常多的文件,有几个文件就会有几个inode,假设我们要新建一个文件,文件系统就会在内存中定义出struct inode,并填入文件的属性值,然后将这个结构体写入到磁盘的inodeTable里面。刚刚说过,一个块的大小是4KB,所以一个块能保存32个inode。

在一个分组中,在这个组内部的文件属性inode都会保存到inode Table中,而一个组内会有非常多的inode,所以,每一个inode都需要有一个编号


-i这个选项是查看文件的详细属性。使用ls -i就可以看到这个文件对应的inode编号,并且这个编号是不会重复的。在Linux下,文件名不再inode中保存。inodeTable里面就是inode的数组,保存的是这个组中所有的文件的属性,这个数组也是以块为单位建立的,占用了多少个块取决于这个组中文件的数量

我们现在知道了每一个文件都会有一个inode,并且这个inode会保存在分组的inode Table中。而这个inode Table中会有非常多的位置。我们如何知道inodeTable中有几个inode已经被分配了?有那些还没有被分配?放入一个inode应该放在哪个位置呢?这就需要看inode Bitmap了,inode Bitmap是一个位图,有了它就能够知道整个inode Table的使用情况了。位图中的值是0表示inode Table中对应的位置没有被占用,1表示被占用了。inode Bitmap需要多大的空间就看inode Table中最多能放入几个inode,一个inode就是一个比特位。inodeBitmap申请空间也是以块为单位的。当我们新建了一个文件,要存放一个inode到inode Table时,是先到inodeBitmap中找到一个空位置,然后置1,再将inode放入inode Table中。我们会从第0位开始找,假设找到第7位是1,那么这个inode的编号就是7,这就是inode编号的来源。所以,inode Bitmap是一个位图,使用一个比特位表示一个inode Table中一个位置是否空闲可用

刚刚说过,磁盘最小的存储单元是扇区,并且磁盘与OS交互的基本单位是4KB,如何将inode Bitmap里面的1个比特位置0或置1呢?inode Bitmap里面的空间也是以块为单位的。假设inode Bitmap中有10000个比特位,大约只需要2KB,也就是只需要一个块。并且要知道,磁盘内部是没办法修改数据的,要修改数据都需要将数据读到内存中,在内存中修改然后再写到磁盘当中。当我们要修改位图时,就会将要修改的那1个比特位所对应的4KB,整个都读到内存当中,在内存中修改后,再以4KB为单位写回去。当然,OS可能会做一些优化,当有大量比特位需要修改时,才会将4KB读到内存中。

当然,一个inode的内容不仅仅只有我们上面列举出来的那些,另外一些重要的内容后面会介绍

Block Bitmap、Data blocks

现在我们已经知道了在文件系统中文件的属性是如何保存的,那文件的内容呢?就保存在Data Blocks中。虽然上面对一个组中空间的分布是那样画的,但是一个组中空间的绝大部分都是Data Blocks。


Data Blocks内部是由一个一个4KB的块构成的,并且这些块也是有块号的。当一个文件有8KB时,是可以存两个块的。所以,Linux下,文件内容和文件属性分开存储

如何找文件?
我们可以拿着文件的inode编号,找到文件的inode,也就找到文件的属性了,至于文件的内容,
在inode中还有一个inblocks[NUM],里面记录的就是这个文件的内容保存在data Bocks的位
置,后面说

如何知道datablocks中那些块被使用了,那些块没有被使用呢?此时就有了BlockBitmap,也是一个位图。一个分组中有了这4个内容,就可以对文件的内容和属性进行保存了。所以,只需要知道一个文件的inode编号,就可以在一个组中找到一个文件了(属性+内容)。当然,使用inode编号去找inode之前,需要先到inode Bitmap中确认这个编号是否合法。删一个文件并不需要清空属性和内容,只需要将两张位图中对应的位由1置0即可。这也正是删除文件后仍然可以恢复文件的原因,只要拿着原先的inode编号就可以恢复。一旦将文件误删了,最好是什么都不要操作,如果将inode覆盖了,即使inode Table和Data Blocks中的数据没有被覆盖,也找不回来了,因为inode已经被占用了;如果将inode Table和Data Blocks中的数据覆盖了,就更找不回来了。我们误删后,要如何知道误删的那个文件的inode呢?在系统日志中会有

Boot Block

每个磁盘在分区时,会有一个区叫做Bootblock,与启动有关。会保存磁盘总容量,每个分区的起始扇区,终止扇区等。我们今天不考虑它

Group Descriptor Table 

对于一个分组整体的使用情况,如总空间,已经被使用的空间,剩余空间等,要如何知道呢?知道了这个就可以根据剩余空间大小,判断一个文件是否能放在这个分区中。所以,每一个组内都有GroupDescriptorTable,称为块组描述符,描述块组属性信息。

Super Block

在每一个组中,还有一个SuperBlock,叫做超级块。这里面保存的是这一个分区的信息,如分区的大小,每个分组的起始块号、终止块号分别是多少等信息。超级块表示的就是文件系统。所以,文件系统是以分区为单位的,一个分区一套文件系统。不同的分区的文件系统是可以不一样的

在一个分区中,并不是所有的分组里面都有SuperBlock,但也绝不是只有一个分组有Super Block。如一个分区有100个分组,可能会有2、3个SuperBlock,并且这2、3个里面的数据是完全一样的。为什么不将SuperBlock放在分组的最开始呢?因为这样如果这个SuperBlock出错了,整个文件系统(分区)就都没了。Linux操作系统是一个分区内有多份SuperBlock,当一个出错了,就可以使用其他的,然后覆盖回来,就可以进行文件系统恢复了。

现在一个分组内的所有内容已经大致介绍了一遍,有一些细节后面说。所以,现在即使一个分组中没有任何的文件,也需要提前划分号每一个区域,并将两张位图中的所有比特位置0,并在GroupDescriptorTable中填入信息,如分组大小,有几个inode,datablocks中有几个块,分组使用情况等等信息。这一系列工作,称为格式化。这是分组的格式化,分区的格式化也是一样的。当我们分好区后,第一件事就是格式化,对分区进行格式化本质上就是对分区内的所有分组进行格式化。格式化就是在向一个分区中写入文件系统

理解文件系统

在上面,我们已经介绍了ext*系列文件系统的所有组成。磁盘会进行分区,每一个分区会进行分组,每一个分组内的各个区域也已经介绍了一遍。但是,还有一些细节需要知道

分组的初始化

分完区后,在分组时,对于每一个组,这个组的Data blocks中会有几个块都是固定的,并且会根据有几个块,以一定的比例分配inode个数。所以,在一个分组中,inode号个数和Data blocks中块的个数都是确定的。既然每一个组都是这样,那么在一个分区中,也就是一个文件系统中,inode号个数和Datablocks中块的个数都是确定的。所以,一个分区最多能够创建多少个文件也是确定的。注意:

  • 不会出现inode号没用完,而Datablocks用完的情况,此时可以跨分组分配资源。
  • 会出现Datablocks没用完,但是inode号用完的情况。此时就无法在这个分组中创建文件了

关于inode

  • inode是以分区为单位的,一个分区一套inode。也就是说一个分区中inode编号不重复。
    inode不能跨文件系统
  • inode分配时,只需要确定每一个组的起始inode编号即可。分区完成后,格式化时就会完成分组此时就会为每一个分组分配inode。并且这个组的起始inode会保存在Group Descriptor Tbale中

关于block

  • Data blocks中块的块号也是以分区为单位统一编号的
  • block分配时,只需要确定每一个组的起始block编号即可。这个起始块号也可以写到Group
    DescriptorTbale当中。

一旦这两个确定好,也就是一个组中有几个inode、几个block确定好,那么两个位图也就确定好了,也就是需要多大空间都确定好了。而SuperBlock和GroupDescriptorTbale是两个固定大小的数据结构,也已经确定好了。此时,一个组内所有的内容都已经确定好了

问题一:在一个组中,是如何分配inode编号的?
只需要拿着在inode Bitmap中找到的空位置的编号,再加上这个组起始编号,就是这个inode的真正编号。这样,在一个分区内都不会出现重复的inode编号

问题二:在一个组内,是如何分别配块号的?
只需要拿着在Block Bitmap中找到的空位置的编号,再加上这个组起始编号,就是这个块的真正编号。这样,在一个分区内都不会出现重复的块号。这样,当一个组内的Datablocks不足时(可能文件变大了),就可以跨组分配块号了。

当我们拿着一个inode编号要查找这个文件时,只需要根据每一个分组的起始inode先确定这个编号在哪一个分组中,然后用这个编号减去分组的起始inode编号,就可以知道在这个分组中位图的位置了,然后到位图中确认这个编号是否有效,若有效,再到inodeTable中对应位置就能找到了。所以,在一个文件系统中,只要拿着inode编号就可以找到一个文件

分配inode,查找inode都与SB和GDT有关。可是一个分区中,会有非常多个分组,要遍历总不能在磁盘上遍历吧?并且在一个分组中新建、删除、查找,这些工作都应该由OS来完成。在一个分组中新建、删除、查找文件等,就是在做文件管理,要进行文件管理,首先要将SB和GDT管理起来。一个磁盘中会有多个分区,每一个分区都会有SB;每一个分区又会有多个分组,每一个分组都有GDT,OS要如何管理文件系统呢?先描述,再组织。只需要在内存中定义structSB,并以链表的形式连接,就可以管理好所有分区;定义structGDT,将一个分区内需要用到的GDT连接,并与这个分区的SB关联起来,就管理好了分组。这样,OS对文件系统的管理就转换成了对链表的增删查改。所以,刚刚说的各种遍历,实际上都是内存级操作。并且文件修改时,也是需要先将磁盘中的文件读到内存中,在内存中修改完再写回内存。OS与磁盘交互的基本单位是4KB,所以从磁盘加载任何数据到内存,都是4KB为基本单位,即使只修改1个比特位

inode与block的映射

前面我们我们给出的inode结构体的图实际上并不完整。还存在一些字段是通过inode,找到这个文件的内容的。

从图中可以看到,inode内部的blocks数组中,有15个元素。当然,并不是所有的文件系统都是15个。前12个是直接映射的,也就是这12个指针指向的Datablocks中的块里面存放的就是文件的数据。剩下3个不是直接映射的。一级间接块指向的Datablocks中的块里面存放的不是文件的内容,里面存放的是块号。这些块号指向的块,里面存放的就是文件的数据。假设一个块号是4字节,一个块就能存放1024多个块号。此时文件大约能保存4MB的数据了;二级间接块指向的Datablocks中的块里面存放的不是文件的内容,里面存放的是块号。这些块号指向的块,里面存放的是一级间接块的块号。此时文件大约能保存4GB的数据了;三级间接块同样如此。

之所以这样设计,是想吧inode设计成固定大小的。inode的大小固定了,就方便对文件属性进行整体加载。通过这样的设计,inode的数据并不一定要保存在inode所在组中,也可以保存到其他组中,此时就将组的设计和文件的存储进行了解耦。

目录文件与普通文件

我们刚刚说过,访问一个文件只需要拿到这个文件的inode编号即可,可是我们之前访问文件使用的一直是文件名,要如何获取文件的inode编号呢?另外,inode里面不保存文件名,文件名又保存在哪里呢?实际上,文件分为普通文件和目录。要了解清楚这个这些问题,就需要知道目录文件中保存的是什么。但是要注意,无论是什么文件,在磁盘底层都是不做区分的,都是属性 + 内容

目录也是一个文件,也会有自己的inode和Data blocks,也就是说,目录也有自己的属性和内容。

可以看到,每一个目录都有自己的inode编号。
那目录的文件的内容是什么?也就是目录的数据块内部保存的是什么?存的是文件名和inode编号的映射关系。所以,我们要获取一个文件的inode,只需要找到这个文件所在的目录,然后拿着文件名就能找到inode编号了。所以,inode中不保存文件名,文件名保存在自己所处的目录的数据块中

重新理解目录的权限---rwx。当对目录没用r权限时,就无法查看这个目录的内容,没有r权限就无法读取这个文件的内容,也就拿不到文件名和inode编号的映射关系,拿不到inode编号也就无法访问文件,更不用说将inode中的部分信息输出出来了。当没用w权限时,就无法在这个目录下创建文件,没用w权限就无法向这个文件的数据块中写入。没用x权限时,无法进入文件,进入文件的本质是打不开文件。

为什么Linux系统要使用inode编号来查文件呢?而不是直接使用文件名来查文件呢?因为文件名是一个字符串,可长可短,而inode就是一个整数,效率更高。Linux系统中不仅仅文件是这样的,记录用户也是这样的,每个用户也有自己对应的uid。字符串永远是给用户看的,对于计算机意义不大

-n是将能显示成数字的都显示成数字。

命令做了什么?会打开当前目录,然后遍历里面的内容,找到文件名和inode的映射关系,拿到所有inode编号,然后根据这些inode编号找到对应的inode,然后将inode中的部分属性和文件名打印出来。

站在用户的角度,打开一个文件需要文件名;站在OS的角度,打开文件需要的是inode编号,要完成文件名到inode编号的转化就需要先打开文件所在的目录,可是目录也是一个文件,要获取里面的内容也需要被打开,此时就需要先打开它的上级目录,获取inode编号,...。所以,要打开一个文件是需要进行逆向路径解析的。

假设我们现在要访问test.txt这个文件,它所处的路径如上图所示。要访问test.txt就需要先打开test_1_21,要打开test_1_21就需要先打开2025,...,最后会需要打开/。而根目录一般是固定的,可以理解成根目录将它的inode编号写到了一个分区的SB中,此时就可以拿到home这个目录的inode编号了,从而路径上所有目录的编号都能拿到了。所以,访问任何文件都必须要有路径,如果没用路径就无法进行路径解析,就找不到这个文件名对应的inode编号。每一个进程都会有CWD,这样能够保证所有的文件都有路径。像open打开文件时,无论是绝对路径,还是相对路径,都可以保证每一个文件都有路径。有了路径就可以进行路径解析了。

OS在进行路径解析时,是正向解析的,刚刚的逆向是想说明路径是需要解析的。假设当前在/home/cxf/2025/test_1_21下,也就是进程的CWD是这个,open("test.txt"),OS会为我们拼接成一个绝对路径。然后从/开始解析,从/的内容中获取home的inode编号,又从home的内容中获取cxf的inode编号,逐层解析下去,直到拿到test.txt的inode编号。

假设访问完了test.txt,就又要访问同级下的文件test2.txt,正常来说又需要进行一次路径解析,但是路径解析本质上就是在不断地访问磁盘文件系统(因为一直在打开目录文件,读取目录文件,都是在与磁盘进行IO),这样效率太低了。所以,Linux操作系统会对路径结构进行缓存

实际上,在磁盘中,并没有路径的概念,只有inode和数据块,也就是只有文件的属性和内容。Linux操作系统,要以什么结构,对路径进行缓存呢?多叉树。多叉树的根结点就是/,当我们访问home时,就会在根节点下面创建一个子节点,里面保存home这个目录文件的名称,和它的inode编号。这个缓存只是一个内存级的缓存。第二次访问时,不需要访问磁盘了,直接访问缓存结构。这颗多叉树的结点是structdentry,是纯内存的内核数据结构。

硬件与软件的关联

我们刚刚介绍了一个文件在磁盘上是如何存储的,但是这些内容与文件描述符、进程又有什么关系呢?我们来看一部分Linux内核源码

通过一个文件的struct file可以获取3个东西:操作表、内核缓冲区、inode。从上面可以看出,每一个文件都会有一个dentry结点,无论是普通文件,还是目录文件。然后通过指针与其他文件的dentry结点构成一棵树。这样,Linux就可以将所有打开的文件以路径的形式管理起来了,对打开文件的管理转化成了对dentry缓存的管理。并且要知道,当一个文件被多个线程时,只需要有一个
dentry结点就行了,dentry结点里面是有引l用计数的。inode也是只有一份的。但是像struct file、操作表、内核级缓冲区就是每个线程都私有一份的

一个文件和文件系统产生关联,真正的宏观认识

Linux内核中会维护棵结点是dentry的树,这棵树最先开始只有一个结点,是/。假设我们现在要打开一个文件test.txt,那么Linux操作系统就会提供一个路径,如/home/cxf/test.txt,然后OS就会去查找这棵dentry树,打开/,然后发现子节点中没有home,就去磁盘中打开home,然后将其缓存到内核的dentry树中,再以同样的方式缓存cxf,通过cxf的内容,就可以知道test.txt的inode编号,假设是1234,然后就会到磁盘的指定分区中,找到编号为1234的inode,创建一个dentry结点,并将这个inode加载到内存中,并创建一个内核缓冲区,将文件内容放到其中,然后给test.txt创建一个struct file,并将上面的那些内容都放入到file中,里面就会有一个指针指向加载到内存中的inode。打开目录文件,如cd,操作也是一样的,还会更新一下进程的CWD。所以,打开文件不仅仅要将内容加载到内存,还要加载属性,并构建路径缓存

挂载

刚刚我们说过,通过文件名和路径解析,就能找到inode编号,就能到分区内找到这个inode,那怎么知道是哪一个分区呢?另外,路径从进程中来,进程的路径从哪里来?在Linux系统中,若有多个分区,那么一定会有一个分区包含根目录,因为根目录在OS加载时就会构建出来(OS加载时已经将/放到了路径缓存中),所以我们可以直接找到这个分区,那其他的分区呢?根目录之下,一定会有若干个普通目录。对于分区,若只有分区这个分区是无法直接使用的,分区必须经过"挂载"到目录上,分区才可以被用通过路径的方式进行访问!

可以看到,当前的OS下只有一个分区。df-h是查看磁盘的详细情况,这里的tmpfs是临时的分区,不用管。Mountedon是挂载,/dev/vda1这个分区挂载到了根目录上。因为这个分区挂载到了根目录上,所以从根目录下访问的所有的路径,访问的全都是这个分区

我们来做一个小实验,我们自己创建一个大文件,将其当成一个分区,向其中写入文件系统,然后挂接到某一个目录下,看看是否能使用这个分区。

dd-以二进制的形式在磁盘中写成一个大文件,这里的disk.iso就是一个大文件,大小是5.2MB
从/dev/zero中读取数据写到disk.iso中,一次写1M,写了5次。将disk.iso当成磁盘空间,并向其中写入文件系统。

这一步就是向这个分区中写入文件系统,也就是格式化。可以看到,写入文件系统时,inode和block的数量就已经确定了。现在,disk.iso就是一个临时的分区。现在这个临时分区还使用不了,因为还需要将其挂载到某一目录下。

我们在/mnt下创建一个目录

这里是将前者挂载到后者

可以看到,挂载成功后,就多出来一个分区

现在我们到这个目录下,在这个目录下创建目录、文件就是在disk.iso的空间中创建了。
cd /mnt/myvda2实际上就是进入了disk.iso这个分区

可以使用umount来删除一个分区,这里删不掉是因为当前正在这个分区下面。我们无法将挂载在根目录下的分区卸载掉,因为我们始终都在根目录下。

所以,想要创建一个分区,需要先申请一块磁盘空间,然后向这块空间写入文件系统(就是初始化),最后将这个分区挂载到某一目录下。经过这三个步骤,才能完成一个分区的创建。

通过这个实验,我们更加清楚了,只有一个分区是没用的,我们必须将其挂载到某一个目录下,才可以实验。并且,进入到这个目录下时,就是进入这个分区。这样,任何一个分区,天然就有了路径。所以,路径一部分是挂载点提供的,一部分是自己创建的,进程就会将其记录下来。所以,如何知道在哪一个分区?每一个文件都有路径,路径的前缀就可以指定在那个分区下。

路径解析时会从根目录开始解析,即使我们访问的文件所在的分区不挂载在根目录下,并不妨碍从根目录开始解析。假设有10个分区,vda1,…,vda10,一般vda1就会挂载到根目录下,在第一个分区中创建普通目录,然后将其他分区挂载到这些普通目录下,这样进入这些普通目录就进入到了其他分区。

task_struct中还有struct fs_struct* fs。struct fs_struct中有struct path类型的pwd,path中就有dentry,从根节点树形遍历,找到的就是pwd。这就是进程的pwd。structpath中还有一个struct cfsmount类型的指针,表示的是这个路径挂载到了那个挂载点

相关文章:

  • Ansible 剧本精粹 - 编写你的第一个 Playbook
  • 告别手动绘图!基于AI的Smart Mermaid自动可视化图表工具搭建与使用指南
  • Vue拖拽组件:vue-draggable-plus
  • 如何设计一个支持线上线下的通用订单模块 —— 面向本地生活服务行业的架构思路
  • Portainer安装指南:多节点监控的docker管理面板-家庭云计算专家
  • docker 部署 gin
  • 模型训练相关的问题
  • CFTel:一种基于云雾自动化的鲁棒且可扩展的远程机器人架构
  • 实现RabbitMQ多节点集群搭建
  • 初学者如何微调大模型?从0到1详解
  • 基于Python与本地Ollama的智能语音唤醒助手实现
  • RV1126-OPENCV 图像叠加
  • Rust 学习笔记:发布一个 crate 到 crates.io
  • 性能优化 - 工具篇:基准测试 JMH
  • 性能优化 - 案例篇:数据一致性
  • NX753NX756美光科技闪存NX784NX785
  • QuickJS 如何计算黄金分割率 ?
  • Microsoft Fabric - 尝试一下Data Factory一些新的特性(2025年5月)
  • 【前端】成长路线
  • day16 leetcode-hot100-32(链表11)
  • 网站建设的审批部门是/百度收录的网页数量
  • 云一网站建设/宁波seo排名优化价格
  • 有个网站是做视频相册的/快速排名软件seo系统
  • 爱爱做网站/seo查询工具
  • 香港公司怎么做网站/网页设计基础
  • 广告设计图案/网站推广优化的方法