目录
3.3.2 GDT(Group Descriptor Table)
要理解文件系统我们先从硬件入手
1.理解硬件
1.1磁盘
机械磁盘是计算机中的唯一一个机械设备,也是一种外设。
以前老式电脑上装的就是机械磁盘,但现代对计算机的速度要求越来越高,现在人们使用电脑基本上都是SSD固态硬盘。SSD固态硬盘并不像机械磁盘需要机械运动,读写速度更快,体积更小,低功耗,耐用,无噪音,且未来还有很大研究空间。所以在桌面领域几乎取代了机械磁盘。
企业级存储则更倾向于使用机械硬盘,因为成本低,容量大,有利于进行大规模的数据存储。

磁盘其实是由非常多个小磁铁拼起来的,都知道磁铁有NS级,我们又知道任何硬件都是只认二进制序列的,那么NS级就可以表示01,所以存储磁盘上的二进制数据,是通过磁头改变某个微小磁性区域的磁化方向存储的。
ps:想要销毁的话用火烧就能退磁。或者将磁盘格式化,也就是全写1或者全写0.
1.2磁盘物理结构



扇区是操作系统访问磁盘设备的基本单位,一般大小的512字节,磁盘也称块设备。
一般来说,内圈和外圈上扇区数量是相同的。外圈周长长,数据密度低,内圈反之,所以如果数据访问高频,应该放内圈,因为内圈更短,能更快访问到。(幻视mc末地岛)
磁盘和光盘不同,两面都能写数据。


相比内存,内存是断电易失存储介质,而盘片是永久性存储介质。
定位扇区:
磁头左右摆动,定位磁道(柱面)。磁头不动时,盘片旋转,定位扇区。
这也就是所谓的CHS定位。(柱面cylinder,磁头header,扇区sector)

扇区是从磁盘读出和写⼊信息的最⼩单位,通常⼤⼩为 512 字节。
磁头(head)数:每个盘⽚⼀般有上下两⾯,分别对应1个磁头,共2个磁头
磁道(track)数:磁道是从盘⽚外圈往内圈编号0磁道,1磁道...,靠近主轴的同⼼圆⽤于停靠磁
头,不存储数据
柱⾯(cylinder)数:磁道构成柱⾯,数量上等同于磁道个数
扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同
圆盘(platter)数:就是盘⽚的数量
磁盘容量=磁头数 × 磁道(柱⾯)数 × 每道扇区数 × 每扇区字节数
细节:传动臂上的磁头是共进退的
1.3逻辑结构
终于看开爱回不来,而你总是太晚明白,最后才把话说开~哭着就我留下来~
相信大家都听过这首《倒带》,倒的什么带,磁带,记得小学一二年级的时候经常用这玩意,要放在一个类似收音机的东西里播放

磁带上面可以存储数据,我们可以把磁带“拉直”,形成线性结构

那么磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在一起的磁带,那么磁盘的逻辑存储结构我们也可以类似于:

这样每一个扇区,就有了一个线性地址(其实就是数组下标),这种地址叫LBA(logical block adress),这样我们只需要一个数组下标就能找到对应扇区。

我们对磁盘展开,是以柱面(磁道)为单位展开的,磁盘在物理意义上分了很多面,但是在我们看来,逻辑上,磁盘整体是由“柱面”卷起来的,就像山楂卷那样。比如像下面这张图,一个柱面->六个磁道为一组。

某一盘面的某一磁道展开,这不就是一维数组

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

柱面上的每个磁道,扇区的数量是一样的,这不就是二维数组么。所以一个柱面展开,就是一个二维数组。
整盘:整盘有多个柱面,多个柱面展开,这不就是三维数组

所以,寻址一个扇区,先找到哪一个柱面(Cylinde),在确定柱面内哪一个磁道(其实就是选择激活哪一个具体磁头(Header)从而确定具体磁道),,最后再确定扇区(Sector),所以就有了CHS。所以为啥有三个呢,就是对应三维数组里的三个坐标。
在学过C/C++数组后,在我们看来,其实全部都是一维数组:

