Linux:文件与fd(未被打开的文件)
hello,各位小伙伴,本篇文章跟大家一起学习《Linux:文件与fd(未被打开的文件)》,感谢大家对我上一篇的支持,如有什么问题,还请多多指教 !
如果本篇文章对你有帮助,还请各位点点赞!!!
话不多说,开始正题:
文章目录
- 未被打开的文件
- 认识磁盘结构
- 引入文件系统
- `Group`:
- `inode Bitmap`
- `Super Block`
- 深入理解文件系统
- `inode`和`block`到底是怎么映射的
- 怎么拿到`inode`,我们访问用的文件名是什么
- 如何确定分区的?
未被打开的文件
上篇文章讲述的是被打开的文件,被打开的文件是将文件放到内存中,那么未被打开的文件放在哪里呢?
答案是:放在磁盘中
那么文件最终的目的其实就是访问它,访问前得打开文件,打开文件得找到文件,就要通过路径来找,也就是说:每个文件都有自己的路径,所以我们需要一个磁盘级别的文件系统,打开文件之后就是内存级别文件系统的事情了,就是上篇文章
对于路径,我们平时买东西一般都会放在菜鸟驿站,当包裹送到就会把包裹放在哪里类似于12-4-1363
这种取件码然你去取,其实12-4-1363
就是路径,能够让你找到你的包裹,那么驿站就是磁盘,存放未被打开的文件的地方
那是不是只需要维护取件码,驿站就能够顺利运行了呢?
肯定没那么简单,要是17号柜子满了,12却是空空如也……一系列问题都需要进行维护
认识磁盘结构
要想真正理解文件,那么磁盘的存储结构必须得了解,我们无法想象出一个没有见过的结构
磁盘也是只认识二进制,磁铁也只有N和S,我们可以把磁盘认为是由许多个很小的磁铁构建而成,所以我们可以约定N是1,S是0
上图就是磁盘的物理结构,主轴马达会旋转,磁头会左右摇摆读取数据
下图是磁盘的存储结构:
- 一个磁片两个面都可以存储数据,每一面都有磁头(header),所以读写哪一个磁头,就是读写哪一面
- 磁头摆动其实就是定位柱面(磁道)(cylinder)
- 磁盘旋转本质就是定位扇区(sector)
- 扇区:是磁盘存储数据的基本单位,512字节,块设备
所以要想修改数据,就必须将512字节的数据全部修改,整个扇区进行修改 - 这种定位方法叫做CHS地址定位法
- 细节:传动臂上的磁头是共进退的(这点⽐较重要,后⾯会说明)
- 磁盘容量=磁头数×磁道(柱⾯)数×每道扇区数×每扇区字节数
对早期的磁盘⾮常有效,知道⽤哪个磁头,读取哪个柱⾯上的第⼏扇区就可以读到数据了。
但是CHS模式⽀持的硬盘容量有限,因为系统⽤8bit来存储磁头地址,⽤10bit来存储柱⾯地址,⽤6bit来存储扇区地址,⽽⼀个扇区共有512Byte,这样使⽤CHS寻址⼀块硬盘最⼤容量为256* 1024* 63* 512B=8064MB(1MB=1048576B)(若按1MB=1000000B来算就是8.4GB)
想必大家都见过磁带吧,原本是一卷一卷的,但是我们可以把它拉直,就可以看成是线性结构,我们磁盘也可以抽象成线性结构
这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),这种地址叫做LBA
但是无论如何怎么抽象,最终从磁盘中获取数据还得是从CHS
这种方法获取,后面我会讲LBA
转换成CHS
,下面先讲解LBA
的真实运作:
⼀个细节:传动臂上的磁头是共进退的
柱面是一个逻辑上的概念,其实就是每一面上,相同半径的磁道逻辑上构成柱面。
所以,磁盘物理上分了很多面,但是在我们看来,逻辑上,磁盘整体是由“柱面”卷起来的。
我们将这些柱面展开:
这不就是二维数组吗?所以对于我们磁盘中有n个柱面,其实就是由n个二维数组构成的
所以,寻址一个扇区:先找到哪一个柱面(Cylinder),在确定柱面内哪一个磁道(其实就是磁头位置,Head),在确定扇区(Sector),所以就有了CHS。我们之前学过C/C++的数组,在我们看来,其实全部都是⼀维数组:
所以,每一个扇区都有一个下标,我们叫做LBA(Logical BlockAddress)地址,其实就是线性地址。
所以只要掌握了如何将LBA
转换成CHS
,操作系统就根本不需要理会CHS
,只需要知道LBA
就可以了,操作系统只需要知道磁盘的总容量和每个扇区的大小,就能够知道每个扇区的下标
CHS转成LBA:
- 磁头数 * 每磁道扇区数 = 单个柱面的扇区总数
- LBA = 柱面号C * 单个柱面的扇区总数 + 磁头号H * 每磁道扇区数 + 扇区号 S-1
即:LBA=柱面号C*(磁头数 * 每磁道扇区数) + 磁头号H * 每磁道扇区数 + 扇区号S-1 - 扇区号通常是从1开始的,而在LBA中,地址是从0开始的柱面和磁道都是从0开始编号的
- 总柱面,磁道个数,扇区总数等信息,在磁盘内部会自动维护,上层开机的时候,会获取到这些参数。
LBA转成CHS:
- 柱面号C=LBA//(磁头数*每磁道扇区数)【就是单个柱面的扇区总数】
- 磁头号H=(LBA%(磁头数*每磁道扇区数))//每磁道扇区数
- 扇区号S=(LBA%每磁道扇区数)+1
“//”: 表示除取整
从此往后,我们对磁盘的认识就是以扇区为单位的一维数组
引入文件系统
一个扇区才512字节
,这对于操作系统与磁盘进行IO
来讲太小了,所以操作系统就设定以1kb、2kb、4kb、8kb……
等为单位,Linux一般是4kb
为单位,也就是8个扇区为单位,这8个扇区叫做数据块,那么转换为LBA
—>块号*8 + [1,8]即可
所以对于操作系统来讲,磁盘就是以块为单位的一维数组,那么操作系统该如何管理磁盘呢?
先描述再组织!
对于磁盘来讲,成百上千GB大小确实不太好管理,但是我们能够将磁盘进行分区,区内还可以进行分组,组管好了区就管好了,区管好了磁盘就管好了,这就是分治思想:
Group
:
- inode知识点:
- 文件 = 内容 + 属性
- 属性也是数据,结构体的方式构建出来,inode
- 一个文件一个inode, inode->属性数据的集合
但是在inode里并没有存放文件名这个属性!!(后续会讲)
对于OS
,磁盘的基本单位就是块4kb
,一个块有多少个inode
(inode
的大小一般是128字节)是可以算出来的,inode
是不能够重复的(但有前提),我们可以在命令行中查看inode
:
ls -l -i
inode Table
存放的就是inode属性集,那么内容是存放在哪里?—— data Block
data Block
里面是以4kb
为单位的块存放着数据,那么可以看出Linux下,文件属性和文件内容分开存储
有了inode
怎么找data block
?其实在inode
里还有属性:
inode Bitmap
在一个group
中,inode
是如何分配的?又是怎么知道在inode Table
中哪个位置可以使用?这就和inode Bitmap
有关了,就是位图,想必大家对位图不陌生吧,0000 0000 0000 0001
这就表示第0
号已经被占用,0000 0000 0000 1001
这就表示第0、3
号已经被占用……
所以新建一个文件,首先就要分配inode
,在位图里查找空位置,把0
置1
,然后填写inode
的属性,所以我们只需要知道inode
就可以找到文件
那么删除文件实际上就是找到位图上的位置,把1
置0
,所以理论上来讲,删除了一个文件,是可以被恢复的,就像我们的回收站一样,但是清空回收站之后再回复就有难度了,并且还不一定能够全部恢复,因为在操作的时候很有可能覆盖了原有数据,所以一不小心删除重要文件,最好不要动,怕覆盖原有文件数据
Super Block
Super Block
超级块,分布在group
前头,但并不是每一个group
都有超级块
- 超级块就是表示文件系统
- 每个超级块都是一样的,所以若某个超级块挂掉了,可以迅速从其他超级块覆盖回去
- 超级块穿插在分区之间,散落在不同的分组里面
- 因为一旦超级块挂掉了,这个分区也就挂掉了,所以并不是所有
group
都有超级块
深入理解文件系统
在每一个group
里面,inode
和data block
的个数都是固定的
总结起来,inode
和data block
之间的关系可以通过以下两种情况来描述:
inode
没用完,而data block
用尽
- 原因:当文件系统内存在大量的大文件时,每个文件可能需要大量的data block来存储数据,但每个文件只占用一个 inode。这样,虽然有足够的 inode,但是由于大文件占用了大量的 data block,导致 data block 被用尽,而 inode 仍然没有完全用完
- 典型情况:块组中有少量的大文件,导致 data block 很快用完,但 inode 没有用完
inode
用完,而data block
剩余
- 原因:当文件系统中存在大量的小文件时,每个文件都需要一个 inode,但是这些小文件所占用的 data block 非常少。这样,虽然 inode 很快用尽,但每个小文件只占用少量的 data block,导致大量 data block 没有被使用
- 典型情况:块组中有大量的小文件,每个文件只需要很少的 data block,导致 inode 很快用完,但 data block 仍然有剩余
总结:
- inode 和 data block 是文件系统分配资源的两种基本单位,它们的使用情况受到文件大小和文件数量的影响
- 大文件导致 data block 用尽,inode 没用完;小文件导致 inode 用尽,data block 剩余
这两种情况在实际的文件系统设计中都可能发生,尤其是在文件大小分布不均的情况下
关于inode
- inode以分区为单位,一套inode
- inode分配的时候,只需要确定起始inode即可
举个例子:比如说在一个分区里,第0号group
的起始start_inode
为1,第2号group
的起始start_inode
是10000,第n号group
……
所以,每一个inode
的编号是固定的,这个起始start_inode
是存放在GDT
也就是Group Descriptor Table
关于block也是统一编号的,所以只需要知道起始块号就行了
inode
怎么分配?该组的起始start inode
加上inode Table
的编号即可,这样就可以确定唯一性
block
也是如此分配,所以block
和inode
具有全局唯一属性,在上述inode
文件属性中指向block
指针是可以跨组指向block
,也就使得大文件可以被创建
总结出:对于block
和inode
都存放在GDT
里,那么只需要将Super block
和GDT
管理好就能够将group
管理好,怎么管理?
先描述再组织
就是以链表的形式进行管理,就转换为对链表的增删查改
inode
和block
到底是怎么映射的
对于12个直接块指针,顾名思义,直接指向块号,但是12个显然不够,所以加入了一级间接块索引指针,二级间接块索引指针,三级……
一级间接块索引指针指向的块里面存放的是块号,然后再指向块,二级间接块索引指针就不用我多说了吧,指向的块存放的是一级间接块索引指针,对于三级间接块索引指针……
怎么拿到inode
,我们访问用的文件名是什么
还记得上述讲到的细节吗?在inode
属性里并没有维护着文件名
那么存放在哪里呢?对于文件分两种:普通文件和目录文件
对于目录文件也有自己的属性和内容,也就是说目录也有自己的inode
:
ls -lid
那对于目录的内容是啥?目录的内容当然是数据块
那里面就是文件名和inode
的映射关系啦,噢,是不是有点恍然大悟,突然明白了为什么文件名不在inode
属性当中
我们人类对于数字是不敏感的,但是名字可以,所以文件名那就是存放在目录里面,所以大家对于目录的权限应该有了新的认知,为什么没有读权限就不能读,为什么没有写权限就不能创建新文件,为什么没有执行权限就进不去
但是目录也是文件,也有文件名,也就要回溯到上级目录
找到文件名->首先要打开当前目录->当前目录inode编号,也是文件!!!也有文件名啊!->逆向的路径解析!
一直回溯到根目录,根目录是已经固定了的
这也就是为什么查找文件的时候需要完全路径,路径是谁提供的?当然是进程,每个进程都有维护着自己的CWD
,就是为了方便查找文件,方便路径解析
但是这么一个一个目录解析路径本质上不就是不断的访问磁盘文件吗?这样不断地交互效率实在是低下,所以作为聪明的操作系统,Linux引入了缓存,用来缓存路径结构
以什么结构缓存?那当然是树结构,以根目录作为根节点,当你第一次访问该目录,就形成结构缓存该路径,当下次再访问的时候就直接访问缓存好的数据,所以这明显就是内存级别的数据结构,纯内存struct dentry
如何确定分区的?
对于系统有多个分区,每个分区都有许多目录,但是只有分区时无法直接访问的,必须要“挂载”到目录上,分区才能够通过路径的方式进行访问,这就使得每一个分区天然就有了基本的路径,所以路径的前缀就已经说明了你在哪一个分区里面