【Linux】磁盘 | 文件系统 | inode

news2024/12/1 12:59:11

🪐🪐🪐欢迎来到程序员餐厅💫💫💫

          主厨:邪王真眼

主厨的主页:Chef‘s blog  

所属专栏:青果大战linux

总有光环在陨落,总有新星在闪烁


模电好难啊,不知不觉怎么就要期末考试了,还有六级考试,哦哭了


前言 

我们之前讲的文件都是被打开的文件,或者说被加载到内存中的文件,但事实上计算机中没有被打开的文件比被打开的文件数量多得多,操作系统是如何管理这些没被打开的文件的呢?显然不会是一股脑往硬盘里一塞就啥也不管了, 今天我们就来好好介绍一下。


磁盘

在了解硬盘中的文件之前,我们要先来了解一下磁盘

计算机中存储数据有两种方式,,一种是存放在内存中,一种是存放在硬盘中,我们常说的电脑是512+16,手机是256+12,这里的大数字“256”、“512”就是硬盘的空间大小(单位G),小数字就是内存的空间大小(单位G)。

内存的特点叫做掉电即丢失,也就是说当你的电脑没电了那你内存中存储的数据会直接清空,要想保存数据就要一直开着电脑,这显然是不可能的,这时就要让硬盘发挥作用了,硬盘的不仅空间比内存大得多,而且他的数据存储是掉电不丢失的,除此之外价格也便宜很多。

硬盘分为机械磁盘和SSD(固态硬盘)

上图中,左侧是磁盘,右侧是SSD。

  1.  机械磁盘是我们的计算机中唯一一个机械设备,IO交互慢

  2. 固态硬盘IO效率、功耗、噪音大小上明显比机械磁盘更好,

事实上我们现在的PC上大多都是固态硬盘,那么为什么我们还要讲磁盘,而不是SSD呢?

首先虽然性能上有明显差异,但是SSD和磁盘的文件系统大框架是一样的,只是SSD多了些优化。

其次,对于企业来说他们要在服务器上安装硬盘,单块硬盘就是几个TB,并且一台机器可以安装几十块,这些硬盘主要是起到存储数据的作用,对他的IO交互速度、噪音什么的没什么要求,为了最大可能的节约成本,当然就要用机械磁盘了。


磁盘的物理结构

一个磁盘是由多个盘面组成的,我们先单拿出一个盘面讲解

在上图中我们可以看到很多圆环,每条圆环都是一条磁道,在工作时,中心的马达会带动磁盘高速旋转(一分钟几千转),磁头可以在磁头臂的帮助下移动到不同的磁道,这样一来磁头就可以读取任何一张盘面的任何地方了,当然磁头不是贴着盘面的,不然会刮坏盘面,而是以极近的距离挨着它。

机械磁盘是通过磁头在高速旋转的盘片表面进行数据的读写操作。盘片表面涂有磁性材料,磁头可以改变盘片上磁性材料的磁极方向来写入数据。例如,当磁头通过电流产生磁场时,就可以在盘片的磁性涂层上产生对应的磁极排列,这些不同的磁极排列就代表了 0 和 1 这样的二进制数据(例如规定N极为1,S为0)。而在读取数据时,磁头会检测盘片上磁性材料的磁极方向,从而将其转换为计算机能够识别的电信号。

磁盘是由多个盘面组成的,每个盘面都有一个磁头,用于写该面的数据,注意每个盘面正反都可以读写,也都各有一个磁头。

我们将不同盘面的相同大小的磁道看作一起,就可以当作一个柱面。


数据的存储

每个圆环都是一个磁道,而每个磁道又被分为了许多扇区,

扇区是磁盘IO的最小单位,一般固定是512字节

当然请注意,越是远离圆心,一个扇区所占面积就越大,为了使得扇区的数据空间都是512,所以外围的磁密度也会减小。

CHS寻址

  1. 第一步确定在哪个Cylinder(柱面),
  2. 第二步确定是哪个磁道,要用哪个Header(磁头)
  3. 第三步确定在哪个Sector(扇区)

数据的逻辑结构 

相信不少朋友小时候都玩过这个东西,揪着一边然后把它整个磁带全部撤出来,最后自然是按不回去了,不难看出磁带里面也是一圈一圈缠绕的,被扯出来后则是一根直线

事实上,这不就是二维数据转化为一维数组吗,我们的磁盘也是同样,于是乎在逻辑结构上我们可以把它从一个CHS三维的数组,转化为一个一维数组,