所以,每一个扇区都有一个下标,也就是LBA地址,其实也就是线性地址。那么如何计算得到LBA地址?
CHS&&LBA
OS只需要使用LBA就可以了。LBA地址转成CHS地址,CHS转换为LBA地址都由磁盘固件,和硬件电路自己完成。
CHS转LBA:
磁头数(有多少个盘面)*每磁道扇区数=单个柱面的扇区总数
LBA=柱面号C*单个柱面的扇区总数+磁头号H*每磁道扇区数+扇区号S-1
(ps:坐标总和=(第几个二维数组-1)*二维数组size+(第几个一维数组-1)*一维数组size+数组内下标(因为扇区号从1开始,要减去1))
ps:1.扇区号通常是从1开始的,而在LBA中地址从0开始,所以要扇区号-1
2.柱面和磁道都是从0开始编号的
3.总柱面,磁道个数,扇区总数等信息,在磁盘内部会自动维护,上层开机的时候,会获取到这些参数
LBA转CHS:
介绍://,地板除,也就是向下取整除法
柱面号C=LBA //(磁头数*每磁道扇区数) (就是单个柱面的扇区总数)
磁头号H=(LBA%(磁头数*每磁道扇区数)) // 每磁道扇区数
扇区号S=(LBA%每磁道扇区数)+1
二维数组坐标=坐标总和/每个二维数组size
一维数组坐标=坐标总和%每个二维数组size 找到在二维数组的相对位置,然后除以一维数组size
数组内下标=坐标总和%一维数组size +1 (因为下标从1开始,不是0)
所以从此往后,在磁盘使用者看来,根本就不关心CHS地址,而是直接使用LBA地址,磁盘内部自己转换。所以:
从现在开始,可以理解为 磁盘就是一个元素为扇区的一维数组,数组的下标就是每一个扇区的LBA的地址。OS使用磁盘,就可以用一个数字就能访问磁盘扇区了。
1.4CPU与硬件交互
不仅CPU有寄存器,其他外设也有寄存器,包括磁盘。
CPU寄存器的作用在于临时存放指令,数据和地址
- 寄存器不是缓冲区:
寄存器是CPU直接操作的存储单元,而缓冲区是内核为管理数据流抽象的内存区域。虽然二者不同,但它们会协同工作:
- CPU通过寄存器执行指令,从内核缓冲区读取数据(如
MOV EAX, [buffer_addr])。- 内核缓冲区依赖CPU寄存器进行地址计算和数据搬运(如DMA操作后,CPU通过寄存器校验状态)。

外设寄存器的作用在于控制外设行为,传输数据或查询状态。
CPU向磁盘发送IO方向的请求时——>
1.控制寄存器(告诉磁盘是打算读还是打算写)
2.数据寄存器(告诉磁盘要写入哪些数据)
3.地址寄存器(告诉磁盘要写的LBA地址,磁盘自己通过CHS寻址)
4.结果寄存器(CPU获取IO请求是否成功的状态,比如可能空间不足写入失败)
2.文件系统
2.1“块”概念
一维数组
其实硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,其实是不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,也就是一次性读取一个“块”(Block)
硬盘的每个分区是被划分为一个个的“块”。一个“块”的大小是由格式化的时候确定的,并且不可以更改,最常见的是4KB,即连续的八个扇区组成一个“块”,“块”是文件存取的最小单位。

注:
磁盘就是一个三维数组,我们把它看待成为一个“一维数组”,数组下标就是LBA,每个元素都是扇区
每个扇区都有LBA,那么8个扇区一个块,每一个块的地址我们也能算出来。
知道LBA后,那么 块号=LBA/8
知道块号:LAB=块号*8+n(n是块内第几个扇区)

2.2“分区”概念
二维数组
其实磁盘是可以被分成多个分区的(partition),以Windows观点来看,你可能会有一块磁盘并且将它分区成C,D,E盘。那个C,D,E就是分区。分区本质上来说是对硬盘的一种格式化。但是Linux的设备都是以文件形式存在的,那是如何分区的?
柱面是分区的最小单位,我们可以利用参考柱面号码的方式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码。此时我们可以将硬盘上的柱面(分区)进行平铺,将其想象成一个大的平面,如下图所示:

柱面大小一致,扇区个数一致,那么其实只要知道每个分区的起始和结束柱面号,知道每一个柱面多少个扇区,那么该分区多大,其实和解释LBA是多少也就清楚了。

2.3 inode 概念
数组元素,存储单个文件全部属性
众所周知,文件=数据+属性(元数据)。
我们在使用 ls -l 命令查看文件除了看到文件名,还能看到文件的属性(元数据)。

ls-l读取存储在磁盘上的文件信息,然后显示出来。
这些信息除了用ls -l显示出来,还能通过stat 命令查看更多信息

文件数据都储存在“块”中,那么很显然,我们还必须找到一个地方储存文件的元信息(属性信息,也叫元数据,提到元的都是属性信息),比如文件的创建者,文件的创建日期,文件的大小等等。这种储存文件元信息的区域就叫做inode(索引节点)。

