Linux第20节 --- inode和文件系统
一、没有被打开的文件
如果一个文件没有被打开,那么该文件存储在哪里?
该文件是存储在磁盘当中的!
文件 = 文件内容 + 文件属性!
- 文件的内容是按照数据块存储的;
- 文件的属性其实就是inode(是一个128字节的数据块,包含该文件的所有属性);
- Linux的文件在磁盘中存储,是将文件的内容和属性分开存储的!!!
磁盘的特性:
- 磁头是一面一个;
- 磁头和盘面不接触(挨得非常近);
- 磁盘一般处于无尘环境;(灰尘对磁盘的影响很大!)
- 数据是在盘面中存储的;
- 磁盘是永久性存储介质(无电没影响);
如上图所示:绿色的是磁道,红色的是扇区;
磁盘中,一圈同心圆被称为磁道,磁道会被且为一小段一小段的区域,这一小段区域被称为扇区!磁盘会被分为无数个扇区;
磁盘被访问的最基本单元是扇区;一个扇区大小通常是512字节,甚至有4KB;
我们可以把磁盘看作是无数个扇区构成的存储介质;
要把数据存储到磁盘,第一个要解决的问题就是定位一个扇区!
因此,首先我们要先定位到哪个面,即定位用哪个磁头?
接下来我们要确定访问哪个磁道?然后再定位访问哪一个扇区?
同一磁道,从上面往下看就是柱面的概念!
磁头是同时移动的!
假如说当前三面磁盘六个磁头,磁头要移动的时候六个磁头同时动!
磁头的左右摆动实际上是定位磁道和柱面的过程!(确定当前半径即磁道大小!)
当磁头不动,光盘动的时候,该过程被称为定位扇区!
对磁盘来说:
- 运动越少,效率越高;
- 运动越多,效率越低;
对于磁盘寻址,有三个关键的参数:Cylinder(柱面),Header(磁头),Sector(磁道)三个参数;
根据这三个参数确定的扇区地址被称为CHS寻址法则;
如果当前我们有三个磁盘,一共六面;
我们将其延展开,逻辑上可以看作是线性的!
其中,每一面又包括许多磁道!每一个磁道又包括多个扇形;
那么最终我们可以得到以扇区为基本单位的数组:(任何一个扇区都有对应的下标)
但是磁盘确认只根据CHS,因此此时我们需要找到对应的CHS和我们抽象化的数组之间的关系!
假设当前磁盘一个盘面有2w个扇区,每个扇区有50个磁道,而每个磁道有400个扇区!
当前有一个扇区标号为28888;那么此时我们根据对应的逻辑运算可以反推得到该扇区位于哪个磁面,哪个磁道和扇区!
LBA地址即为我们抽象的逻辑地址!
回归到磁盘的硬件
不仅CPU有寄存器,其他外设(磁盘)也有寄存器!
磁盘当中有三个寄存器:
- 控制寄存器;
- 数据寄存器;
- 状态寄存器;
如果操作系统想要访问磁盘,此时需要告诉控制寄存器当前是读取还是写入?即控制寄存器控制IO方向(r/w),然后读取/写入的数据位于数据寄存器当中,接下来再获取LBA的地址即可进行读取或写入,最后返回操作后的结果;
文件系统
假设当前我们有800G的磁盘空间,分区的过程实际上就是定义一个结构体,该结构体包含了抽象数组中我们使用的起始地址和最终地址!
此时我们只需要把这分割好的200G管理好,那么按照相同的策略即可管理好总的800G的磁盘!
我们先看Data block:
Data blocks可以有很多块(block),每个block的大小可以是1024,2048,4096个字节,这三种选项;Data blocks是用于存放文件内容的;(此时凡是我们通过fopen,fread,fwrite访问磁盘,那么访问的基本单元都是上面三种我们设置的1,2,4KB大小(文件块大小)!一般而言,每个块只有自己对应的数据!)
inode table:存放的有单个文件的所有属性,一般的大小是128字节!一般而言,一个文件有一个inode!在inode里面有唯一的编号!且该文件内容有多少个块(block)这一信息也在inode!
inode可能大致如下结构所示:
inode不包含文件的名称!每个文件用唯一编号确认!
通过ls -li可以查看文件对应的编号:
在Linux系统里面标识一个文件使用的是inode编号!
假如说当前NUM = 15,那么是不是说该文件最大为4*15 = 60KB呢?
答案:不是!可以通过二级索引以上来进行扩容!
例如当前我们0~12为直接索引,直接指向文件内容;12,13,14可是是二级索引,引用的块用于存放新的数据块的指针!4KB = 4*1024;每个二级索引最大为4MB!
1. 直接索引(Direct Index)
- 定义: inode中的
block[0]
到block[11]
(不同文件系统数量可能不同)是直接索引项,每个项直接指向一个数据块(Data Block)。例如,若直接索引项有12个,每个数据块大小为4KB,则直接索引可存储最大 12 × 4KB = 48KB 的文件。 - 作用: 直接处理小文件,无需额外索引块,访问速度快。
- 示例: 若文件仅占用直接索引块,系统直接通过
block[i]
找到对应数据块。
2. 二级索引(一级间接索引,Indirect Index)
- 定义: inode中某个索引项(如
block[12]
)指向一个一级间接索引块,该索引块本身存储多个数据块指针。假设每个指针占用4字节,一个4KB的索引块可存储 4096B / 4B = 1024个指针。此时,二级索引可支持 1024 × 4KB = 4MB 的数据。 - 作用: 扩展中等大小文件的存储能力,避免直接索引项不足。
- 流程:
- inode的
block[12]
指向一个索引块。 - 索引块中的每个指针指向实际数据块。
- 系统通过两次磁盘访问(索引块 + 数据块)获取数据 。
- inode的
3. 三级索引(二级间接索引,Double Indirect Index)
- 定义: inode中某个索引项(如
block[13]
)指向一个二级间接索引块,该索引块再指向多个一级间接索引块,每个一级索引块再指向数据块。此时,三级索引可支持 1024 × 1024 × 4KB = 4GB 的数据。 - 作用: 支持大文件,通过多层间接索引扩展存储容量。
- 流程:
- inode的
block[13]
指向二级间接索引块。 - 二级索引块中的指针指向多个一级间接索引块。
- 每个一级索引块再指向数据块。
- 系统需三次磁盘访问(二级索引块 + 一级索引块 + 数据块) 。
- inode的
我们如何知道哪个存放文件的数据块被使用了,哪个没有被使用?
Block Bitmap: Block Bitmap里面存放了记录Data Block中的哪个数据块被占用了,哪个数据块没被占用!(1个比特位表示1个块! --- 置为1表示该块被使用!)
4KB = 4096个字节 = 4096*4个比特位!
问题:删一个文件的时候,用不用把块(文件内容)清空呢?
不用!只需要将对应的位图清空!全设置为0即可!
- 获取当前文件的inode编号(在inode table里面)
- 然后再inode bitmap查看该inode是否是有效的!
- 如果有效,在通过inode table读取属性,获得inode编号与其内容的映射关系,将读取到的块号在block bitmap里面置为0!
- 再在inode bitmap将对应的编号设置为0!
删文件对内容没有关系!
每一个文件的分组都有如上的结构;
在Linux中,inode编号是以分区为单位统一分配的(不能跨分区)!
磁盘访问中是以块大小为基本单位的!扇区是最小单位!(例如8个扇区的大小即为1个块!)
- 一共有多少个组;
- 每个组的大小
- 每个组的inode的大小;
- 每个组的block的数量;
- 每个组的其实inode;
- 文件系统的类型和名称等!
super block不会在每一个组中都有!有的组可能没有,有的组可能有多份!
如果当前super block这个模块的内容挂掉了!那么当前根据组进行的分区等什么都做不了,因此整个分区都挂掉了!
所以一般会在Block group 0 ~ n中零零散散有多个Super block!
结论:每一个分区在被使用前,都需要提前将部分文件系统的属性信息提前设置进对应的分区当中!方便我们后续使用这个分区或者分组,这一过程叫做格式化!(具体点还会将Block Bitmap 和 inode Bitmap进行清空!再将对应的文件信息写入进去)
蓝色部分是为了管理后面红色部分的属性信息!
在Linux中,可以通过stat指令查看文件的详细信息(比ls更详细)
问题:
- 新建一个文件,系统要做什么?
- 删除一个文件,系统要做什么?
- 查找一个文件,系统要做什么?
- 修改一个文件,系统要做什么?
回答上面这些问题之前,我们可以先总结下我们得到的结论:
- Linux中,一个文件只有一个对应的inode,每一个inode都有自己对应的inode编号!(inode的设置只在对应的分区有效,例如我们举例的200G)
- inode表示文件的所有属性,文件名并不属于文件属性!
当我们在一个路径下创建文件,有了路径我们可以确定要创建到对应的那个分区!
系统会对当前的文件分配对应的inode编号(分配inode编号的过程,根据路径我们已经认定是在哪个区了,然后查询对应的GDT,可以发现对应的inode的使用率很低,然后再查询inode bitmap,查询到最近的没有被使用的inode编号,然后分配)
删除 = 允许被覆盖!
cat test.txt
在操作系统的层面上看:实际上是cat根据test.txt的inode找到对应的磁盘分区和分组,然后在inode BitMap确定该文件对应的inode是0还是1,如果是1,再到inode Table查询对应的存储空间(blocks),然后读取对应的数据块的内容;
stat test.txt
在操作系统的层面上看:实际上是stat也是根据文件的inode找到对应的inode table,再将里面的重要的属性提取出来!
问题:我们如何知道一个文件的inode编号?
用户从来没有关心过inode,用的是文件名!
解答:这是因为目录也是文件!也有自己对应的inode编号;
我们知道,文件 = 内容 + 属性,那么目录的内容是什么?即目录需不需要对应的数据块?
答案是需要的!在目录的数据块里面,存放了文件的文件名和对应的inode的映射关系!
问题:当我们执行ls /ll 的时候,对于磁盘文件操作系统应该是如何实现的?
找到当前目录文件的inode,然后根据inode再对应的inode table找到对应的数据块,此时可以找到文件名和对应文件的映射关系!此时可以找到目录里面文件的inode,从而对接到上面新建文件等四个过程!
因此接下来我们可以回答之前的四个问题:
- 为什么同一个目录不能用同名文件?
文件名作为key值要找到对应的inode编号,即value!
- 为什么目录下,没有w我们不能创建文件?
即使能将文件创建出来,但是这个文件的文件名和inode的映射关系无法写入到数据块中!
- 为什么目录下,没有r我们不能查看文件?
查看文件之前,我们需要获取文件的inode,但是当前目录我们无法读取数据块中的内容!因此无法查看文件!
- 为什么目录下,没有x我们不能进入目录?
进入一个目录需要cd,cd的本质实际上就是找到目录的inode,然后把当前系统的环境变量进行更新!但是当前我们无法获取inode!
- 路径解析的必要条件: 当用户尝试进入目录(如执行
cd dir
)或访问目录内的文件(如cat dir/file
),系统需要遍历目录的目录项以找到目标文件的inode号。这一过程需要访问目录的数据块,而访问数据块的前提是目录具备x权限 。 - x权限与inode的关系: 目录的x权限控制了对目录数据块的搜索能力。即使有读权限(r),也只能列出文件名(通过
ls
),但无法通过inode号访问具体文件,因为系统无法定位到目录数据块的物理位置 。
关键问题:怎么获取目录的inode编号?
我们需要从当前目录进行递归向上寻找,直到找到根目录,根目录里面存放的有目录的inode信息!访问任何的文件,我们都需要路径来进行访问!
二、软硬链接
软链接是一个独立的文件!因为具有独立的inode!
建立硬链接的指令:
ln 原文件 硬链接名字
# 例如:ln cat.jpg my_pet.jpg
建立软链接的指令:
ln -s 原文件 软链接名字
# 例如:ln -s cat.jpg shortcut_to_cat.jpg
- ln是建立链接的指令!
- -s表示建立软链接;
- ln什么都不带表示表示硬链接!
针对硬链接:
对于硬链接,我们发现文件和硬链接共用一个inode!
硬链接不是独立的文件,因为没有独立的inode!
硬链接形成的inode一样,所以对应的文件的特性是一样的!
结论:所谓的建立硬链接,本质其实就是在特定目录的数据块中新增文件名和指向的文件的inode的映射关系!
- 硬链接的本质实际上就是取别名!
- 任意的一个文件,无论是目录还是普通文件,都有inode编号!
- 在每一个inode编号的内部,都有一个引用计数的计数器!(计数器的作用是表明当前有多少个文件指向这个inode)
可以存在多个文件名映射同一个inode!
引用计数为0的时候,该文件才会被彻底删除!
针对软链接:
如何理解软连接?
软连接是一个独立的文件,有独立的inode编号,也有独立的数据块,它的数据块里面存放的是指向原文件的路径!
因此,当我们进入如下操作的时候:
我们往源文件中写入数据,软链接的文件也能直接访问到(因为写入的是地址)!
因此,如果将软链接删除,对源文件无影响;
但是如果将源文件进行删除,软链接就找不到了!
软连接类似于Windows中的快捷方式!
除了通过rm进行删除,我们还可以进行unlink进行删除!(软硬链接都可以!)
unlink 软/硬链接文件名
软链接的应用:
当我们需要执行的程序位于路径深处,但是此时我们想要在当前目录下运行,此时我们可以跟其进行软连接,而不用到路径深处来执行!
例如下面的例子:
硬链接的应用:
当前目录下我们有一个file文件,其硬链接数为1很好理解;
但是如果我们创建一个空目录,那么当前的硬链接数为2!
这是因为当我们进入到这个目录的时候,我们还有对应的一个点也就是当前目录!
一个点是对应的文件夹的硬链接,所以它们的inode值是一样的!
问题:为什么对应的..的硬链接数是3呢?
这是因为..(上级目录)是一个对应的硬链接,然后上一级目录是lesson22,这个目录还存在本身和当前目录. !!!
如果我们子啊对应的dir目录下再创建一个子目录,这时候dir对应的硬链接会变为3!
这是因为创建的子目录里面包含了对应的..上级目录,这算一个硬链接!
规律:已知当前目录的硬链接数,那么这个目录下有多少个子目录呢?(硬链接数 -2)
应用:通常用于路径定位,采用硬链接可以进行路径间的切换!
问题:Linux内部不允许对目录进行硬链接,为什么?
如果我们要在根目录下根据文件名查找一个文件且此时与根目录实现硬链接:
此时我们需要读取路径上inode和其对应的属性,但是当我们找到硬链接的时候,此时又重新返回到根目录,造成死循环!
问题:目录内部有 ./..不是硬链接吗?
这是操作系统自己编码时实现的,操作系统可以,但是用户不可以,哪怕是root!
系统在搜索路径的时候,不会对.和..这两个硬链接的路径做搜索!所以不会出现死循环!
硬链接只是对应的文件名和一对对应的inode映射!不是真正的目录,依旧属于当前目录下的内容,因此rm删除目录不需要考虑.和..!