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

文件系统----底层架构

当我们谈到文件系统的时候,最重要的点在于:文件的内容与属性是如何存储在磁盘中的?以及操作系统是如何精准定位到这些文件内容的?在谈及文件的内核前,我们先来了解一下储存文件的硬件-----硬盘

一.理解硬件

首先我们来看一下传统磁盘的基础结构

磁盘是由磁头,主轴,磁盘,磁头臂,马达组成的。类似于针一样的就是磁头,它可以左右移动;中间的圆盘就是主轴 ,会带动磁盘高速旋转;空心圆就是磁盘,里面存储了数据;磁头臂控制磁盘的移动。

基于这种组成结构,那么磁盘是如何运转的呢?

首先计算机只能对二进制进行识别,所以说磁盘当中的信息存储就是用无数的0和1构成的。磁盘在高速旋转中,磁头与磁盘之间是有间距的并不是紧贴在磁盘上。磁盘通过顺时针和逆时针的旋转区别出0和1的信息,磁头通过磁场感知将磁信号转化成电信号。电信号再被传输到内存当中。

那么磁盘是如何存储信息的,下面我们来看一下盘片的区域划分的。

在硬盘中分为磁头,磁道,扇区这三块。如图为三片六面,其中每一面都存在着一个磁头,每个磁头对应着相应的盘面,盘面以圆盘为中心向外存在着一圈一圈的称为磁道, 每一圈磁道之间都有间隙,间隙与间隙之间的范围称为扇区。

在了解了上述三个概念之后,我们还要引入柱体这一概念。

 柱体是根据磁道来确定的,硬盘通常是由多片磁盘组成,有2片4片,每片都存在着上下磁头,其中所有的磁头都有机械轴串起来,所有磁头与圆盘圆心的距离都是相同,这也就意味着,当1号磁头停留在1号磁盘的1号磁道上,那么2号磁头会在2号盘面上停留在1号磁道上,以此类推。该硬盘中这些磁道立体起来就变成了一个柱体。

那么我们应当如何定位硬盘中的任意位置?

我们先从物理结构来理解!

这里我们引入了“CHS定址法”。

通过上文讲的柱体概念我们可以很轻松的理解。首先我们要先确定柱体的半径大小,也就是磁道的编号,对应的(Cylinder)。接着确定柱体高度,也就是磁头的编号,对应的(Header)。最后确认扇区的位置,对应的(Sector)。我们就完成了对硬盘的定位。类似于数学中的三维坐标,这里我们只是相应的更换了坐标系用“CHS”来进行定位。一个扇区大小通常为512字节,我们可以通过一个扇区大小来计算出硬盘的存储量大小。

磁盘容量 = 磁头数 * 磁道数 * 每条磁道扇区数 * 每扇区字节数

接下来我们从逻辑结构来理解!

我们用磁带来类比一下。

 磁带分为左右两边,左边输入右边接收中间识别信息。我们将磁带展开就是一条线性结构。

类比于硬盘,我们将同一条磁道上的每一个扇区划分为一个单元格,我们对其展开,一条磁道上就是一条线性结构,我们可以将其类比于一个一维数组;而在同一个柱面上存在着许多类似的磁道,同样的进行展开,就组成了一个面,我们将其类比于一个二维数组;整个硬盘由许多此类的柱面形成,我们想象成柱面卷成了一个更大的实心柱体,我们将其类比于一个三维数组。所以说,我们可以将硬盘简单的理解成一个三维数组。

 一维磁道展开

 二维柱面展开

三维 柱面合成

 

 我们将硬盘类比于三维数组,通过数组下标我们便可以访问到数组任意位置的信息,将下标信息转化成CHS下标进行管理。

由于每个扇区大小较小,如果系统对一个扇区一个扇区进行管理对于操作系统来说消耗太频繁。通常我们将八个扇区作为一组也就是4KB大小,我们将这八个扇区称为“块”。操作系统通过块号来定位文件,从而屏蔽底层对于扇区的复杂定位,使得我们可以快速找到相应的扇区。

这里我们引入了“逻辑区块地址” 也就是“LBA”(Logical Block Address)

我们将硬盘划分为一个个块,定义为区块1,区块2等等。意味着我们只要知道起始地址,磁盘的总大小,我们就可以定位到磁盘每个单位的下标,再通过CHS进行计算就可获得对应的地址。因此,LBA寻址是对CHS寻址的逻辑抽象,LBA好似一个个门牌号而CHS更像是一间间确定的房间号,前者是对后者的简化和升级。LBA寻址后也需要再转换成CHS寻址确定位置。