注:
Linux下文件的存储是属性和内容分离存储的。文件属性和内容都存在磁盘中。
Linux下,保存文件属性的集合叫做inode,一个文件,一个inode,inode内有一个唯一的标识符,叫做inode号。
文件属性集合inode代码如下,感兴趣可以看看:


ps:
文件名属性并未纳入到inode数据结构内部。
inode的大小一般是128字节或者256。
任何文件的内容大小可以不同,但是属性大小一定是相同的。
3.ext2 文件系统
3.1宏观认识
我们想要在硬盘上存储文件,必须先把硬盘格式化为某种格式的文件系统,才能存储文件。文件系统的目的就是组织和管理硬盘中的文件。在Linux系统中,最常见的就是ext2系列的文件系统。
ext2文件系统将整个分区划分成若干个同样大小的块组(Block Group),如下图所示:

上图中启动块(Boot Block/Sector)的大小是确定的,为1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。启动块之后才是ext2文件系统的开始。
3.2 块组(Block Group)
ext2文件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。
3.3 块组内部构成
3.3.1 超级块(Super Block)
存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:block和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检测磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个文件系统结构就被破坏了。
超级快在每个块组的开头都有一份拷贝(第一个块组必须有,后面的块组可以没有)。为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的super block信息在这种情况下也能正常访问。所以一个文件系统的super block会在多个block group中进行备份,这些super block区域的数据保持一致,这样如果第一个超级块崩了,还可以利用其他超级块恢复。


3.3.2 GDT(Group Descriptor Table)
块组描述符表,描述块组属性信息,整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储一个块组的描述信息,就像一个元数据目录,比如这个块组从哪个地方开始时inode Table,从哪里开始是Data Blocks,空闲的inode和数据块还有多少等等。块组描述符在每个块组的开头都有一份拷贝。
ps:超级块和GDT看似功能很相同,但并不是同一概念
超级块具有全局性:记录 整个文件系统 的元数据(如总块数,总inode数,挂载时间等)。是文件系统的“总控中心”,破坏后可能导致整个文件系统无法识别。
GDT具有局部性:描述 单个块组 的元数据(如组内空闲块数,inode表位置等)。
每个块组独立维护自己的GDT,用于快速定位组内资源。
超级块就像公司CEO:掌握全局资源(总员工数,总预算),但不关心每个部门的具体运作。
GDT像部门经理:只管理本部门的资源(如空闲工位数,员工座位表),无需知道其他部门细节。
ps:为什么需要GDT?
文件系统通过分块组提高性能和管理效率,GDT作为块组的“元数据目录”,帮助操作系统快速定位组内资源(空闲块,inode表),避免每次从超级块遍历全局,从而减少磁盘I/O开销。

3.3.3 块位图(Block Bitmap)
Block Bitmap中记录着Data Block中哪个数据块已经被占用,哪个数据块没有被占用。
3.3.4 inode位图(Inode Bitmap)
每个bit表示一个inode是否空闲可用。
3.3.5 Inode(Inode Table)
存储所有inode结构体的数组
每个inode结构体存放文件属性 (如 文件大小,所有者,最近修改时间等)
inode号就是数组的逻辑下标,不过是从1开始的
inode编号以分区为单位,整体划分,不可跨分区。
3.3.6 Data Block
绝大部分磁盘分组空间都被这玩意占据
数据区:存放文件内容,也就是一个一个的Block。根据不同的文件类型有以下几种情况:
对于普通文件,文件的数据存储在数据块中。
对于目录,该目录下的所有文件名和目录名存储在所在目录的数据块中,除了文件名外,ls -l命令看到的其他信息保存在该文件的inode中。
Block号按照分区划分,不可跨分区。
3.4 inode和datablock映射
inode 内部存在 __le32 i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
也就是数据块指针数组
就是用来进行inode和block映射的,这样 文件=内容+属性,就都找到了。

小结:
分区之后的格式化操作,就是对分区进行分组,在每个分组中写入SB,GDT,Block Bitmap,Inode Bitmap等管理信息,这些管理信息统称:文件系统
只要知道文件的inode号,就能在指定分区中确定是哪一个分组,进而在哪一个分组确定是哪一个inode
拿到inode文件属性和内容就全都有了。
知道inode号的情况下,在指定分区,请解释:对⽂件进⾏增、删、查、改是在做什么?
理解创建一个空文件