注意CHS中计算数量,扇区号是从下标1开始算的,磁道号和柱面号是下标0开始

在这个逻辑结构基础上,先人又发明了LAB寻址,

LAB下标=柱面号*单个柱面的扇区数+磁道号*单个磁道的扇区数+扇区号-1(最后减一是因为LAB下标从0开始算)

但是一个扇区的大小只有512字节,这个单位太小了,经过大量时间后人们发现当OS与磁盘单次OI以1kb、2kb、4kb、8kb为单位时效率高,其中又以4kb最为常用,于是我们接下来都是对4kb为单位的OS进行讨论。

我们把八个扇区看作一个数据块,这个块就是4字节,即OS与磁盘IO的单位 


文件系统

inode

属性会以结构体类型struct_inode的方式被构建出来,一个文件只有一个inode,他是文件属性的集合。

一个inode一般为128字节(某些OS下是256),他的主要内容如下

struct inode {
	unsigned long		i_ino;
	umode_t			i_mode;
	uid_t			i_uid;
	gid_t			i_gid;
	loff_t			i_size;
};
  1. i_ino:该inode结构体的编号

  2. i_mode:该文件的权限和类型(文件还是普通文件或其他的类型)

  3. uid:文件拥有者

  4. i_gid:文件拥有组

  5. i_size:文件大小 

使用指令 ls -i即可查看文件的inode编号

请注意,在linux中文件名属性没有在inode中保存, 


 分区&分组管理 

我们的PC一般是1T或者512G,而服务器可达几十TB,如此巨大的空间会存储大量的数据,那么如何对这么多数据进行管理呢?思路很简单,分治,分而治之。

这里的分区对应的就是我们windows里的分盘,C盘、D盘、E盘,不同的盘可以是不同的文件系统,这样可以防止出现一个文件系统挂了,文件全部出问题的情况

此时我们要做的就是如何管理好一个组的问题了,只要一个组管理好了,其他组就借鉴他的管理方法就可以了。


Group内容

我们先看看一个组里都有什么

 inode Table 

用于存储该组中的文件的inode,

Data blocks

以块为单位保存该组中的文件的内容,一份文件所占用块数是“文件大小/4kb”(向上取整)。

加入一个文件只有一个字节,就是是1/4k(向上取整),要给他分配一个数据块。

如果是大小为6kb,那就是6/4(向上取整),要分配两个数据块。

inode Bitmap&Block Bitmap

我们把inode Table中的空间以一个inode的大小(128字节)分为一份一份的,如何确定某一份空间有没有被使用呢,如何确定Data Block中的一个块(4字节)有没有被使用,靠的就是他们俩Bitmap。

这是两个位图,对于inode Bitmap第i个比特位为0表示第i份空间还没被使用,第i个比特位为1,表示第i份空间被使用了,Block Bitmap的第i个比特位负责表示第i个数据块是否被用了

当然了,虽然我们这里用的是位图,但是我们之前说了磁盘数据的IO要以块为单位,所以哪怕你只修改bitmap中的一个比特位,也要把一个块大小的Bitmap都读进去。

OS在给inode分配空间时,不是一定是连续的,加入第一份空间被使用了,第二份还没有,那么下次存储inode不一定会用第二份,而可能是第三、第五、第一千份空间。


Group Descriptor Table

简称GDT,块组描述符,用于描述块组属性信息

  • 该块组内的可用数据块数量。。
  • 该块组内的可用inode数量。
  • 该块组的块位图和inode位图的位置
  • 该块组的inode表的位置。

Boot block通常包含了启动计算机或其他设备所需的初始指令和数据,这些指令和数据是设备启动过程中最先被加载和执行的部分。我们现在先不考虑他。

Super Block(超级块)

存放文件系统本身的结构信息,是文件系统的核心。
Super Block的信息被破坏,可以说整个文件系统结构就被破坏了
记录的信息主要有:
  • bolck 和 inode的总量,
  • 未使用的block和inode的数量
  • 一个block和inode的大小
  • 最近一次挂载的时间,
  • 最近一次写入数据的时间
  • 最近一次检验磁盘的时间等其他文件系统的相关信息。
  • 还有一些用于管理文件的函数

从功能上来讲,一个分区有一个Super Block就够了,那为什么实际是每隔几个Group就要设立一个Super Block呢?

因为一旦一个Super Block崩溃了,那么他管理的数据都要gg,所以我们不能把鸡蛋都放在一个篮子里。因此每隔几个group就设立一个super Block,正常情况下就一个Super Block负责管理该区,当他出了问题,OS就会把好的SuperBlock复制过去以修复问题,大大提高了文件系统的稳定性。请注意一个区中的不同位置的Super block的数据是完全一样的,