二. 文件系统

我们整个磁盘有不同的大小之分,800GB,1TB等等,若操作系统在每次操作时都对整个磁盘进行扫描查找,必定会耗费大量的时间。结合我们的生活实际,通常我们会将一个大盘分为几个小盘,C盘,D盘,F盘,这里就是采用了分治的思想。我们将各个区域分而治之,可以简化管理提高我们操作的灵活性。

在类UNIX文件系统(如ext系列),完成分区之后,操作系统还会对每一个分区进行分组(Block Group)处理,将一个分区进行多个分组。每个组中会包含超级块(Super Block),GDT(Group Description Table),块位图(Block Bitmap),inode位图(inode Bitmap),inode表,数据区。

我们只要能弄清楚一个组中是如何工作的,根据分治的思想我们就能理解整个文件系统是如何运转的。下面我们来理解一下上面的几个概念。

超级块:这是一个存储整个文件结构信息的块,类似于整个文件的地图指南。主要记录Block和inode的总量,使用情况,大小,最近一次挂载的时间,最近一次写入数据的时间。若超级块被破坏了,可以说整个文件系统结构就被破坏了。所以说,通常在每一个组中都会有一份超级块进行备份,以免损坏后文件结构破坏。

GDT:块组描述符,描述块组的属性信息。整个分区有多少个块组就对应有多少个块组描述符,记录inode Table,Data Block的起始位置,空闲inode和Data数量,GDT在每个块组开头都有一份拷贝。

块位图:应用了位图的方式用1表示该数据块被占用,0表示未被占用。

inode位图:表示inode是否空闲可用,对inode是否占用进行映射。

inode表:存放文件属性的表。

数据区:存放文件内容的区域。

我们知道 文件 = 内容 + 属性,文件内容被存放在数据区中,文件属性被存放在inode表中,一个文件可以没有内容,但是一定会有属性。这里的inode相当于一个结构体,存储着文件属性,以及指向该文件数据区的指针。inode结构体中存在一个inode编号,使得操作系统可以定位区分不同的文件,inode结构体内又存在着数据区的指针,可以找到文件的内容,所以说,我们只要知道了inode编号我们就能得到文件的内容+属性。

inode结构体:文件大小,文件所有者和所属ID,文件类型权限,时间戳,指向文件数据块的索引指针(双重,三重指针)

 在知道inode号,我们如何对文件进行增删查改?

增:遍历inode位图找到空闲的空间,分配数据块,更新inode信息。

删:通过inode号遍历到inode,将inode位图从0变为1。

查:用inode号找到inode表,判断用户权限大小,通过inode指针获取数据块内容。

改:定位inode并检查权限,修改数据块内容,最后更新inode表内容。

我们知道Linux下一切皆文件,同样的目录也是文件。inode里面不存文件名,文件名是存储在目录里的,我们在目录下寻找文件,就是因为文件名与inode形成了一层映射关系。因此在同一个目录下不能有两个相同的文件名,因为不能有两个相同的inode出现,所以说 文件名就是与inode对应。

对于一个文件的访问权限,实际就是对于inode的访问权限 ,inode会将权限信息存储起来,即使对文件名进行删除也不会影响到inode的访问权限。

目录名也是一个inode,它也有对应的数据块。我们在目录下寻找文件就是先从根目录开始,向下寻找下一个目录的inode,在目录的inode中再向下寻找,以此类推。我们将这样的过程称为路径解析,为了提高运行的效率。Linux内核中有dentry(directory entry)缓存,可以缓存近期解析的路径信息,这样可以减少操作系统的开销。

下面我们通过一个完整详细的流程来描述用户程序中fwrite()写入数据时,底层发生了什么,以及新文件的更新流程。

#include <stdio.h>
#include <stdlib.h>int main()
{FILE* fp = fopen("newfile.txt", "w");if (fp == NULL){perror("fopen");return 1;}const char* data = "Hello,fwrite!\n";size_t wrote = fwrite(data, 1, sizeof(data) - 1, fp);if (wrote < sizeof(data)){perror("data");return 1;}fclose(fp);return 0;
}

一. fopen阶段

首先fopen()是C标准库函数,不直接完成创建,它会调用系统调用(open())完成底层操作。fopen()根据"w"权限,会调用open(“newfile.txt”,O_WRONLY|O_CREAT|O_TRUNC,0666)。若文件不存在,传入的O_CREAT就会创建一个新文件。