总结一下,就是:内核遍历inode位图找到为0的inode号,在inode表中对应偏移量找到空闲的inode,文件属性记录其中。再查块位图,找到空闲数据块写入文件内容,然后再把块号到inode里建立数据块映射,最后内核在目录文件数据块中添加一个目录项,里边建立inode号与文件名的映射。
理解对文件写入信息
1.通过文件inode号找到对应的inode结构体
2.通过inode结构体找到存储该文件内容的数据块,并将数据写入数据块。
3.若不存在数据块或者申请的数据块已被写满,则通过遍历块位图的方式找到一个空闲的块号,并在数据区当中找到对应的空闲块,再将数据写入数据块,最后再到inode里建立数据块和inode结构体的对应关系。
ps:数据块和inode结构体的对应关系,是通过inode结构体内部的数组进行维护的。该数组一般可以存储15个元素,其实前12个元素直接对应文件使用的12个数据块,剩余的三个元素分别是一级索引,二级索引,三级索引,当该文件使用数据块的个数超过12个时,就可以通过这三个索引扩充inode可以存下的数据块。
理解删除文件
1.将该文件申请过的数据块在块位图当中标记为空闲,可供后续文件使用。
2.将该文件对应的inode在inode位图当中置为无效,清除文件的元数据。
3.从父目录的数据块中删除该文件的目录项。
所以,文件系统并不是真的擦除数据,而是解除inode和数据块的映射关系,将其标记为可复用。(这也解释了为什么删除文件相比下载文件快那么多,也解释了为什么有些软件可以帮助你找回误删的文件)后续新写进来的文件会对数据进行覆盖。
3.5 目录与文件名
我们在访问文件的时候,都是用文件名访问的,不用inode啊?而且文件名不在inode内部,那在哪?其实这一切都和目录有关
目录也是文件,但是磁盘上并没有目录的概念,只有文件属性+文件内容的概念。
目录的属性不必多说(其实就是文件属性),但内存保存的是 文件名和文件名对应inode的映射关系。
所以,访问文件,需要打开当前目录,根据文件名,获得对应inode号,然后进行文件访问。
也就是说,访问文件必须要知道当前工作目录 本质是 必须能打开当前工作目录文件,查看目录文件的内容。这也能解释一系列问题:
问题1:为什么一个目录下不能有同名文件?
——>因为文件名和inode是一一对应的关系(类似键值对)。
问题2:为什么没有w无法创建文件?
——>w权限控制目录的写入能力,没有w意味着我们无法将文件名和inode的映射关系写进去,因此无法创建文件。
问题3:为什么没有r无法查看文件?
——>没有r意味着无法读,本质就是拿不到目录的数据块,拿不到数据块就查看不到文件名和inode的映射关系,找不到inode。
问题4:为什么没有x无法进入目录?
——>没有x意味着没有执行权限,进程无法将当前目录名更新到环境变量PWD中,所以无法进行该操作的话我们就无法进入。
3.6 路径解析
问题:打开当前工作目录文件,查看当前工作目录文件的内容,当前工作目录不也是文件吗?我们访问当前工作目录不也是只知道当前工作目录的文件名吗?要访问它,不也得知道当前工作目录的inode吗?
答案:所以也要打开当前工作目录的上级目录,上级目录不也是目录吗??不还是上面的问题吗?
所以类似“递归”,需要把路径中所有的目录全部解析,出口时“/”根目录。
最终答案:实际上,任何文件,都有路径,访问目标文件,都要从根目录开始,依次打开每一个目录,根据目录名,依次访问每个目录下指定的目录,直到访问到目标文件。这个过程就叫做Linux路径解析。
那这样访问不会很慢吗?
不会,当用户访问指定路径下的文件,Linux会在路径解析过程中,在内核形成目录树和路径缓存。(类似记忆化搜索)
ps:根目录固定文件名,inode号。无需查找,系统开机之后就必须知道。
可是路径由谁提供?
用户访问文件,都是通过指令/工具访问,本质就是进程访问,进程有CWD,进程提供路径。
open文件时,提供了路径。
3.7路径缓存
问题1:Linux磁盘中,存在真正的目录吗?
不存在,只有文件。只保存文件属性+文件内容。
问题2:访问任何文件,都要从根目录开始进行路径解析?
是,但这样太慢,所以Linux也会缓存历史路径结构(类似递归+记忆化搜索)
问题3:Linux目录的概念,怎么产生的?
打开的文件是目录的话,由OS自己在内存中进行路径维护。
比如Linux中,内核中维护树状路径结构的内核结构体叫做:struct dentry
ps:
每个文件其实都要有对应的dentry结构包括普通文件。这样所有被打开的文件,就可以在内存中形成完整的树形结构。
整个树形节点也同时会隶属于LRU(Least Recently Used,“最近最少使用”)结构中,进行节点淘汰,所以用户不用担心路径太多把内存撑爆。
整个树形节点也同时会隶属于Hash,方便快速查找
更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都在先在这颗树下根据路径进行查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径。

