【Linux内核系列】:文件系统收尾以及软硬链接详解
🔥 本文专栏:Linux
🌸作者主页:努力努力再努力wz
💪 今日博客励志语录:
世界上只有一种个人英雄主义,那么就是面对生活的种种失败却依然热爱着生活
内容回顾
那么在之前的学习中,我们知道了操作系统是如何管理我们未打开的文件,我们知道文件的数据是存储在磁盘上的,而操作系统要管理磁盘上的所有数据,那么必然在操作系统中建立一个磁盘的逻辑映射,那么其中磁盘中的数据是保存在盘面上的一圈一圈的磁道的扇区当中,那么我们可以将该立体空间中每一个扇区按照一个线性的一维数组来进行一个排列,那么该一维数组就是磁盘的逻辑结构,其中该一维数组中的每一个元素就是一个扇区,其中为了区分不同位置的扇区,那么我们就得给扇区一个唯一的标识符,那么我们扇区在一维数组对应的数组下标便是扇区的编号从0到n分配给不同位置的扇区,而该编号也称之为LAB地址,那么当我们有了扇区的LAB地址之后,由于LAB地址是逻辑地址而不是扇区在实际的磁盘中的物理地址,而我们需要定位扇区在磁盘中真实的物理地址,而定位其在磁盘中的位置则需要该位置对应的磁头以及磁道和扇区这三个坐标,所以有了LAB地址之后,操作系统会采取特定的算法将LAB地址映射转换该位置的三个坐标即可
那么扇区是我们磁盘存储的基本单元,那么磁盘读写数据是以扇区为单位来进行读写,但是由于扇区的存储的内容量一般只有512字节,而现在的文件的大小动辄就是几十甚至上百个GB,而磁盘读取的速度取决于机器运动的次数,那么以扇区为单位读写必然io的速度就会很慢,所以不同的文件系统会采取将多个扇区给组合形成该磁盘读写的最小单元,以EXT4文件系统为例,那么它是以8个扇区为一组作为一个逻辑块,那么磁盘一次读写的数据量就是4KB,那么这样就提高了io的效率,所以在文件系统所定义的逻辑块的视角下,那么原本的一维线性数组的每一个元素便不再是一个个扇区而是逻辑块,所以给一维数组的元素个数以及每个元素的编号就会发生变化
其中由于磁盘的数据量很大,那么操作系统管理如此庞大的数据,那么采取的策略就是分区,其中将该一维线性数组分成不同的区域来进行管理,但是即使将其分区,那么每一个区的数据量依然很大,所以会进行更为细致的划分,将每一个分区在划分成不同的分组,那么其中管理好整个磁盘就是管理好该分区的每一个分组以及不同的分区,那么这就是对前置知识的回顾,如果感到陌生或者说好奇其中的细节的话,可以阅读之前的文章
★★★ 本文前置知识:
文件系统
重新理解文件
那么了解了磁盘的一个逻辑结构以及操作系统管理磁盘的一个方式之后,那么我们知道文件是由两部分所构成,分别是文件的属性以及文件的内容,那么对于文件的属性来说,操作系统则是会为其定义一个inode结构体,其中封装了该文件的各个属性字段其中就包括文件的权限以及文件的创建时间以及最近修改时间,那么inode数据则是保存在inode区域中的特定的逻辑块中,而对于文件的内容,那么保存文件内容的逻辑块则是和保存文件属性的逻辑块位于不同的区域,所以在Linux下,文件的属性以及内容是分开来管理,而其中对于inode结构体中还有一个非常重要的字段便是一个索引数组,那么该数组则是保存了该文件的内容的数据块的索引,那么意味着有了inode结构体,那么我们既能获取文件的属性也同时能够获取文件相关联的数据块
所以现在我们就便能理解当我们创建以及一个文件时,系统会做什么
创建文件
那么文件是有内容+属性这两部分构成,当我们创建一个文件时,那么意味着首先就得定义该文件对应的inode结构体,而既然要创建inode结构体,那么我们就得为该文件分配一个空闲的逻辑块来保存该inode结构体的数据,所以系统会到分区的各个分组中,首先会查看该分组的gbt字段,因为gbt字段保存了该分组的整体的逻辑块的使用情况,其中记录了该文件的多少inode逻辑块被使用,多少inode逻辑块是剩余空闲,那么接着再去查看inode表,那么inode表是一个位图结构,其中每一个比特位对应特定位置编号的逻辑块,那么该比特位的值就表示该逻辑块是否被使用,那么为inode分配好一个逻辑块之后后,接下来就是对inode结构体的相关属性进行一个初始化,那么这就是系统创建文件的一个过程
删除文件
那么文件是由属性和内容两部分构成,那么删除一个文件必然就是要删除这两部分数据,而我们知道文件本质就是由保存属性的inode结构体以及保存文件的内容的数据块所构成,而我们的inode结构体中内部有一个索引数组能够找到该文件关联的数据块,那么也就是意味着删除一个文件,我们只需要找到该文件的inode结构体,那么即可获取该文件的全部内容,而获取文件的inode结构体的方式那么就是得需要知道保存该inode结构体的数据的逻辑块的编号,所以一旦获取到inode编号之后,那么我们根据该编号确定该inode对应的逻辑块是在哪一个分组中,然后该分组的块表则记录那些逻辑块被使用哪些逻辑块未被使用,而inode表则是记录了哪些inode结构体被使用哪些未被使用,所以我们获取到inode结构体的编号之后,那么意味着也能同时获取到数据块的编号,接着就只需要将块表以及inode表中对应位置的比特位设置为0即可,无需要覆盖inode块以及数据块的内容,然后更新gbt,这样就逻辑上完成了对文件的删除
所以删除一个文件,意味着会更新相应的块表以及inode表等属性,而不会直接采取覆盖数据块以及对应的inode块,那么也就意味着其实删除一个文件本质上是可以恢复的,但是恢复的过程其实比较复杂还要涉及到专业的工具,并且文件系统的格式化其实也就是在重新初始化对应的分区的每个分组的块表以及inode表等属性,也不会覆盖所谓的数据块以及inode块
那么对于其中的文件的删除,过程道理想必大家都懂,那么关键是我怎么获取目标文件的inode编号呢,我们在Linux上删除文件,我们知道是输入rm指令,而其中我们输入rm指令删除目标文件都是后面直接输入的是删除目标文件的文件名,而不是输入的是其inode编号,但是系统也确实成功删除了目标文件,那么这又是怎么回事呢?
那么我们删除一个文件核心肯定是需要文件的inode编号,那么既然我们只需要输入文件名就可以达到删除的效果,那么只能说明一点,那么就是系统有我们该文件名到inode编号的映射,那么要说清楚这点,那么就得重新来认识一下我们的目录了
目录文件
那么我们之前在Linux的学习中,我们知道可以输入mkdir指令来创建一个目录,那么我们也知道目录本质上其实也是一个文件,有着自己的属性,那么既然是一个文件,那么不用说,它肯定也有一个inode结构体在其中的一个特定的分组当中,那么它的inode结构体肯定也记录其相应的属性比如权限以及创建时间等,那么同理它也一定有一个索引数组指向其关联的数据块,那么我们对于普通文件来说,其inode关联的数据块就是其文件内容,那么对于我们目录文件来说,它也有自己所属的数据块,那么对于目录文件来说,它的数据块保存的是什么内容呢?
答案就是它的数据块保存的就是其目录当中子目录以及子文件的文件名到inode编号的映射,那么每一个数据块也就是目录项保存的都是这个key-value模型的一个映射关系的内容,那么也就是说,我们查找一个目标文件的inode编号,那么就需要到目标文件所处的目录中的目录项中去匹配找到对应的映射关系,获取到其对应的inode编号,所以这就是我们为什么同一个目录下,不能有重名文件的文件的原因
所以我们有了目录文件的概念之后,那么我们就得对之前上文所说的创建文件以及删除文件的过程进行一个完善,那么对于其中创建文件,我们知道会为该文件定义一个inode结构体,然后为在分组中为其分配一个未被使用的逻辑块来保存inode的数据,而其中对于其所处的目录文件中,那么目录文件的数据块也就是目录项中也会添加该文件关于文件名到其文件编号的映射
同理对于删除文件来说,那么首先我们得获取该文件的inode编号,那么我们就得从其所处的目录中的目录项中找到其对应的inode编号,也就是要获得其所处目录的目录项里面的内容,那么我们也得递归的去该目录的上一级目录中得到该目录的inode编号,而我们的文件是以树状的数据结构来组织的,那么其中整个文件系统的根节点便是根目录,而其inode编号是已知的,例如在EXT4文件系统中它对应的inode编号是2,那么我们要得到目标文件的文件编号,我们只需要得到该目录的绝对路径,也就是从根目录到该目标文件的路径,那么系统就会从根目录往下逐层解析,从根目录开始,扫描其目录项中寻找下一级目录的映射关系获取到其inode编号,那么再同理递归到下一级的目录当中扫描其目录项获取其下一级的目录或者文件的inode编号直到达到目标文件
那么由此便能解释之前的问题,为什么我们rm指令输入目标文件的文件名而不是inode编号,也能够删除目标文件
那么如果我们输入删除的目标文件带有绝对路径,那么就是按照上述过程,解析该绝对路径找到目标文件的inode编号然后删除
但是如果是相对路径的话,那么系统会获取到该进程的内核的环境变量中的CWD字段,那么该字段保存了所处的工作目录的inode编号,那么从该目录文件的目录项找到目标文件的inode编号从而删除
而至于进程的内核的环境变量中的CWD字段为什么能够直接获取到所处的目录的inode编号,那么则和dentry缓存有关,因为我们知道从根目录开始解析到目标文件,这其中的过程要涉及扫描每一个目录的目录项的映射关系,那么时间代价就很大,所以dentry就是一个数据结构记录了每一级目录的inode编号,那么我们进程切换目录的时候会利用缓存来得到当前目录的inode编号从而更新CWD,而无需解析整个路径
软链接
那么有了目录以及目录项的概念之后,那么我们其中便可以引入软链接
那么第一个问题:
软链接是什么
那么我们在解释其原理之前,我们先来看看软链接长什么样子,那么我们可以输入该指令来创建软链接一个指向test.c文件的名为soft的软链接
ln -s [目标文件或目录] [软链接名称]
那么创建完之后软链接之后,我们再来使用ls -l 指令来查看一下当前目录下的所有子目录以及文件的属性,
我们发现创建的软链接本质上其实也就是一个文件,那么既然它是一个文件,那么它肯定就由文件的内容和属性所构成,也就意味着其一定有对应的inode结构体,那么我们可以输入ls -li查询inode编号
根据结果我们发现其软链接以及该软链接指向的目标文件test.c的inode编号是不同的,那么说明其有自己独立的inode结构体以及其相关联的数据块,那么其独立的inode结构体肯定存储其相关的属性,那么其相关联的数据块那么存储的是什么内容呢?
那么我们可以输入cat指令来打印器软链接的文件内容到终端上
test.c文件内容:
cat内容:
那么我们发现软链接打印的内容竟然就是其指向的目标文件的内容
软链接怎么做到的
那么我们软连接本身有一个独立的inode结构体以及相关联的数据块,软连接本身的数据块存储的内容便是目标文件的路径,所以当我们输入cat指令来访问到软链接的时候,那么首先会得到软链接的inode编号并且识别到该文件的类型,然后从inode编号中获取其关联的数据块,而其数据块存储的内容便是其目标文件的路径,那么接着系统会解析这个路径得到目标文件的inode结构体从而间接访问到目标文件的数据块,然后打印的是指向的文件的文件内容,所以我们的软链接和我们c语言的指针其实非常的像,那么指针的本质其实就是一个变量,只不过该变量的内容就是指向的目标数据的地址,而同理我们的软链接,也是一个文件,只不过内容保存的是指向的目标文件的路径
注:软链接指向的目标文件一定要存在,如果不存在,那么我们软连接保存的内容是无效的,那么此时该软链接的状态就是悬浮的就类似于我们指针不能为空,不然解引用就会出错
软链接的应用场景
那么在我们Windows下我们的一个可执行文件的成功执行需要编译各种配置的源文件,那么可执行文件和其源文件会封装到一个文件夹在特定的盘的特定路径下保存,那么我们运行这个可执行文件,那么我们就得知道其路径,但是我们用户通常不需要记住每一个可执行文件它保存的路径在哪里,而是通过桌面的快捷方式点开即可运行,那么其实这个快捷方式本质上就是一个软链接,那么它内部记录了可执行文件的路径,那么打开该快捷方式其实本质上就是解析其路径然后运行目标的可执行文件即可
所以学习了Linux之前,相信大家都有过这么的经历,那么就是删除一个文件,很多人以为我将快捷方式放到回收站删除即可那么现在我们知道了,你删除的快捷方式其实本质上是删除了一个软链接,那么该程序的可执行文件以及源文件其实没有任何影响
硬链接
那么有软连接,我们Linux还有硬链接的存在,那么它的作用其实和软连接是差不多的
那么我们先来看看在Linux下我们是如何创建我们的硬链接:
ln 目标文件 硬链接
硬链接是什么
我们在认识硬链接是什么之前,我们还是先来看一下硬链接长什么样子吧,那么我们假设在当前目录下创建一个test.c文件,然后创建一个名为hard的硬链接来指向该test.c文件,然后首先我们还是输入ls -l指令来查看我们当前目录下的子目录以及文件属性,我们发现当前目录下有我们的硬链接文件以及指向的目标文件test.c
然后我们再来输入ls -li来查看该该目录下的文件的inode编号,我们发现硬链接文件以及指向的目标文件的inode编号是相同的
下一步我们再来输入cat指令来打印我们的硬链接的文件的内容时
test.c内容:
cat 硬链接:
我们发现其硬链接打印的内容竟然还是指向的目标文件test.c的文件内容
硬链接怎么做到的
那么我们知道硬链接文件是和其指向的目标文件的inode编号相同,那么说明其共享一个inode结构体,并其硬链接和指向的目标文件是共享inode结构体和其关联的数据块,那么当我们创建一个硬链接的时候,那么会在该硬链接所处的目录中添加一个新的映射,也就是该硬链接的文件名到inode编号的映射,所以当我们访问该硬链接的时候,那么其实本质上就是直接访问了其指向的目标文件的inode结构体,所以打印的内容就是目标文件的内容
并且有了硬链接的概念之后,那么我们输入ls -l指令所展示的文件的属性中,其中拥有者以及所属组和其他后面那个数字便是该文件的硬链接数,而我们创建一个普通文件,那么该文件的硬链接数初始化是1,因为它所处的目录文件的目录项会指向它,而对于目录文件来说,则是2,是因为它所处的目录文件会有一个指向其子目录的目录项,并且对于该目录的目录项来说,那么它也有一个目录项,也就是文件名为".“指向自己的inode的映射,同时还有一个文件名为”…“指向其上级目录的映射,而对于根目录来说,其”.“与”…"都指向自己
注:一般不建议创建指向目录的硬链接,因为我们知道了目录的目录项中有自己以及所处子目录的引用,而如果你在当前所处的目录下创建一个比如其上级目录的应用,那么我们在解析路径的时候就会陷入循环导致崩溃,所以硬链接使用的很少,一般都选择软链接
所以当我们删除一个文件的时候,我们要确定是否彻底删除清理该文件的属性以及内容的数据的时候,我们系统其实首先会得到其文件的硬链接数,然后减一,如果该硬链接数不为0的话,那么意味着还有其指向该文件的inode结构体,所以不会清空该文件的inode结构体以及内容数据,但是如果为0,那么该文件不再被需要,那么则会删除该文件的inode结构体以及其相关联的数据块
结语
那么这就是本篇文章关于文件系统以及软硬件链接的全部内容了,那么这篇文章也就是我们Linux文件系统的收官了,那么文件系统也就此完结撒花告一段落啦,那么恭喜你看到这里,成功的翻阅了文件系统这道大山🎆🎆,当然,对于我这几篇文章来说,肯定是不可能全部覆盖到所有的Linux文件系统的知识,只是覆盖了大部分并且其中最高频的知识,那么其中文件系统跟Linux其他的内容比如进程之间的联系,那么又是说来又是话长,那么我也考虑要不要出一期文章来解析,总之感谢你的耐心观看!
那么我下一期的文章便是动静态库的实现,那么我会持续更新,希望你能够多多关注支出,如果本篇文章有帮组到你的话,那么还请你多多三连加关注哦,你的支持就是我创作的最大的动力!