1. 汇总 ext2 文件系统
-
新建文件
一个文件一定是在某个路径下创建的,有了路径,就能够知道该新建文件所在分区,然后再查 GDT,发现 inode 还没分配完,于是遍历 inode Bitmap 找到最近一位没有被使用的 inode 编号(即比特位为0), 再通过 inode Table 找到对应的 inode,把文件属性填充到 inode 中,如果文件中没有写入数据,那么文件的新建,到这一步就完成了。如果后续对文件写入数据,确定了写入数据的大小,接着根据 inode 编号找到文件所在分组,然后在 Block Bitmap 中分配足够的数据块,并且把这些数据块的编号填充到 inode 属性中,最后将数据写入对应的数据块。 -
删除文件
与新建文件相反。先根据 inode 找到文件属性,再根据其中的 block 数组,通过 Block Bitmap 映射,将所有数据块编号所对应的比特位置 0,再通过 inode Bitmap 映射到对应的 inode,将该比特位置 0 即可。因此,删除的本质只是允许被覆盖,并不会真正去删除文件的属性和内容这些数据。如果删除文件时,就直接删除数据,更加频繁的 IO 会导致整机效率的降低,并且存储设备的寿命减少。 -
查找文件
根据文件所在路径确定分区,再根据文件 inode 编号确定所在分区,查找 inode Bitmap 发现文件有效之后,通过位图索引到 inode Table 中对应的 inode,读取文件属性。如果类似cat file.txt
读取文件内容,那么就再通过 inode 中的 block 数组找到所有的数据块,把数据块内容做拼接,最后显示打印在显示器上。 -
修改文件
不管是 chmod 修改文件权限,还是 touch 修改文件时间,又或者修改文件内容。只要拿到文件 inode 编号,修改属性的话就直接对 inode 中的属性做修改。修改内容,如果原来数据块空间不够用了,那就在 Block Bitmap 中重新找到可用的数据块,分配给该文件inode,最后把新增的数据块映射在 inode 中的 block 数组上不就行了。
2. 文件名 与 inode 的关联
在 linux 系统中,一个文件有一个 inode,每一个inode都有自己的 inode 编号( inode 分配,是以分区为单位的,不能跨分区),inode 中记录了文件的所有属性,但不包括文件名,文件名并不属于 inode 内的属性!
但是,作为操作系统的使用者,我如何得知一个文件的 inode 编号?用户可从来不关心 inode,在对文件进行操作时,用的也都是文件名啊! 那我们平时用的文件名到底和 inode 之间有什么关联呢??
目录也是文件,也有 inode,因此目录也有自己的文件属性!
而目录不仅有属性,它也是有内容的,所以目录也需要分配数据块。目录文件的数据块存放的就是该目录下所有文件名 与文件对应的 inode 编号之间的映射关系,是一组 kv 结构的数据。
换言之,如果我想要找到目录下的某一个文件时,必须先找到该目录文件的 inode,然后根据该目录文件的 inode,找到其属性和内容,读取它的数据块,就能够知道该目录下的文件和对应文件的 inode 的映射关系,进而找到文件。
- 所以我们也就自然能够理解为什么同一目录下,不允许存在相同的文件名,因为一切文件操作是根据文件的 inode 编号执行的,而目录的数据块中,是以文件名作为 key 值映射查找文件的 inode,所以文件名当然不一样,不然怎么映射对应文件的 inode,每个文件的 inode 可是具有唯一性的!
- 如果目录没有 w 权限,是无法创建文件,删除文件等一切对文件做写操作的行为。这是因为,如果目录权限规定了不具备写权限,那么即便文件在该目录已经被创建出来了,该文件的文件名与 inode 编号的映射关系也无法写入目录的数据块中, 没有映射关系,别说写文件了、删文件了,就连该目录下的文件的属性和数据,你都无法找到。
- 同理,如果目录没有 r 权限,无法查看文件属性和内容。目录不让用户读取数据,用户就无法读取目录的数据块,因此也就无法拿到该目录下的文件和对应文件的 inode 编号的映射关系,也就拿不到文件的 inode,一切文件操作就无法进行。
- 目录没有 x 权限,我们就无法进入该目录。当 cd 进入目录时,需要拿到目录的 inode 编号,然后把更新系统中的环境变量的信息,但是这中间会多一条判断,如果发现该目录没有 x 权限,则驳回一切请求。
到这里,大家必须要有一个疑问:
找一个文件时,需要先找到该文件所在目录的 inode,进而根据目录文件的 inode 找到其数据块,才能根据文件名知道我这个文件的 inode,这一点没问题,但是我怎么知道该目录的 inode 呢??一个目录一定是另一个目录的子目录,所以按照这种逻辑,我是不是得一路向上递归,想要找这个文件 inode,就得找当前目录的 inode,想要找当前目录的 inode,就得找上级目录的 inode,这不就是套娃吗?!
好在我们的根目录的名字是固定的,因此根目录的 inode 一定能够找到,因为它是不变的。所以只要我递归找到根目录的 inode,我就能够一层一层把 inode 返回,最终找到我这个文件所在目录的 inode,然后找我的文件 inode。这也是为什么我们在进行文件操作时,一定需要有路径,没有路径是找不到当前文件的 inode 的!
你说的不对!我在执行 ls,touch,mkdir 这类指令时,我压根就没带路径!
不!那只是你觉得没带路径,诸如上述这种系统指令,其程序所在目录早就在系统中的环境变量中配置好了,当你执行这些指令时,操作系统会自动带上环境变量中的路径。任何文件执行起来都会带上路径,包括自己写的可执行程序文件,执行起来变为进程,进程也有当前工作目录 cwd.
可是,如果是按照这种思想,我要找一个文件,需要一直递归找到根目录,再把 inode 层层返回才能拿到我想要的文件的 inode,那岂不是太慢了。
没错的,所以操作系统对此采用了一个 dentry 缓存,操作系统会把常访问的若干目录,以及递归查找目录 inode 时经过的各种文件名,文件对应的 inode 属性信息,包括使用过的路径信息全部缓存起来,这样就不需要每次都递归查找文件 inode 了。
3. 软硬链接
ln -s file.txt sotf-link # 软链接
ln test.txt hard-link # 硬链接
[outlier@localhost inode]$ ls -li
total 0
# 【硬链接数】/【引用计数】
34150680 -rw-rw-r-- 1 outlier outlier 0 Aug 13 16:07 file.txt
34165047 -rw-rw-r-- 2 outlier outlier 0 Aug 13 16:07 hard-link
34150681 lrwxrwxrwx 1 outlier outlier 8 Aug 13 16:07 sotf-link -> file.txt
34165047 -rw-rw-r-- 2 outlier outlier 0 Aug 13 16:07 test.txt
软链接是一个独立的文件,对 file.txt 建立软链接,sotf-link 的 inode 编号与 file.txt 并不相同!因为软链接具有独立的 inode!
相反,硬链接不是一个独立的文件,因为太没有独立的 inode,它的 inode 编号与 test.txt 是相同的。
3.1 理解硬链接
对 test.txt 做了硬链接,hard-link 与 test.txt 是同一个 inode,所以就注定了它们的文件属性是一模一样的。因为 inode 是同一个,因此在文件系统上,inode Table 里面最终找到的都是同一个 inode,里面的文件属性自然也就一样了,不仅如此,它们所指向的数据块也是一样的。这也就再次印证了,inode 中不保存文件名信息;如果 inode 中保存了文件名,那么就不可能出现同一个 inode 会有两个不同的文件名的现象。
还记得上面讲过,一个文件的文件名与其对应的 inode 编号的映射关系,就存储在所在目录的数据块中!那硬链接做了什么事呢?硬链接的本质就是在特定目录的数据块中新增 文件名 和 硬链接指向的目标文件的 inode 编号的映射关系!
任意一个文件,无论是目录还是普通文件,都有 inode,每一个 inode 内部都有一个引用计数。而目录的数据块保存的是 文件名 与 对应文件的 inode 编号的映射关系,假如有 file1、file2、file3、file4 都与编号为 123456 的 inode 建立了映射关系,换言之,这几个文件名都指向了 inode123456,而引用计数就是记录有多少个文件指向当前 inode!
所以如果这种说法是正确的话,那么当我删除一个文件名时,本质就是 引用计数 -1 的操作,直到引用计数为 0 时,才会清空该文件 inode 所对应的位图结构(Block Bitmap 和 inode Bitmap)
而我们创建出来的普通文件,其引用计数一定是 1,因为在该目录下,只有你创建出来的这个文件名与该文件的 inode 编号做了映射啊!
3.2 理解软链接
软链接与目标文件的 inode 编号并不相同,这就说明软链接是一个独立的文件,也就拥有独立的文件属性和数据块,它的数据块保存的就是指向目标文件的路径!
所以只要我对软链接做读取,是可以读取到目标文件的内容的。
既然软链接是一个独立的文件,所以把软链接删除,不会影响目标文件;反之,软链接文件的数据块存储的是目标文件的路径,因此把目标文件删除了,软链接也就无法使用了。软链接的作用就相当于 windows 平台中的快捷方式!
3.3 软硬链接的应用场景
-
软链接:
在发布一个应用程序时,很多都不止是一个可执行程序那么简单,还会有与该应用息息相关的各种配置文件 conf,也有 bin 目录下的各种二进制文件,也包括可执行程序。但一个应用程序发布时,往往都不会把可执行程序直接暴露给用户,而是对其做一个软链接(即快捷方式),供用户使用。 -
硬链接:
在一个路径下,创建一个新目录 dir,为什么它的硬链接数就是 2 了??
因为每个目录都会存在两个隐藏文件,一个
.
当前目录,一个..
上级路径,.
不就是指向当前这个目录 dir 吗,加上 dir 自己,一共不就有两个文件名指向 dir 目录吗??并且可以看到.
这个隐藏文件的 inode 编号与 dir 目录的 inode 编号是一样的,换言之,.
就是目录 dir 的硬链接!所以 dir 目录的引用计数 / 硬链接数自然就等于 2 了。再看,所以为什么
..
的硬链接数位 3 呢?? (上级目录名假设为 inode)..
代表上级目录,上一个目录也得有.
和..
这两个隐藏文件吧。所以上级目录 inode 自己 + inode 目录内的.
文件,再加上 dir 目录内的..
指向 inode 目录,一共就有三个文件指向 inode 目录!因此 inode 的硬链接数为 3,也即 dir 目录内的 … 硬链接数为 3,它们是指向的都是同一个 inode 和数据块。所以,如果我在 dir 目录下再创建一个 subdir,那么 dir 的引用计数将会变为 3,原因与上面同理,dir 自己 + dir 目录内的
.
文 + subdir 目录内的..
,一共三个文件指向 dir 目录,因此 dir 的引用计数为 3。所以我们是能够发现一个规律的,一个目录内部如果没有子目录,那么它的引用计数为 2,多新增一个子目录,那么引用计数就会 + 1,因为新增的目录里面会有一个
..
文件指向上级目录。因此我们查看一个目录的引用计数,就能够得知该目录下有多少子目录。所以硬链接通常用来进行路径定位,采用硬链接,可以进行目录间的切换。但作为用户,linux 不允许用户为一个目录建立硬链接,因为这样会导致文件系统出现环路问题,但目录内的两个隐藏文件的本质也是一个环,只不过那是操作系统的设计者建立的硬链接,操作系统设计者可以通过硬编码完成这件事,但并没有给操作系统的使用者这个权限,root 用户也不具备该权限,所以给目录建立硬链接,我们用户无法完成。
环路问题:当我们给一个目录 dir 建立链接 hard-link 之后,我们 find 查找一个普通文件时,查找的路径上如果碰到了dir的硬链接,那么就会指向 dir,然后就又回到了 dir 目录开始往下找,这就是环路问题。同样的,假如你 find 查找
..
这类文件时也会导致环路问题,所以操作系统在搜索时不对目录下的两个隐藏文件做搜索。
如果感觉该篇文章给你带来了收获,可以 点赞👍 + 收藏⭐️ + 关注➕ 支持一下!
感谢各位观看!