3.8 挂载分区
我们可以根据inode号在指定分区找到文件,也能根据目录文件内容找指定的inode,在指定的分区里我们已经可以为所欲为了。
但inode不跨分区,Linux不是也可以有多个分区吗?如何知道所处分区???
df -h //查看可以使用的分区

在Linux中,磁盘设备一般在dev下,然后机器的磁盘叫做vda(不同机器磁盘叫法不一样),vda1表示磁盘的一个分区。
一个磁盘必须分区格式化,才能使用。一个分区要被真正使用,需要挂载到指定目录。像图片中的vda1就被挂载到根目录下,所以在根目录下所做的所有增删查改的工作都是在vda1这个分区下进行的。
我们可以通过dd命令,创建一个大的磁盘块,当做一个分区
dd if=/dev/zero of=./disk.img bs=1M count=5 #制作⼀个⼤的磁盘块,就当做⼀个分
区


可以通过mkfs命令格式化写入文件系统
mkfs.ext4 disk.img # 格式化写⼊⽂件系统

mout命令将分区挂载
sudo mount -t ext4 ./disk.img 【要挂载到的目标文件名】
sudo umount 【要挂载到的目标文件名】 # 卸载分区

ps:
那么我们之后在hello目录下执行的一切文件操作,都是在我们创建的盘上工作。
3.8.1 小结
分区写入文件系统,无法直接使用,需要和指定的目录关联,进行挂载才能使用。
所以,可以根据访问目标文件的“路径前缀”准确判断处在哪一个分区。
3.9 文件系统总结




4. 软硬链接
4.1硬链接
通过学习我们了解,真正找到磁盘上文件的并不是文件名,而是inode。其实在Linux中可以让多个文件名对应于同一个inode。
我们可以通过ln命令,创建文件的硬链接
ln 源文件 硬链接名

abc和def的链接状态完全相同,他们被称为指向文件的硬链接。内核记录了这个连接数,inode 263466的硬连接数为2。
硬链接文件可以理解成源文件的一个别名,一个文件有几个文件名,该文件的硬链接数就是几。
我们在删除文件时干了两件事:1.在目录中将对应的记录删除;2.将硬连接数-1,如果为0,则将对应的磁盘释放。
ps:刚创建的目录硬链接数时2
因为每个目录创建后,该目录下默认会有两个隐含文件.和..,它们分别代表当前目录和上级目录,通过命令ls -i -d 目录名 和 ls -i -a 目录名 可以发现.和当前目录的inode号一样,也就是说他们是同一个文件。
ps:一个目录下的相邻子目录数等于该目录的硬链接数减去2,因为子目录的..文件指向的是父目录,子目录..文件inode和父目录一样,也是父目录的别名。
4.2软链接
硬链接是通过inode引用另一个文件,软链接是通过名字引用另一个文件,但实际上,新的文件和被引用的文件的inode不同,应用常见上可以想象成一个快捷方式。
软链接又称为符号链接,软链接文件相对于源文件来说是一个独立的文件,该文件有自己的inode号,但是该文件只包含了源文件的路径名,所以软链接文件的大小要比源文件小得多。软链接就类似于Windows操作系统当中的快捷方式。
ln -s 源文件/目录 软链接名
删除了源文件后,软链接文件就不能独立存在,虽然仍保留文件名,但却不能执行或查看软链接的内容。
4.3软硬链接对比
软链接是独立文件,有自己的inode号,相当于一个快捷方式。
硬链接并没有创建文件,相当于给源文件取别名,给文件备份,这样如果误删了一个文件,还通过别的文件名去访问文件内容,此时误删一个文件只是使硬链接数减去1,当硬链接数减到0的时候才会将对应磁盘释放,硬链接是文件名和目标文件inode的映射关系。
5.文件的三个时间
在使用stat命令的时候我们能看到这三行数据
ps:acm
文件的三个时间:
Access 最后访问时间
Modify 文件内容最后修改时间
Change 属性最后修改时间
一般情况下,当修改文件内容的时候,文件的大小一般也会随之改变,所以一般情况下Modify改变会带动Change一起改变,但是修改文件属性不会影响文件内容,所以Change改变不会影响Modifiy改变。

若是想要将文件的三个时间都更新到最新状态,可以使用命令touch 文件名进行时间更新。所以touch并不只有创建新文件的功能,还可以更新已有文件的时间戳。
此篇完


889

被折叠的 条评论
为什么被折叠?