文件系统以分区为单位,不同的分区可以安装不同的文件系统

inode编号是以分区为单位分配的,而不是以组为单位,

即C盘中可能有个inode编号是100,D盘也有个inode编号100,但是不可能在C盘的两个inode编号都是100

每个组的inode在分配编号时只需要确定该组的起始inode编号即可(保存在GDT中),他的编号就等于该组的起始inode编号+i(在该组用的第i份空间),例如她所在组的起始inode编号是1000,它用的是第25份空间,那他的编号就是1025,Datablock也是同理。


inode对Data block的映射

在inode中还有一个元素

__le32	i_block[EXT2_N_BLOCKS];/* Pointers to blocks */

i_block就是一个int类型的数组,他的容量是EXT2_N_BLOCKS,这个宏是15,这个数组的前12个空间存放的就是数据块的编号,显然靠这12编号最多只能存放48个字节的数据

一级间接块索引表指针存储的数据块编号所对应的数据块不是直接存储文件数据的,而是在这个块中存储其他块的编号,一个编号是int类型占四个字节,一个块是4KB可以存1000个块号,通过这1000个块可以存储的数据时4MB

同理 一级间接块索引表指针指向的数据块存的是1000个一级间接块索引表指针,相当于可以存4GB。

三级间接块索引表指针指向的数据块存的是1000个二级间接块索引表指针,相当于可以存4TB。

加入我们没有这些间接索引,那单单12个块很容易就会不够,可如果你开太大比如1200甚至120000个块,那inode本身空间就太大了消耗了很多空间。

如此一来就在保证inode本身所占空间不太大且固定的基础上,基本可以满足各种大小的文件的存储,

或许有同学会问,为什么不直接放个int类型指针,需要几个块就malloc几个int空间来存放

因为mallco分配的是内存空间,我们这里讨论的都是磁盘空间!!

只能说,优雅,太优雅了(间谍过家家音)。


inode编号与文件名的映射

显然我们在通过指令或者代码中的函数操作文件时,使用的都是文件名,那么文件名和inode又有何关系呢?

首先明确,文件名没有存放在inode中。

如何理解目录?

事实上,目录本身也是个文件,

所以它也有自己的inode和data block,我们可以看出在底层磁盘,是不区分所谓的文件类型的,大家都是一个inode+几个block,

目录的内容是什么?

是文件名和inode编号的映射关系 ,当然这个映射是相互的,inode编号也可以映射文件名,这也是为什么一个目录下不会有同名文件。

所以我们我们要在磁盘中找到一个文件,只需要通过他的上级目录和它自己的文件名就可以得到他的inode编号,接着通过编号找到这个inode结构体,结构体里面有对应的数据块编号,如此就得到了文件的数据,即可加载到内存中。

这里我们就知道了,一个文件的文件名是存储在他的上级目录中的。这就是为什么当你没有读权限时无法看到目录中的内容,因为读不了目录文件,所以就无法获得inode与文件名的关系,所以你就访问不了目录里面的东西;没有写权限就是无法将inode与文件名的映射写到目录中

为什么不把文件名放到inode?

编号可以进行加减以实现一些优化,编号的大小比较速度也优于字符串,因此为了提高效率就只用inode了,那么没有用武之地的文件名就没有在inode的存放的必要了。事实上用户id,组id等信息也是以整型的形式存放的,而不是直接存储我们的字符串形式的用户名。而文件名即字符串这种东西是为了方便我们用户的观看和使用才被使用的。可以看出OS为了提高效率真的是煞费苦心。

但是你先别高兴,要从磁盘找到我们的文件,要先找到上级目录拿着它的inode映射去在磁盘找,可是,要找到上级目录得先找到上上级目录啊,诶死循环了。

当然不是啦,根目录是特殊处理的,他的inode和文件名是固定的,所以找到根目录不用再依靠别的文件。这就是为什么我们访问一个文件需要相对路径或者绝对路径,因为要打开一个文件,就要从自身开始层层递归到根目录才可以。


路径缓存struct dentry

他是一个存放于内存的结构

我们现在知道了要找到一个文件,就要从他的根目录开始把他的路径中的文件夹都打开(从磁盘加载到内存),才可以最终获得该文件的inode,进而在磁盘中找到它,但是这样就意味着我们为了找到inode要不断和磁盘做IO去打开文件,效率不忍直视,于是佬们发明了dentry