接着内核接收到open()的调用,会进行路径解析。从根目录开始查找文件所在目录,通过dentry和inode表来找到该文件的inode。若该文件不存在,就要重新进行资源分配。

若文件不存在,那么就要为这一新创建的文件进行inode和Block的分配。先查找inode位图,找到空闲的inode号,给新文件使用。在inode表中初始化该文件的属性,此时不需要立即分配数据块,数据块将在后续写入后进行分配。

此时内核需要对当前文件的目录添加该文件的inode号,使目录的数据块对于inode号产生一个映射。

最后返回一个FILE*对象(struct file),并在进程中记录。open()返回文件描述符fd,fopen()接受到fd后为其分配FILE*结构。

二. fwrite()阶段

用户将要输入的信息用data保存并交给fwrite(),fwrite()将内容拷贝到用户缓冲区中,此时并未真正输入,当用户调用fflush()或者缓冲区满了,才会将数据刷入到系统当中。

当内容刷入到内核时,此时会进行系统调用write()。

先通过fd找到struct file对象,通过结构体找到对应的文件inode。此时需要检查inode的数据块指针,若未分配数据块,此时先在数据位图上找到一个空闲的位置进行分配,然后更新指针信息到inode结构体中。

将内容拷贝到缓冲区中,更新inode。

三. fclose阶段

先调用fflush()将用户缓冲区刷新,随后调用系统close()关闭文件描述符,内核将减少file对象的引用计数,如果是最后一个关闭的将释放file对象。inode和dentry会保存在缓存中一段时间方便下次寻找。

三. 软硬链接 

我们通过 In  【选项】  源文件   目标文件    来创建软硬链接

若表示创建软链接选项为   -s

1. 软链接

当我们给一个文件添加软链接时,会产生一个新的独立文件,当然这个独立文件也有自己的inode。

软链接是对文件的一个拷贝,相当于我们计算机桌面的快捷方式一样。

2. 硬链接 

硬链接是对文件inode进行拷贝,它没有独立的inode,它与源文件同用一个inode。当我们对一个文件进行硬链接时,会给该文件进行引用计数加一,当我们删除了其中一个文件名时,引用计数相应的减一。由此我们可以知,硬链接是对文件进行备份处理

http://www.dtcms.com/a/271981.html

相关文章:

  • 如何处理mocking is already registered in the current thread
  • IDEA 安装AI代码助手GitHub Copilot和简单使用体验
  • Apache http 强制 https
  • 百度文心ERNIE4.5部署与性能白皮书:FastDeploy加速方案+全系列模型实测数据对比
  • DVWA靶场通关笔记-弱会话IDs(Weak Session IDs Medium级别)
  • mmu 是什么?core和die是什么?
  • 计算机网络实验——无线局域网安全实验
  • UE 植物生长 Motion Design
  • 深度学习-正则化
  • 【SkyWalking】服务端部署与微服务无侵入接入实战指南
  • 【spring boot】三种日志系统对比:ELK、Loki+Grafana、Docker API
  • 【世纪龙科技】汽车信息化综合实训考核平台(机电方向)-学测
  • 零基础入门物联网-远程门禁开关:云平台创建
  • selenium中xpath的用法大全
  • anchor 智能合约案例5 之 vesting
  • 汽车加气站操作工历年考试真题及答案
  • CSS表达式——下篇【selenium】
  • WebSocket实战:实现实时聊天应用 - 双向通信技术详解
  • 【C++】——类和对象(上)
  • C 语言基础:操作符、进制与数据表示通俗讲解
  • AI【应用 03】Windows环境部署 TTS CosyVoice2.0 详细流程记录(Matcha-TTS、spk2info.pt等文件分享)
  • Qt中处理多个同类型对象共享槽函数应用
  • git多分支管理
  • 缺陷的生命周期(Bug Life Cycle)是什么?
  • Java 正则表达式白皮书:语法详解、工程实践与常用表达式库
  • WWDC 25 风云再起:SwiftUI 7 Charts 心法从 2D 到 3D 的华丽蜕变
  • 【HarmonyOS Next之旅】DevEco Studio使用指南(四十二) -> 动态修改编译配置
  • 全面解析 wxPython:构建原生桌面应用的 Python GUI 框架
  • 【计算机基础理论知识】C++篇(二)
  • [python] 数据拷贝浪费内存,原地修改暗藏风险:如何平衡内存使用效率与数据完整性?