引言本文是对《文件系统I》的补充。块编号分配之前一直都在说Data Blocks中每个块都有自己的编号那么它们的编号是怎么分配的呢不同分区间是否存在相同编号的块不同分组间是否存在相同编号的块在ext2文件系统中块编号是相对于整个分区进行分配的。当文件系统初始化时整个分区的存储空间就被划分为固定大小的块。整个分区的块数量是一定(超级块中会存储相关信息)的块编号也由此诞生。将整个分区划分成多个块组可能还有Boot Block后将整个分区划分成多个块组和整个分区被划分为多个块是不冲突的例如一个块组中可以包含很多块每个块组再被分为多个区域此时每个块组中的Data Blocks区域会包含一部分数据块其他数据块是元数据区域这些数据块的编号就是文件初始化时诞生的。也就是说Data Blocks区域中的数据块号可能并不是从0开始计数。既然Data Blocks区域中的数据块号并不能从0开始计数那么怎么通过Block bitmap判断该块号是否被使用呢之前说道Block bitmap映射的是整个块组的所有块所以该位图映射的是一个块的组内块号找到对应块的组内块号后然后通过当前的块组索引block_group_index和每个块组的块数量blocks_per_group得到该块的全局块号。即全局块号 (块组索引 * 每组块数) 组内块号反过来我们就能将一个全局块号映射到对应块组的位图中。所以文件系统中并不会直接记录Data Blocks区域的数据块起始编号我们只需知道块组索引和每组块数便能计算出全局块号对应的组内块号从而建立数据块和Block bitmap的映射关系。而这两个数据在文件系统初始化的时候便决定好了它们存储在Super Block中。我认为现在我们对块编号的分配已经有所了解了基于这些知识我的脑海中又出现两个问题不知读者是否有思路。首先既然Block bitmap映射的是整个块组中的所有块那么系统在创建文件时给文件分配空闲块存储内容不会给文件分配那些元数据所在的块吗其次我们计算一个块的全局块号需要依靠块组索引和每组块数有些块组中没有超级块它们又该怎么计算呢最后如果一个文件比较大在一个块组中剩下的数据块不够分配了系统会怎么办呢会跨组分配还是直接将整个文件放到另一个能存下文件的组里去如果每个组都放不下又会如何第一在文件系统初始化时元数据块所在的位已经被标记成1了不出意外的话这些位也不会被清零。因此在分配空闲块时会自动跳过这些块所以不必担心。其次在系统挂载文件系统时会从有超级块的块组中一般是0号块组读取超级块并将blocks_per_group等元数据加载到内存中。之后需要这些数据时可以直接从内存中进行读取即使没有超级块也不用担心。最后在当前块组的剩余数据块不够分配时系统首先会将剩余的数据块分配给文件然后在新的块组中为该文件分配数据块。在这过程中数据块所在的组的位图也会同步更新。如果整个分区的空闲空间都装不下文件的话系统会返回一个错误。对于块编号的分配讲到这里就结束了更细节的问题例如跨组分配后怎么找到其他组的数据块就需要读者自行探索了。块编号分配结束后接下来需要了解的便是inode编号的分配问题了。inode编号分配每个inode都有自己唯一的编号它们的编号又是怎么分配的是否和块编号类似呢它的编号又怎么和位图映射起来的呢和块编号类似inode的编号也是基于分区进行分配的。也就是说每个inode在整个分区中存在一个唯一的编号。这个编号从1开始直到分区格式化时创建的inode总数这个值存放在超级块中。一般而言编号为0的inode不存在或者保留编号为1的inode通常是损坏数据块的链表编号为2的inode是根目录的inode。inode编号的分配在文件系统格式化时已经确定了。在格式化时系统会根据预期的文件数量为每个块组预分配一定数量的inodeinodes_per_group它也会存储在超级块中也就是说每个块组的inode编号是固定的。不过系统也不需要记录每个块组的inode起始编号。和块编号的映射相同inode的全局编号和组内编号可以通过计算进行转换即global_inode_number (block_group_index * inodes_per_group) local_inode_index这也是inode bitmap和inode编号映射的依据。此外在inode分配时会尽量将文件的inode和其数据块放在同一个块组中目录的inode和其下文件的inode尽量放在同一个块组中。如果当前块组中inode已满那么就会在其他块组中寻找空闲inode进行分配。因为inode的数量是格式化的时候预先设定的所以可能会出现分区的inode耗尽但磁盘空间未耗尽的情况文件都很小此时系统就会报错。inode编号分配的知识到此就告一段落更多的细节需要读者自行研究。格式化我们前面一直在说文件系统在初始化时整个分区的就被划分为固定大小的块又说inode编号在文件系统格式化时已经确定了。这里所谈的“初始化”、“格式化”又是什么意思呢文件系统是管理分区的它怎么管呢它需要知道这个分区里有多少块吗它需要知道这个分区要被分成多少组吗正如我们使用文件的属性可以帮助我们更好地管理文件一样文件系统也有自己的属性信息像整个分区有多少块组总计有多少块等等。格式化指的是每一个分区在使用之前都必须提前将文件系统的部分属性信息提前设置进对应的分区中方便我们使用这个分区。问题过渡现在我们对文件系统已经有所了解了接下来我们来看一些问题当我们新建、删除、查找、修改一个文件时系统要做什么呢当我们新建一个文件时系统先去inode bitmap中寻找空闲的inode和它的编号然后通过编号找到inode再接着前往Block bitmap中寻找空闲的数据块以及编号将数据块和inode链接起来即可。至于删除文件我们需要将文件彻底删除吗例如清空数据块清空inode等等。答案是可以但没有必要。思考一下系统如何判断一个数据块或者inode是空闲的呢系统是通过位图来判断数据块是否空闲而不是根据数据块上是否有数据进行判断。所以我们在删除文件时只需要改变位图的状态即可。总之对于系统而言删除并不是消失而是可覆盖。所以删除文件时我们首先按照文件的inode编号在位图中判断该inode是否存在然后通过inode找到文件的数据块编号然后在Block bitmap中将这个数据块编号对应的位置置为0接着在inode bitmap中将该inode对应的位置置为0最后在目录中将文件名删除即可。查找和修改的过程也差不多找到inode找到数据块修改数据块即可。总之我们需要记住在Linux系统中一个文件对应一个inode。一个inode在一个分区内有唯一的编号。我们观察上面所说的过程可以发现系统对文件进行操作时是通过inode编号标识文件的而不是文件名。这是因为文件名并不属于inode内的属性正因如此也无法通过文件名区分inode。那么问题来了我们怎么在删除查找和修改文件时都是使用文件名的几乎不会关心文件对应的inode是什么在我们只告知文件名的情况下系统又是怎么知道文件的inode编号呢在回答这个问题前我们需要了解一下目录。理解目录在这一部分我们首先需要一个前置知识目录也是文件也有自己的inode。我们知道文件 属性 内容。我们已经知道目录是文件也有自己的inode那目录它有自己的内容吗或者说它需要有数据块吗答案是目录有自己的数据块。那里面存的是什么呢实际上目录的数据块中存放着该目录下的文件名和文件对应的inode的映射关系。所以当我们使用“ls -l”指令显示该目录下的文件及其属性时系统会读取目录的数据块中的内容然后根据文件名和inode的映射关系找到对应的inode进而打印出相关属性。这也是我们使用文件名系统能找到inode的原因。同时我们也可以解释以下问题或者现象了。1. 为什么同一目录下不能有同名文件呢当出现同名文件时同一个文件名会映射不同的inode系统就无法通过文件名准确找到对应的inode了。2. 在该目录下没有该目录的写权限就无法创建和删除文件。这是因为创建和删除文件时系统需要修改该目录下的映射关系即对该目录的内容进行修改这个行为需要目录的写权限。3. 在该目录下没有读权限就无法查看该目录的文件列表。这是因为查看该目录下的文件需要对读取目录的内容当然需要读权限。现在我们知道怎么获取一个文件的inode了但是既然目录也是文件我们怎么找到目录的inode呢答案是找上级目录即向上递归。我们也许不知道上级目录的inode但我们知道最上级目录根目录的inode是2之后向下就能找到当前目录的inode了。这也是为什么我们在找一个文件时可以使用绝对路径当然使用相对路径也能做到同样的事。说到相对路径不知道读者对目录下的隐藏文件.有没有印象我有一个问题不知道你能否解答在系统无法找到该目录下的inode时它能否通过该目录下的.文件找到该目录呢希望你自己能找到答案。上面说我们在找文件时会从当前目录向上递归那么每次查找文件都会从根目录开始找吗并不是Linux中会对一些目录进行缓存读者可以自己了解。到这里目录就告一段落了接下来就是比较轻松的内容了。软硬链接在讲解软硬链接究竟是什么之前我们先看看在Linux中怎么建立软硬链接。首先在Linux中建立软链接的指令是link -s file1 file2。该指令的作用是建立一个名为file2的链接该链接指向file1其中的-s我们可以理解成soft。具体的可以看下图图1图中用户所在的目录下存在一个文件“test.txt”其中存放着一个字符串。接着使用指令ln建立一个软链接soft_link该链接指向“test.txt”。接着我们可以使用指令ls -li查看这两个文件的inode编号就会发现软链接的inode和该链接指向的文件“test.txt”的inode编号不同。也就是说软链接是一个独立的文件因为它拥有独立的inode编号。具体可以看下图其中文件属性的第一列就是文件对应的inode编号图2看完软链接后我们再看看如何建立硬链接。建立硬链接的指令与软链接的指令类似只不过需要去掉-s选项具体如下图图3如上图所示我们仍然处于软链接所在的目录下。这次我们建立了一个硬链接test_link该链接仍然指向test.txt而且与软链接不同的是硬链接的文件类别是普通文件。不知读者是否观察到此时文件“test.txt”属性的第二列数字从1变成了2可以看看图1中建立软链接后的文件属性这个数字代表着文件的硬链接数。图4在图4中我们可以看到硬链接“test_link”和被链接的文件“test.txt”的inode编号是相同的也就是说硬链接“test_link”没有自己的inode它并不是一个独立的文件。那么该怎么理解硬链接呢理解硬链接我们之前说过目录本质也是一个文件它的数据块中存放着该目录下的文件名和文件对应的inode的映射关系。而建立硬链接本质就是在特定的目录的数据块中新增一个文件名和inode编号的映射关系只不过在新增的映射关系中inode编号与链接指向的文件的inode编号相同。至于文件的硬链接数则是inode中的一个属性。每一个inode内部都有名为“引用计数”的计数器它统计着“有多少个文件名指向我”。硬链接是新增了一个映射关系因为自己创建的硬链接和被链接的文件指向的是同一个inode所以对硬链接的修改会直接反映到源文件这倒可以理解那么软链接呢它和被链接文件的inode又不相等我们该怎么理解软链接文件呢理解软链接之前说过软链接是一个独立的文件有自己的inode所以它也会有自己的数据块。我们可以认为软链接的数据块中保存的是它所指向的文件路径。形象的说软链接就相当于对应文件的快捷方式。到这里我们已经对软硬链接有所了解了。既然能够建立链接当然也能除去链接。我们可以使用指令unlink file除去链接。软硬链接的应用场景我们学习了软硬链接那么这两者究竟有什么用呢对于软链接我们可以将其作为快捷方式可以链接一个非常深的可执行文件提升我们的访问效率。例如当我们运行自己写的可执行程序时我们需要指明文件路径。但是执行指令时只需要告诉系统指令名称即可。这是因为这些指令都存放在系统的默认搜索路径下因此我们可以在这些默认搜索路径下创建一个软链接这个软链接指向我们自己的程序。之后运行该程序就只需要文件名就可以了。硬链接一般用于路径定位可以进行目录间的切换。图5如图5我们查看我们所在的目录信息就能发现目录对应的硬链接数目是2。而且结果显示的文件名是“.”。我们一般称这个隐藏目录为当前目录这个便是一种硬链接使用这个隐藏目录我们就能定位到当前所处的路径从而运行自己所写的程序。既然目录也是文件那么我们可以建立目录的硬链接吗答案是不行系统不允许我们用户包括root建立目录的硬链接因为如果我们可以建立目录的硬链接那么就会引发目录的环路问题。而系统建立目录时创建的隐藏目录原理上确实也会引发目录的环路问题但是系统在搜索时不考虑这两个隐藏目录包括目录“..”这也解决了这两个隐藏文件的环路问题。到这里文件系统就暂时告一段落了。