首先dentry是一种树状结构,它里面有父子节点的指针以便于构造树状结构,它里面存放了一个文件的文件名(字符串)和她对应的inode

他所构建的结构就像linux的tree命令一样

 

最开始这个数据结构就会默认把根节点设置为根目录,之后加入我们要把project1打开,就会打开根目录->home->用户->projcet,此时就会对于每个文件都会创建新的dentry把该文件的文件名和inode的映射关系存放进去,并且把这些节点按照上面的树状结构链接起来,如此一来下次我们再要打开project2,就可以在内存中通过dentry直接获取用户A文件的inode,不用从根目录开始层层推导,新的文件也会带来新的dentry,继续链接到该树状结构上。当然一些dentry路径要是一直不被用就会被OS去掉,以节省空间。

这样我们就把找inode的操作只依靠内存实现了,效率比磁盘高了太多。

至于为什么不直接预加载所有路径,因为占用的空间太大了,等你的内存什么时候扩展到512了再说吧。


文件增删查改

在学习了这些东西后,我们回头在看文件的增删查改。

查找一个文件

如果是对于相对路径,先拼接成成绝对路径,

先去dentry里看有没有对应的缓存,如果有直接拿到inode,如果没有就层层展开,从根目录开始映射文件名和inode编号,最后拿到目标文件的inode

让inode编号和每个组Group的GDT中记录的inode起始编号比较,直到找到该文件所在Group,然后在inode Bitmap中检查该inode编号是否合法(即bitmap的该比特位为1),合法后去inode Table找到inode结构体,通过结构体中的i_block找到文件的数据块,至此,文件的内容(数据块)和属性(inode)就算都找到了。

创建一个文件

OS在inode Bitmap中确定第i个比特位,它对应的值为0,表示inode Table对应位置的inode还未被使用。

使用包括inode编号i在内的信息创建一个inode结构体,把它放到inode table第i份(以128字节为单位)空间,接着计算他的大小需要占用几个数据块(向上取整),通过Block Bitmap给他在Data balock分配n个数据块空间存储内容数据,并将数据块的编号存放在inode的i_block数组中。

如何删除一个文件

先查找到文件的inode编号,接着找到inode结构体获取数据块编号,最后把对应的inode Bitmap的比特位和Block Bitmap比特位都置零即可。

这意味着当你删掉了它后,他的数据还在Data Block中保存,也有被恢复的可能,不过你要是接着又建立了一些文件,占用了原本文件的Data Block就真的恢复不了了。

修改一个文件

先通过查找找到该文件,然后把文件加载到内存,对其内容修改,最后将修改后的数据刷新回磁盘中的文件即可。

-------------------------------写了很久,创作不易,麻烦点赞关注支持一下--------------------------------------

​​​​​​​ 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2251116.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AntFlow 0.20.0版发布,增加多数据源多租户支持,进一步助力企业信息化,SAAS化

传统老牌工作流引擎比如activiti,flowable或者camunda等虽然功能强大,也被企业广泛采用,然后也存着在诸如学习曲线陡峭,上手难度大,流程设计操作需要专业人员,普通人无从下手等问题。。。引入工作流引擎往往需要企业储…

Scrapy管道设置和数据保存

1.1 介绍部分: 文字提到常用的Web框架有Django和Flask,接下来将学习一个全球范围内流行的爬虫框架Scrapy。 1.2 内容部分: Scrapy的概念、作用和工作流程 Scrapy的入门使用 Scrapy构造并发送请求 Scrapy模拟登陆 Scrapy管道的使用 Scrapy中…

洛谷 B3626 跳跃机器人 C语言 记忆化搜索

题目: https://www.luogu.com.cn/problem/B3626 题目描述 地上有一排格子,共 n 个位置。机器猫站在第一个格子上,需要取第 n 个格子里的东西。 机器猫当然不愿意自己跑过去,所以机器猫从口袋里掏出了一个机器人!这…

docker快速部署gitlab

文章目录 场景部署步骤默认账号密码效果 场景 新增了一台机器, 在初始化本地开发环境,docker快速部署gitlab 部署步骤 编写dockerfile version: 3.7services:gitlab:image: gitlab/gitlab-ce:latestcontainer_name: gitlabrestart: alwayshostname: gitlabenviron…

计算机视觉工程师紧张学习中!

