在这里我们首先学习两个新的知识点第一个:
第二个:
下面我们再来理解思考下面的四个问题:
我们首先来解决第一个问题如果新建一个文件Linux系统会做什么呢?
当我们要新建一个文件的时候,我们肯定是在系统的一个文件路径下去新建的,这个路径能够帮助我们确定清楚这个文件是在哪一个分区中(C盘还是D盘还是其它盘),以及在哪一个分组(分组就是上一节说的800G被划分成了好几份,第一个200G就是一个分区,然后我们在将200G划分成为好多不同的组(每个组10G{假设}))里面(通过路径就能够确认)。我们暂时不将分组加上,至少通过路径我们能够确认的就是哪一个分区里面。那么现在我们已经知道这个文件在一个分区中了,os下面就会为这个文件设定在哪一个分组中,这里我们假设第一个分组的inode编号是从1到10000,第二个分组的inode编号是从10000到20000。第三个第四个依次类推,那么要如何获得inode编号呢?很简单,直接查询GDT(Group Descripe Table)如果此时发现这个inode的使用率很少(10%左右),代表这个分组使用的inode是很少的,然后就去inode bitmap中查询最近的一个没有被使用的inode编号是多少,假设这里是5,然后这里是第一个分组所以此时的inode编号就是10005。由此我们就能够通过inode编号知道这个inode是在哪一个分组的。如果某一个inode的编号是20005,代表就是在第3个分组中。由此通过inode编号就能够很方便的确认这个文件时在哪一个组中,确定了文件所在的组之后我们会去将inode bitmap中的对应的比特位给设置为1。然后就在去inode Table中找到对应的1号inode对象,然后将我们新创建的这个文件的属性往这个对象中填写进去,如果你要往这个文件中写入内容,会根据你要写入的内容的大小(这也是为什么在c语言或者是系统调用中,你要传入你写的文件的大小)给你提供对应的Data block块,最后根据这个文件所在的是哪一个组加上该组的偏移量便形成了对应的inode编号。到此就完成了对一个文件的新建,以及对这个文件的写入。
总结:你要新建一个文件,系统要做的就只是修改对应的位图结构,在位图中申请inode,然后再inode Table中将文件的属性填入,如果你要往这个文件中写入数则再去Block bitmap中申请对应的块,然后将文件的内容填写到块中。然后将块的下标填写到这个文件的inode对象的block数组中。
从这个过程我们可以知道Data block中的块是否被使用根本不由Data block决定而是由block bitmap决定。我们要使用Data block中的块只需要拿着块下标直接使用即可。
当然这个过程中还是存在很多的问题的。在后面我们会重新说明。上面的过程只是一个思路。
删除文件肯定也是根据inode来删除文件的,那么根据inode要如何删除文件呢?
首先根据文件所在的目录知道了文件所在的分区,然后根据inode的范围能够知道这个文件的inode对象是在哪一个分组的,然后直接将我对应的inode编号减去当前分组的起始inode编号,然后我们就能够在当前组的inode bitmap中进行索引了,这里是删除文件,但是文件删除一般也是先删除文件的内容,再删除文件的属性。所以这里现根据inode bit map判断这个inode是否是存在,如果存在,则根据下标在inode Table中,找到这个文件对应的inode 对象,然后根据对象中的数组,找到这个文件对应的block数组,然后再block bitmaop中将这些块号全部由1设置为0.到这里就完成对文件内容的删除,从这里能够得到一个结论(当我们删除一个文件的时候,是不需要去访问block Table这个区域的)。当将文件的内容删除完成之后,最后去到inode bitmap中将这个文件inode对应的比特位由1设置为0。到这里就彻底得完成了对一个文件的删除。
在这里我们也能够知道了为什么删除一个文件很快速,但是拷贝一个文件很慢。
那么恢复文件自然就是根据inode编号找到对应的inode对象,然后拿到这个对象中的block数组中的下标,然后将这些下标再block bitmap中全部由0设置为1,然后去到inode bitmap中将对应的inode对象的比特位由0设置为1,此时就完成了对一个删除文件的恢复操作。
当我们将一个文件不小心删除了,然后又没有专业的工具去恢复,最好的做法就是什么也不做,如果这个文件不重要,那就不管了,如果这个文件很重要,那么就去寻找一些专门的组织去恢复。为什么什么都不做是最好的呢?因为当你确定将一个文件删除之后,这个文件之前所对应的那些inode 编号是可以被覆盖的。换句话说在计算机的世界里,你将一个文件删除并不是去到这个文件的具体的块中,将这个文件对应的块的内容重新格式化,而是不管这个块的内容直接将这个块对应的内容覆盖就好了。
如果计算机是去到具体的块上做工作的话,第一个计算机的效率会被降低,第二:会减少对应硬件的使用寿命,第二个原因都是次要的,最主要的就是会将计算机的效率大大降低,想象一下,你删除一个大型文件的速度,和拷贝这个大型文件的速度一样,那样计算机的效率是非常的低的。覆盖写不需要考虑残余的信息,因为你覆盖写的那个文件本来就存在管理信息啊。
由此我们能够知道如果没有计算机去做二进制信息的转化,你拿到了一个磁盘你也不知道这个磁盘中储存的信息是什么。而文件系统就是为二进制序列做解释的。
所以对于一个文件而言你是什么类型的文件,完全由文件的属性来决定。
知道了上面的两个步骤那么查找自然也就是差不多一样的思路了,根据inode编号,判断在inode bitmap中这个文件是否是存在的,存在再去inode Table中拿到这个文件对应的属性,如果你还需要这个文件的内容,那么在根据属性中的那个块下标数组,找到对应的块,然后拿出块中的内容就能够完成对这个文件内容的拿取了。
在Linux中使用
ls -li<这个命令能够看到对应文件的inode编号>
那么cat 命令在Linux底层做了什么我们也能够知道了,cat首先通过路径确定这个文件所在的分区,然后根据inode 编号知道在哪一个分组,让inode编号减去对应分组的起始编号之后,就能够使用这个编号在inode bitmap中索引了,在inode bitmap中确定这个文件的inode是否存在,然后再inode Table中根据下标找到这个对象,然后在对象中根据block数组,拿到这个文件对应的所有块。然后将块中的内容拿出来载入到内容中(根据这个文件的大小,拿取对应的块中的数据,这样就不会出现拿到错误数据的情况,如果这个文件的大小只是12字节,那么只会拿取所有的块中的12个字节的内容,其它的内容不会拿取)。
而stat命令能够查看一个文件的属性。
stat命令在底层做的事情和cat是几乎一样的,只不过他不选要去访问块的内容,而是直接找到这个文件对应的inode对象,就能够得到这个文件的所有的属性了,将这些属性加载到内容中即可。
最后一个修改文件,修改文件,修改文件无非就是修改文件的属性/内容。依旧是根据inode编号找到inode对象,然后再inode对象中修改属性,如果你要修改文件的内容,则根据属性中的block数组找到这个文件对应的块,然后修改块中的内容即可。
在这之前我们就知道了一个知识点那就是在inode中是不会保存文件名的,如果在inode中要保存文件名,如果你将来要获得这个文件名的时候,你是先要inode还是先要文件名字。
那么我们现在知道的以上的所有的步骤都是在一个基础上才去执行的,那就是我们已经知道了一个文件的inode编号,但是我们怎么知道一个文件的inode编号呢?作为一个Linux的使用者我们一般是不会知道某一个文件的inode编号的。 还有一个在没有知道inode之前,我连inode是什么都不知道。即:
既然文件名不在inode中,那么文件名字在哪里呢?然后还有一点那就是我们根据之前所学的知识知道你要访问一个文件必须知道这个文件的inode,那么inode和文件名一定会存在某种联系,这个联系又是什么呢?
要理解上面的两个问题我们就需要去重新理解目录
首先目录也是一个文件,那么目录也就具有了自己独立的inode,那么自然就会有自己的属性。
那么目录有内容呢?如果有那么又是什么内容呢(也就是目录需不需要数据块呢?)
答案当然是要,那么目录这个数据块中写的内容是什么呢?
先说结论:
里面储存的是该目录下,文件的文件名和对应文件的inode的映射关系。
也就是说这里存在一个dir的目录,里面存在一个test的文件这个文件的inode是1(假设)。那么dir这个目录储存的内容就是dir这个文件名和1的映射关系。(即能够根据文件名找到这个文件的inode)。
那么如果现在我在某一个目录下适应ll命令,系统是怎么做的呢?
首先会根据这个目录的inode编号(这里我们先不管这个编号是从哪里来的),然后在inode bit map中确定这个indoe对象是否存在,再去inod额Table中找到这个对象,然后根据inode对象中的block数组找到目录文件保存的内容(也就是这个目录下所有的文件名和对应的indoe的编号,就能够通过文件的名字找到对应文件的indoe编号,然后再根据我们上面的步骤获得对应文件的属性然后打印即可)。
由此我们就能够理解:为什么在一个同一个目录下,不能够创建同名文件。
因为在一个目录下,我们要根据文件的名字找到这个文件对应的inode编号,如果出现了同名文件(同一个目录下),文件系统就不能确定文件的inode和文件名的映射了。
然后是:如果目录没有w权限,我们无法创建/删除文件,原因很简单。因为没有写的权限,我们无法往目录的写入/(删除旧的文件名和inode编号的映射)新的文件名和inode编号的映射关系,自然也就无法创建/(删除旧文件)新的文件了。(我们不要把目录想象的很特殊,目录也是一个文件而已)
然后是:
如果目录没有r权限,我们是无法查看到这个目录下的文件的。原因也很简单,你要查看一目录下的所有文件,你首先就要去查看目录的数据块中的每个文件和inode的映射关系,找到所有文件对应的indoe编号,才能查看这个目录下所有文件的属性/文件名,但是现在你无法查看目录文件的内容,自然也就不能去查看目录下其它的文件了。
然后是最后一个
原因是什么呢?当你使用cd<目录名>的时候,本质就是要修改当前的环境变量(path增加/目录名),你要执行cd的时候,这里会有一个判断,如果目录没有x权限,直接让这个工作无法正常执行就可以了。
对于下面的这四个问题:
现在我们就有了更为深刻的理解:
但是以上的一切还是存在一个问题,那就是这个目录的indoe如何获得呢?
这个目录肯定也是在另一个目录中的,那么我们就需要再去上面一层的目录中寻找,由此不断向前,最终一定会到达到根目录(绝对路径),这个根目录他有文件名字(/),他也有inode也是确定的。找到了根目录的inode,我们就能够知道根目录下所有文件的文件名和inode的映射关系。由此我们能够知道你找任何一个文件都要不断的递归往上,最终到达根目录,由此我们要找到一个文件的时候需要带上一个路径的原因我们也就能够知道了。由此我们也能够知道一个结论了:你要访问任何一个文件你都需要带上路径。
虽然你在某一个目录下使用cat test.txt从你写的命令下你没有看到路径,但是在Linux系统中是存在很多的环境变量帮你记录下了这个文件的路径的。而进程也有自己的当前工作目录,也会记录你要访问的文件的路径。即在os查找一个文件的时候,必须存在路径,那么相对路径呢?那么相对路径那就更简单了,相对路径一定是某一个文件已经被找到了。才存在的相对路径。此时根据相对路径的方式找到某个文件的难度也是不大的。那么这样查找,岂不是很慢。这个理解是正确的,而os为了处理这个问题,os一般会将你最常访问的若干目录以及路径上的所有的inode等等的属性信息缓存起来。这个缓存叫做dentry缓存。
也就是将我们Linux历史上,访问过的常用的目录,都直接导入到内存中提前放好。你要的时候直接在缓存中拿,不用读盘了。
dentry缓存的具体实现是很复杂的。他要记录内容的时候也是很慢的,这也是为什么,但你刚刚使用Linux的时候,你访问一个陌生的目录特别使用find目录,因为他会根据我们上面所学的所有的知识,将对应的信息都录取到对应的缓存中去。这样在系统中会记录一个缓存,能够做到对已经访问过的文件做到快速的跳转。我们只需要知道会将我们访问过的文件和文件路径上的信息都缓存下来就可以了。
此时对于任意一个Linux文件,他在Linux硬盘中是如何储存的在我们现在的脑海中已经有一个模型了(这个文件在哪一个分区,哪一个分组,哪一个inode中)。
由此我们也能够知道在新建文件的最后,一定会存在一个文件名和inode的映射关系,我们需要将这个映射关系写到这个文件所在的目录的数据块中去。
理解了上一篇博客,和下面的所有的内容之后我们下面就去理解一下什么是硬链接,什么是软链接。
首先我们创建一个文件(file.txt)。
我们使用ln 命令建立链接。
ln<建立链接命令> -s
首先是软链接。
此时我们就为file.txt这个文件建立了一个软链接。出现了一个soft-link的文件指向了file.txt。然后我们还注意到一个点那就是这两个文件的两个数字都是1没有变化,这个数字对于链接来说是很重要的。这个数字代表的意思我们在后面会说明。
下面我们去看一下这个soft-link和file.txt的inode的编号是否是一样的。
可以看到inode编号也是不一样的。这说明了这两个文件是两个不同的文件,因为一个文件一个inode。
在这里我们就能够得到第一个结论了:
软链接是一个独立的文件。因为具有独立的inode。
下面我们在创建一个新的文件。
注意此时的这个文件默认的数字是1.然后下面我们在去创建另外一个硬链接。
依旧是使用ln命令。
ln<不带任何的选项代表的就是建立一个硬链接> <第一个文件名是你要建立链接的文件名> <文件的名字>
总结就是ln命令带s代表建立的是一个软链接,如果不带s选项建立的是一个硬链接,软/硬链接都是由后面的文件指向前面的那个文件。
然后我们就会看到多了一个hard-link的文件,然后test.txt文件和hard-link这两个文件的数字变成了2。下面我们来看一下这两个文件的inode编号是否是一样的。
可以看到这两个文件的inode 编号也是一样的。
这个数字也就是硬链接数。这个数字有什么意义我们稍后再说。
我们看到硬链接的两个inode编号是一样的,所以就有了一个新的结论。
硬链接不是一个独立的文件,因为他没有独立的inode。
现在我们已经通过现象得到了两个结论,下面我们在提出一组问题:
我们首先从硬链接。
我们要如何理解硬链接呢?
首先根据inode编号是一样的,对于建立硬链接的两个文件而言,两个文件的属性肯定是一样的,因为他们代表的就是同一个文件(找到了同一个inode对象)。
从这里也变相的验证了文件名是没有保存在inode中的,因为如果文件名也是保存在inode中的,那么建立硬链接的这两个文件的名字也应该是一样的。
由此我们也能够知道了
所谓的建立硬链接,本质其实就是在特定目录的数据块中新增文件名和指向的文件的inode编号的映射关系。
意思就是在一个特定的目录下(存在一个test.txt映射inode编号为1),在这个目录的内容中新增一个hard-link映射inode编号为1的信息。
下面我们删除一个硬链接文件。
此时的test.txt这个文件还存在吗?存在只不过硬链接数由2变成了1。
经过硬链接的操作我成功的做了一个"取别名"的操作对test.txt。对于文件本身而言并没有被修改。
下面我们再来理解硬链接数。
在任意一个文件,无论是目录还是普通文件都存在inode,每一个inode内部都有一个叫做引用计数的计数器。
目录里面保存的是文件名和inode的映射关系。
由此就可以存在下面的情况:
即多个不同的文件名映射同一个inode。
这不就类似于我们之前在语言学习过的一个叫做指针的概念吗?
在现实生活中凡是带有指向性的东西都可以叫做指针,例如在一个数组中,数组的一个下标指向一个值,那么我们就可以把数组的下标当作一个指针。
因为可能存在多个文件名指向同一个inode,所以就存在了这个叫做引用计数的东西。
而这也即是这个引用计数也就是这个硬链接数的概念。
所以正常的情况下当你删除一个文件的时候,这里只不过是去到特定的目录下将对应的文件名和inode的映射关系删除,然后去到引用计数处让这个引用计数减去1.如果减去到0的时候,这个文件才会被真正的删除。
当然之中间还是存在很多问题的。但是我们现在先去理解一下软连接。
即我们如何理解软连接
当软连接在指向目标文件的时候,我们发现这两个文件的引用计数都没有变化。
同时根据inode编号,我们能够看到软连接的这两个文件是两个不同的文件
这告诉我们软连接是一个独立的文件并不会影响原文件的引用计数。
因为软连接是一个独立的inode,所以我们可以把软连接想象成一个独立的文件,它具有自己独立的属性和内容(具有自己的inode对象)。那么这个软连接的文件的内容里面放什么呢?
内容放的是指向文件的路径。即上图中的soft-link这个文件的内容保存的是到达file.txt这个文件的路径。
由此我们就存在了下面的现象:
我们往file.txt文件中写入内容,然后cat soft-link也能够看到我们写入的内容。因为soft-link中保存的是到达目标文件的路径。
所以如果我们将软连接删除了并不会影响文件,但是如果把软连接对应的文件删除了,就会影响软连接。
因为在soft-link的内容中保存的到达file.txt文件的路径错误了。由此我们就能够知道这个软连接就特别的像Windows中的快捷方式。
在这个目标中保存的就是到达这个可执行程序的路径。
将这个路径放到文件管理器上就会找到这个可执行程序。
所以在Windows中你删除快捷方式是没有用的,但是你删除对应的可执行程序,那么这个快捷方式也就没有用了。
下面我们补充一个知识点,删除这种链接文件你可以使用rm,也可以使用unlink。
上面的soft-link就是一个链接文件。
下面我们进入下一个步骤;
即为什么要使用软连接?
首先假设下面是一个可执行程序
然后其它的文件都是这个可执行程序需要的配置文件等等。
然后我的可执行程序也不在这里而是在bin路径下的一个目录中
由此导致了用户未来在执行文件的时候要这么写:
但是这么写,不就显得很麻烦吗?(因为每一个较大型的项目都是会有自己的项目目录结构的)。
所以为了方便所以我们就在这里创建了一个软连接:
往后我们使用下面的方式也就一样能够运行了
此时我们就不用再使用一长串的路径再去启动我们的可执行程序了。
同时这也是为什么在根目录的lib64号目录下能够看到大量软连接的原因。
所以为了在以后我能够不带路径直接执行我们自己的命令(还有一个方法是在环境变量中增加路径)
我们可以在lib目录下建立一个软连接。
然后直接运行我们自己的文件就可以直接运行了
这里我首先就在bin目录下为我的myexe可执行程序建立一个软连接
然后我们直接运行。
运行成功了。这就是真正理解了软连接了
下面我们来理解硬链接解决的问题。虽然硬链接的例子很少但是还是存在一个的并且我们经常使用这个例子。
当我们创建一个file.txt文件的时候,默认的硬链接数是1,这个我们现在已经可以理解了,此时就是file.txt这一个文件名和一个indoe编号建立了映射关系,此时的映射关系只有1个所以这里的数字自然就是1了。
但是当我们创建一个目录,为什么这个目录对应的这个数字却是2呢?
在普通文件中一个文件名和inode对应因此这里的数字是1.我们能够理解,但是为什么一个目录中的这个数字默认是一个2呢?
那是因为
此时的dir有自己的目录名和inode对象对应,这是一组映射关系。当我们进入这个dir之后,不要忘了,任何一个目录都会具有两个文件那就是.和..。
然后我们看到.这个文件的inode编号和dir这个目录的inode编号是一样的。
所以这个.被叫做当前目录,由此我们就能够理解什么叫做当前目录。也就是说在dir这个目录中的.这个文件是,dir这个目录的一个硬链接是.。
1个点已经被我们搞定了,那么我们不得不去谈一下两个点这个东西了。
不要忘了两个点,被我们叫做上级目录。
现在我们正在dir这个目录中,一个点是dir的一个硬链接,那么..自然就是dir的上级目录的一个硬链接了。
在我这里..也就是test这个目录的硬链接了。
如何证明呢?我们去看一下test这个目录的inode编号和这里两个点的inode编号对比一下即可。
下面就是我的test这个目录的inode编号和上面的两个..的inode编号是一样的。由此也就证明了..也就是test这个目录的一个硬链接。
这也是为什么两个点被叫做上级目录。这也是为什么当我们cd ..的时候,会进入到上级目录呢?因为..对应的上级目录,我们就能够找到上级目录对应的inode,找到了上级目录的inode,那么上级目录所在的路径自然也是能够得到的,直接切换路径就完成了。
此时我们就能够当我们创建一个空目录的时候为什么硬链接数是2了,除了自己的目录名和inode编号存在一个映射,在目录中一定会存在一个.文件也是映射的inode编号。
那么如果我在dir里面在创建一个空的目录
此时dir的硬链接数就变成3了,为什么呢?因为在dir1中存在一个..文件,这个..文件代表的是上级目录,也就是dir这个文件的一个硬链接。
可以看到在dir1中的..的inode编号和dir的编号是一样的。
那么下面我们来看一下下面的这个信息
可以看到/也就是根目录也是具有inode编号的,并且这个inode编号很小说明这个目录在很早之前就被创建了。此时的这个/的引用计数是一个20,说明在这个根目录中存在18个目录,原因除了/这个名字和这个inode编号对应之外,还有一个.和这个inode编号对应之外。只有其它文件夹中的..作为/的硬链接才会让这个硬链接数增加了,所以这里我们能够判断至少存在18个目录。
只数文件夹。
证明这是正确的
由此我们就能够理解硬链接了,虽然硬链接我们很少见到,但是硬链接必须存在,因为硬链接能够维持Linux整体的目录结构。
那么我们(用户)能否手动为一个目录创建一个硬链接呢?
答案是不能,即使你使用sudo提权。
那么软连接呢?
为目录创建软连接一般也是为了能够做到直接cd <软连接>直接进入dir文件而已。所有的对应的软连接都是为了方便我们操作的。
可以看到我们(用户)能够给目录创建一个软连接,但是硬链接是不被允许的。
Linux系统不允许对我们的目录建立硬链接为什么呢?
下面是Linux中的一个多叉的一个目录结构。
如果系统允许对目录建立硬链接
此时我们在左下角的那个文件中建立了一个对于根目录的硬链接(如何建立的:ln / root)。然后如果下面我们要执行这个命令
在根目录下寻找一个名字叫做test.c的文件。
既然要寻找肯定是要访问/下每一个目录和它对应的inode和属性的。
然后在这一条线路上
此时我们找到了这个硬链接,这个硬链接又告诉了os要去inode编号为2的文件上去找。
此时就又回到了根目录,此时就造成了一个环路问题。总结就是如果你给目录建立了硬链接,就容易造成这种环路问题,出现系统级别的bug。
因为查找某一个文件的本质是要拿到这个文件的属性/内容,都需要先拿到这个文件的inode,要拿到这个文件的inode,自然要通过目录拿到这个文件名和inode的映射关系,那么就免不了要去访问每一个目录和文件。此时当访问到某一个硬链接的时候就会回到上面的那个问题。所以os不允许用户创建为目录建立硬链接。
但是在Linux中的.和..不就是对目录的硬链接吗?
原因就是.和..是系统为每一个目录文件建立的硬链接,而我们用户和系统之间的权限肯定是不一样的,因为系统是不信任除了自已以外的任何人的,即在系统的硬编码那里建立的。即在Linux中其实是存在为目录建立硬链接的接口的,但是系统会认为用户自己为目录建立硬链接会出现环路问题,所以系统不会让用户为目录建立硬链接,而系统自己为目录建立硬链接,系统会认为自己建立的硬链接是不会出现问题的。但是真的不存在环路问题吗?其实是存在的以下图为例子:
这个理解是没有问题的,但是系统在搜索的时候,是不会对.和..这两个隐藏文件做搜索的。由此也就不存在了上面所说的环路问题。
os因为已经百分百确定用户不会引发环路问题了,所以os就可以很从容的去解决.和..造成的环路问题了(解决方法上面已说)。
那么为什么要存在.和..呢?这不就是为了让我们衍生出一个叫做相对路径的概念吗?
到这里就基本的结束了。
希望这篇博客能对你有所帮助,如果发现了任何的错误欢迎指出。