在当今这个日新月异的科技时代,计算机视觉作为人工智能的重要分支,正以前所未有的速度改变着我们的生活和工作方式。为了紧跟时代步伐,提升自我技能,一群怀揣梦想与热情的计算机视觉设计开发工程师们聚集在了本次线下培训活动中。…

RabbitMq死信队列(详解)

死信队列的概念 死信(dead message)简单理解就是因为种种原因,无法被消费的信息,就是死信。 有死信,自然就有死信队列。当消息在⼀个队列中变成死信之后,它能被重新被发送到另⼀个交换器中,这个交换器就是DLX( Dead L…

30分钟学会正则表达式

正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符、及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。 作用 匹配 查看一个字符串是否符合正则表达式的语法 搜索 正…

IDEA无法创建java8、11项目创建出的pom.xml为空

主要是由于Spring3.X版本不支持JDK8,JDK11,最低支持JDK17 解决的话要不就换成JDK17以上的版本,但是不太现实 另外可以参考以下方式解决 修改spring初始化服务器地址为阿里云的 https://start.aliyun.com/

Unity类银河战士恶魔城学习总结(P149 Screen Fade淡入淡出菜单)

【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili 教程源地址:https://www.udemy.com/course/2d-rpg-alexdev/ 本章节实现了进入游戏和死亡之后的淡入淡出动画效果 UI_FadeScreen.cs 1. Animator 组件的引用 (anim) 该脚本通过 Animator 控制 UI 元…

IDEA 解决Python项目import导入报错、引用不到的问题

使用Idea 23.1 专业版编写Python项目时,import 导入爆红,无法引入其他package的代码,现象如: 解决方案:Idea表头打开 File -> Project Settring 解决效果:

[NSSRound#12 Basic]ordinary forensics

解压出来两个文件,一个是镜像文件另一个不知道 先查看镜像文件 vol.py -f /home/kali/Desktop/forensics.raw imageinfo再查看进程,发现有个cmd的程序 vol.py -f /home/kali/Desktop/forensics.raw --profileWin7SP1x64 pslist进行查看,有…

uniapp中父组件数组更新后与页面渲染数组不一致实战记录

简单描述一下业务场景方便理解: 商品设置功能,支持添加多组商品(点击添加按钮进行增加).可以对任意商品进行删除(点击减少按钮对选中的商品设置进行删除). 问题: 正常添加操作后,对已添加的任意商品删除后,控制台打印数组正常.但是与页面显示不一致.已上图为例,选中尾…

【Git系列】利用 Bash 脚本获取 Git 最后一次非合并提交的提交人

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

hadoop环境配置-vm安装+麒麟ubantu

一.VM版本 选择16版本,15版本存在windows蓝屏的情况,也不用设置HV等相关设置 激活下载参考下述博客:https://blog.csdn.net/matrixlzp/article/details/140674802 提前在bois打开SVM设置,不设置无法打开新建的虚拟机 ubantu下载…

基于SpringBoot的电脑配件销售系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…

【linux学习指南】详解Linux进程信号保存

文章目录 📝保存信号🌠 信号其他相关常⻅概念🌉在内核中的表⽰ 🌠 sigset_t🌠信号集操作函数🌉sigprocmask🌉sigpending 🚩总结 📝保存信号 🌠 信号其他相关常…

[在线实验]-Redis Docker镜像的下载与部署

镜像下载 dockerredis镜像资源-CSDN文库 加载镜像 使用以下命令从redis.tar文件中加载Docker镜像 docker load --input redis.tar 创建映射目录 为了确保Redis的数据能够持久化,我们需要创建一个本地目录来存储这些数据 mkdir -p datasource/docker/redis 运…

嵌入式QT学习第4天:Qt 信号与槽

Linux版本号4.1.15 芯片I.MX6ULL 大叔学Linux 品人间百味 思文短情长 本章思维导图如下: 不使用 Qt Designer 的方式进行开发,用代码绘界面,可以锻炼我们的布局能力,和代码逻辑能力&#x…

【设计模式系列】解释器模式(十七)

一、什么是解释器模式 解释器模式(Interpreter Pattern)是一种行为型设计模式,它的核心思想是分离实现与解释执行。它用于定义语言的文法规则,并解释执行语言中的表达式。这种模式通常是将每个表达式抽象成一个类,并通…

在开发环境中,前端(手机端),后端(电脑端),那么应该如何设置iisExpress

首先,要想手机端应用能成功请求后端,两个设备至少需在同一个局域网内,且IP地址互通; 因为ajax是http(s)://IP地址端口号的方式请求,但是iisExpress默认是localhost如何解决,并没有IP地址,所以手…