文章目录
- 前言
- 一、理解磁盘
- 二、引入文件系统
-
- 引入"块"概念
- 补充OS访问外设的实现细节
- 引入"分区"概念
- [文件内容Data Blocks](#文件内容Data Blocks)
- 文件属性inode
- inode和datablock映射
- [GDT(Group Descriptor Table)](#GDT(Group Descriptor Table))
- [超级块(Super Block)](#超级块(Super Block))
- 几个子问题(格式化、删除文件、文件名存储)
- 证明目录内容是文件和inode的映射关系
- 解决文件权限相关困惑
- 路径解析
- 路径缓存
- 总结
- 挂载分区
- 三、软硬链接
前言
我们在基础IO专题都是围绕打开后的文件展开讨论的,下面我们要认识一下文件打开之前是怎样的,会围绕一下几个问题展开:
1、为什么打开文件要带文件路径?
2、打开文件时操作系统做了什么?
3、没被打开的文件在哪里?如何存放的?
我们目前可以回答第三个问题,没被打开的文件一定在磁盘这样的存储设备上。(下面我们介绍的Ext系列文件系统是专为磁盘等持久化块存储设备设计的文件系统,也就是下面的内容都是围绕磁盘中的文件展开的)
一、理解磁盘
机械磁盘是计算机中唯⼀的⼀个机械设备
磁盘--- 外设
慢
容量⼤,价格便宜
正因为磁盘价格便宜,容量大,所以如今的大型互联网公司都青睐用磁盘来存数据。
我们知道计算机只认识二进制,这个二进制的程序员规定出来的,在不同的设备可能用电的有无或者高低电平表示0、1,而磁盘中有无数细小磁铁,所以磁盘是用小磁铁的正负极来表示0、1的。
磁盘的物理结构

上面是磁盘的物理结构示意图,我们只用知道主轴马达会一直高速旋转,磁头会高速左右摆动就够了。
磁盘的存储结构
扇区:是磁盘存储数据的基本单位,也是操作系统访问数据的基本单位,512字节,它是块设备。
我们一般认为一个磁盘中各个半径不同的磁道所含的扇区数目是一样的,原因是不同磁道扇区的疏密程度不同,一般需要高频访问的数据在内侧,不太高频访问的数据就在外侧。

我们看上图的磁盘结构:
1、盘面和磁头是一对一对应的。
2、并且一个磁盘的所有磁头是共进退的,如上所示,同一时间六个磁头都是访问的是对应盘面同一半径的磁道,我们把这六个磁头所在的六个相同半径的磁道合为一体称为柱面。
下面我们让磁盘动起来:1、磁头左右摆动的本质是在定位哪个磁道
2、盘面旋转的的本质是在确定某个磁道后,定位该磁道(柱面)上的某一个扇区。
如何定位⼀个扇区呢?1、可以先定位磁头(header)。
2、确定磁头要访问哪⼀个柱⾯(磁道)(cylinder)。
3、定位⼀个扇区(sector)。
4、这就叫做CHS地址定位,但是现代磁盘一般是用LBA地址定位,后面再讲。
5、一般磁头、磁道编号从0开始,扇区编号从1开始。
总结:1、扇区是从磁盘读出和写⼊信息的最⼩单位,通常⼤⼩为 512 字节。
2、磁头(head)数:每个盘⽚⼀般有上下两⾯,分别对应1个磁头,共2个磁头。
3、磁道(track)数:磁道是从盘⽚外圈往内圈编号0磁道,1磁道...,靠近主轴的同⼼圆⽤于停靠磁头,不存储数据。
4、柱⾯(cylinder)数:磁道构成柱⾯,数量上等同于磁道个数。
5、扇区(sector)数:每个磁道都被切分成很多扇形区域,每道的扇区数量相同。
6、圆盘(platter)数:就是盘⽚的数量。
7、磁盘容量=磁头数 × 磁道(柱⾯)数 × 每道扇区数 × 每扇区字节数。
8、细节:传动臂上的磁头是共进退的(这点⽐较重要,后⾯会说明)。
9、柱⾯(cylinder),磁头(head),扇区(sector),显然可以定位数据了,这就是数据定位(寻址)⽅式之⼀,CHS寻址⽅式。
磁盘的逻辑结构
理解过程
磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成为卷在⼀起的磁带,那么磁盘的逻辑存储结构我们也可以类似于:

这样每⼀个扇区,就有了⼀个线性地址(其实就是数组下标),sector array[N],这种地址叫做LBA(Logic Block Address)
真实过程
我们在前面理解磁盘展开的时候是以盘面为单位展开的,但磁盘的真实展开,是以柱面为单位展开的。

某一个盘面的其中一个磁道是一维数组,磁盘中该磁道所处的柱面是二维数组,那么整个磁盘本质就是一个三维数组。
所有,寻址⼀个扇区:先找到哪⼀个柱⾯(Cylinder) ,在确定柱⾯内哪⼀个磁道(其实就是磁头位置,Head),在确定扇区(Sector),所以就有了CHS,而CHS这正好对应磁盘三维数组的下标:sector array[C][H][S]。
不管是二维数组还是三维数组,本质还是一维数组,因为数组元素在内存中都是连续存放的,下面是三维数组降维后的演示图:

所以从今天开始我们可以把磁盘当作一个以sector为单位的线性一维数组(操作系统内部并没有定义这个一维数组,所以一维数组只是一个抽象概念),数组的每个扇区元素的下标,就是LBA地址。
所以未来操作系统只用使用LBA地址就可以访问磁盘了,磁盘自己会做LBA和CHS之间的转化。
CHS/LBA地址转换
CHS转成LBA:
1、磁头数*每个磁道扇区数 = 单个柱⾯的扇区总数。
2、LBA = 柱⾯号C*单个柱⾯的扇区总数 + 磁头号H*每磁道扇区数 + 扇区号S - 1。 3、即:LBA = 柱⾯号C*(磁头数*每磁道扇区数) + 磁头号H*每磁道扇区数 + 扇区号S - 1。
4、扇区号通常是从1开始的,⽽在LBA中,地址是从0开始的。
5、柱⾯和磁道都是从0开始编号的。
6、总柱⾯,磁道个数,扇区总数等信息,在磁盘内部会⾃动维护,上层开机的时候,会获取到这些参数。
LBA转成CHS:1、柱⾯号C = LBA // (磁头数*每磁道扇区数)【就是单个柱⾯的扇区总数】。
2、磁头号H = (LBA % (磁头数*每磁道扇区数)) // 每磁道扇区数。
3、扇区号S = (LBA % 每磁道扇区数) + 1。
4、"//": 表⽰除取整。
所以:从此往后,在磁盘使⽤者看来,根本就不关⼼CHS地址,⽽是直接使⽤LBA地址,磁盘内部⾃⼰转换。 所以:从现在开始,磁盘就是⼀个 元素为扇区 的⼀维数组,数组的下标就是每⼀个扇区的LBA地址。OS使⽤磁盘,就可以⽤⼀个数字访问磁盘扇区了。
二、引入文件系统
引入"块"概念
其实硬盘是典型的"块"设备,操作系统读取硬盘数据的时候,其实是不会⼀个个扇区地读取,这样效率太低,⽽是⼀次性连续读取多个扇区,即⼀次性读取⼀个"块"(block)。
硬盘的每个分区是被划分为⼀个个的"块"。⼀个"块"的⼤⼩是由格式化的时候确定的,并且不可以更改,最常⻅的是4KB,即连续⼋个扇区组成⼀个 "块"。"块"是操作系统访问⽂件的最⼩单位。
注意:
1、磁盘就是⼀个三维数组,我们把它看待成为⼀个"⼀维数组",数组下标就是LBA,每个元素都是扇区。
2、每个扇区都有LBA,那么8个扇区⼀个块,每⼀个块的地址我们也能算出来。
3、知道LBA:块号 = LBA/8。
4、知道块号:LAB=块号*8 + n. (n是块内第⼏个扇区)。

所以现在我们站在操作系统的角度看待磁盘,就认为磁盘是一个块设备,每个块都有下标,从文件系统的角度,对磁盘的文件进行访问都是以块为单位的。
至此我们已经完成了对磁盘的完整建模过程,磁盘本质就是: block array[N] 数组,是块设备。
补充OS访问外设的实现细节
其实不止CPU有寄存器,各种硬件外设也有寄存器,只不过数量较少。
我们磁盘为例,磁盘中会有dir寄存器,指示操作系统要对硬盘做什么操作,例如是读还是写。addr寄存器,指示操作系统要对磁盘的哪一个扇区进行访问,一般会对addr寄存器内写入LBA地址。data寄存器,用于存储操作系统的读写数据,所以操作系统对外设的控制本质就是对外设的特定寄存器写入对应数据,然后外设内部的硬件电路会自动进行相应的访问。
引入"分区"概念
其实磁盘是可以被分成多个分区(partition)的,以Windows观点来看,你电脑会有⼀块磁盘并且将它分区成C,D,E盘。如果没有给电脑添加硬盘那么C,D,E就是电脑自带的一块机械硬盘(磁盘)的分区。分区从实质上说就是对硬盘的⼀种格式化。但是Linux的设备都是以⽂件形式存在,那是怎么分区的呢?
分区可以类似治理国家,国家很大,所以需要甚至省政府,市政府...而一块磁盘空间也很大,也需要分区治理,一块磁盘会被分为多个区,而一个区还会被分为多个组,所以我们只用研究如何把一个组管理好,就可以把整个磁盘管理好。下面我们就需要来研究一个磁盘的一个组内部有哪些要素。

文件 = 内容 + 属性,磁盘中文件的内容和属性都需要存储。这里小编先输出两个结论:
1、Linux中,文件的内容和属性是分开存储的。
2、OS文件系统中,OS和磁盘进行IO的基本单位是4kb。
文件内容Data Blocks
Data Blocks 是用来存储文件内容的,以4kb数据块为单位,它占据了绝大部分磁盘的分组空间。 Block Bitmap
用来表示数据块的整体使用情况,它记录了Data Block中哪个数据块已经被占用,哪个数据块没有被占用,每一个比特位的0、1用来表示其中一个数据块是否被占用。
文件属性inode
在linux中,我们一般用结构体 struct inode 表示文件属性,下面是ext2文件系统的inode完整代码:
cpp
/*
* Structure of an inode on the disk
*/
struct ext2_inode {
__le16 i_mode; /* 文件类型 + 权限(如截图中 `-rw-rw-r--`) */
__le16 i_uid; /* 所有者UID的**低16位** */
__le32 i_size; /* 文件大小(字节),截图中touch创建的空文件为0 */
__le32 i_atime; /* 最后访问时间(时间戳) */
__le32 i_ctime; /* inode元数据最后修改时间(时间戳) */
__le32 i_mtime; /* 文件内容最后修改时间(时间戳,对应截图中`10:55`等) */
__le32 i_dtime; /* 文件删除时间(时间戳,未删除时无效) */
__le16 i_gid; /* 所属组GID的**低16位** */
__le16 i_links_count; /* 硬链接数(截图中每个文件为1) */
__le32 i_blocks; /* 文件占用的磁盘块总数 */
__le32 i_flags; /* 文件标志(如是否为特殊文件、日志标记等) */
union {
struct {
__le32 l_i_reserved1;
} linux1;
struct {
__le32 h_i_translator;
} hurd1;
struct {
__le32 m_i_reserved1;
} masix1;
} osd1; /* 操作系统相关字段(兼容Linux、Hurd、Masix) */
__le32 i_block[EXT2_N_BLOCKS];/* 数据块指针数组(直接/间接块,EXT2_N_BLOCKS通常为15) */
__le32 i_generation; /* 文件版本号(用于NFS等网络文件系统) */
__le32 i_file_acl; /* 文件ACL(访问控制列表)的块指针 */
__le32 i_dir_acl; /* 目录ACL的块指针(若为目录时有效) */
__le32 i_faddr; /* 碎片地址(若文件启用碎片存储时使用) */
union {
struct {
__u8 l_i_frag; /* 碎片编号 */
__u8 l_i_fsize; /* 碎片大小 */
__u16 l_i_pad; /* 填充(字节对齐) */
__le16 l_i_uid_high;/* 所有者UID的**高16位**(扩展UID范围) */
__le16 l_i_gid_high;/* 所属组GID的**高16位**(扩展GID范围) */
__le32 l_i_reserved2;/* 保留字段 */
} linux2;
struct {
__u8 h_i_frag; /* 碎片编号(Hurd系统用) */
__u8 h_i_fsize; /* 碎片大小(Hurd系统用) */
__le16 h_i_mode_high;/* 高16位模式(Hurd系统用) */
__le16 h_i_uid_high;/* 高16位UID(Hurd系统用) */
__le16 h_i_gid_high;/* 高16位GID(Hurd系统用) */
__le32 h_i_author; /* 作者标识(Hurd系统用) */
} hurd2;
struct {
__u8 m_i_frag; /* 碎片编号(Masix系统用) */
__u8 m_i_fsize; /* 碎片大小(Masix系统用) */
__u16 m_pad1; /* 填充(字节对齐) */
__le32 m_i_reserved2;/* 保留字段(Masix系统用) */
} masix2;
} osd2; /* 操作系统相关字段(第二部分,兼容多系统) */
};
既然inode是结构体,说明inode的大小是固定的,所以每个inode所包含的成员变量类型和数目是一样的,但不同文件之间inode的成员变量的具体内容一般不同。文件的inode根据文件系统的不同,一般是128字节或者256字节。OS若读取inode,因为一次读取4kb,所以OS一次可以最多可以读取32个inode。
一般一个文件对应一个inode,一个文件可能对应0个或多个Data Block。这里会引出两个结论:1、inode会有一个int类型变量inode,用来表示文件的唯一性。
2、linux中,文件名不能也不在inode中存储。
下面介绍文件属性在磁盘中的存储情况,磁盘的分组中会有一个inode Table用来存放文件属性内容,结构如下图所示,innode Bitmap类似Block Bitmap,表示innode Table的整体使用情况。

下面我们上手实践一下,看我们创建的文件的inode number 是多少,可以通过 -i 选项查看文件的inode number :

现在我们拿到了文件的inode number,就可以拿到文件的属性:struct inode,那如何通过文件属性找到文件的内容呢?实际上文件的 struct inode 中会有一个inode到block的映射关系表:
cpp
__le32 i_block[EXT2_N_BLOCKS];
总结:我们可以通过inode编号访问struct inode,然后根据struct inode内部包含的inode和数据块的映射关系,进一步找到文件的内容。
inode和datablock映射
inode中不仅有文件的属性信息,内部还维护了一张表i_block存储了inode和文件数据data block的映射关系。
一个inode只能映射15组data block数据,前12组是映射到的数据块中的数据就是文件内容,而13-15是映射的数据块中的数据是其他数据块的块号,也就是索引,详情见下表,13、14、15依次的一级索引、二级索引、三级索引。

一个组内的inode是可能映射到同一分区其他组的 data block 的,因为data block 是全区整体编号的。
GDT(Group Descriptor Table)
块组描述符表,描述块组属性信息(包含块组的区域划分情况),整个分区分成多个块组就对应有多少个块组描述符。每个块组描述符存储⼀个块组 的描述信息,如在这个块组中从哪⾥开始是inode Table,从哪⾥开始是Data Blocks,空闲的inode和数据块还有多少个等等。块组描述符在每个块组的开头都有⼀份拷⻉。
cpp
// 磁盘级blockgroup的数据结构
/*
* Structure of a blocks group descriptor
*/
struct ext2_group_desc
{
__le32 bg_block_bitmap; /* Blocks bitmap block */
__le32 bg_inode_bitmap; /* Inodes bitmap */
__le32 bg_inode_table; /* Inodes table block*/
__le16 bg_free_blocks_count; /* Free blocks count */
__le16 bg_free_inodes_count; /* Free inodes count */
__le16 bg_used_dirs_count; /* Directories count */
__le16 bg_pad;
__le32 bg_reserved[3];
};
超级块(Super Block)
存放⽂件系统本⾝的结构信息,描述整个分区⽂件系统信息。等于说GDT管理它所在的一个组,Super Block管理它所在分区的所有组。(一般来说磁盘的每个分区都有各自独立的文件系统)
这里会有一个问题,既然Super Block是管理一整个分区,为什么它不在分区的开头,而是在一个分组的开头?
这其实是一种数据容灾的备份处理:Super Block不仅仅只在一个组里,它可能同时在多个组里存在,但是不一定整个分区中的所有组都有,那么当其中一个分组的Super Block出于某种原因损坏后,不会导致整个分区直接崩掉,并且损坏的数据可以通过其他分组中的Super Block恢复,分组的数据量不是很大,所以分组的GDT只有一份。 记录的信息主要有:bolck 和 inode的总量,未使⽤的block和inode的数量,⼀个block和inode的⼤⼩,最近⼀次挂载的时间,最近⼀次写⼊数据的时间,最近⼀次检验磁盘的时间等其他⽂件系统的相关信息。Super Block的信息被破坏,可以说整个⽂件系统结构就被破坏了。
几个子问题(格式化、删除文件、文件名存储)
1、格式化分区是指给特定分区写入管理信息,即写入文件系统和分组分区的相关管理数据,文件数据可以暂时不写入。所以当我们新建一个分区时会就会将该区格式化。
2、删除文件不用释放掉文件inode Table和Data Block中的数据,只用把它在 inode Bitmap 和 Block Bitmap 中的对应位置置为0就行了。这其实可以解释我们平时在下载文件需要很长时间,但是删除文件只需要1、2秒的原因。
这里可以引申出一个结论:计算机删除文件,只用设置数据无效就行了。
3、要访问一个文件,只能通过该文件的inode,所以我们要拿到它的inode编号,因为只有inode编号可以在一个分区内表示文件的唯一性。
4、inode编号和data block编号是全区统一分配的,不是只在一个组内有效。所以super
block中会维护该区中每个组的中有多少个block和inode。 不同分区的inode编号和data
block编号就互不影响,可以重复了。

5、我们之前谈过,文件名不在文件的inode struct中,那文件名应该在哪里?其实普通文件的文件名在它所在的目录的文件内容中,因为目录也是文件,所以目录也有文件内容和文件属性。但普通文件的文件名并不是单独的存在目录内容中,而是以文件名和它对应的inode的映射关系形式存储的。所以我们平时用的文件名本质是指定目录下的映射关系。这也解释了为什么同一目录下文件名不能重复。
6、从今天开始,在文件系统的角度,普通文件和目录文件的存储方式没有任何区别,无非就是存储inode和数据块,只不过内容不同。区分普通文件和目录文件是用文件的struct inode中的权限成员变量i_mode的第一个比特位:"-"表示普通文件,"d"表示目录文件。
证明目录内容是文件和inode的映射关系
cpp
// readdir.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char* argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
exit(EXIT_FAILURE);
}
DIR* dir = opendir(argv[1]); // 系统调⽤,⾃⾏查阅
if (!dir) {
perror("opendir");
exit(EXIT_FAILURE);
}
struct dirent* entry;
while ((entry = readdir(dir)) != NULL) { // 系统调⽤,⾃⾏查阅
// Skip the "." and ".." directory entries
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..")
== 0) {
continue;
}
printf("Filename: %s, Inode: %lu\n", entry->d_name, (unsigned
long)entry->d_ino);
}
closedir(dir);
return 0;
}
解决文件权限相关困惑
1、在指定目录下新建文件的本质是将文件的文件名和inode的映射关系写到目录的内容data block里,相当于新建文件本质要对目录做写入操作,所以这里可以解释之前讲的为什么在目录下新建文件,需要目录有w权限。
路径解析
我们已经知道要访问一个文件首先要拿到它的inode,拿着文件名要找inode就需要访问它的上级目录内data block中的文件名-inode映射关系,而这个上级目录也是一个文件,要访问它有需要拿到它的inode,而它的inode又在更上级的目录中...直到打开根目录为止,这个过程类似递归。所以我们访问要linux下任意一个文件,都需要从根目录开始,对该文件上的所有目录进行解析,也就是依次打开路径上的所有目录(如 /home/fdb: 打开根目录,拿着home文件名就能在根目录中找到home的inode,就能打开home了...依此类推),这个过程就叫做路径解析。
这就是为什么打开一个文件、访问文件必须要有该文件路径的原因,也是为什么PCB中会维护cwd(当前进程的工作路径)的原因。
路径缓存
我们学习了路径解析后,我相信大家和我都有一个疑问,每访问一个文件都要做路径解析,这样是不是太慢了啊?所以linux还会支持一个名叫路径缓存的功能。
在linux系统中,当用户访问指定路径下的文件时(包括路上目录,最终的目标文件在内),linux会在进路径解析的过程中,在内核里形成目录树和路径缓存,在整个树形结构中,普通文件和空目录就是叶子结点。

1、Linux中,在内核中维护树状路径结构的内核结构体叫做: struct dentry。
2、每个⽂件其实都要有对应的dentry结构,包括普通⽂件。这样所有被打开的⽂件,就可以在内存中形成整个树形结构。
3、整个树形节点也同时会⾪属于LRU(Least Recently Used,最近最少使⽤)结构中,进⾏节点淘汰。
4、整个树形节点也同时会⾪属于Hash,⽅便快速查找。
5、更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何⽂件,都在先在这棵树下根据路径进⾏查找,找到就返回属性inode和内容,没找到就从磁盘加载路径,添加dentry结构,缓存新路径。
Linux内核内存中的 struct file 内部会有 struct dentry 结构体,struct dentry 内会有 inode,所以我们之前介绍struct file内的三大内容之一的文件属性就在inode中。内存中的inode是由磁盘的inode在将文件加载到内存时初始化的,我们前面介绍的ext2_inode就是磁盘级inode。
总而言之,小编只想让大家理解一点,打开一个文件首先会对该文件进行路径解析、对每个路径结点检查 / 创建 dentry,dentry中还会关联inode, 并将dentry结构挂进目录树中,之后会为其创建struct file,并将struct file与刚创建的dentry相关联,所以我们就可以通过struct file访问该文件的内容和属性了(访问文件内容也需要通过inode),文件描述符封装了file,所以最上层就能通过文件描述符访问文件的内容和属性了。
总结
1、当我们要对磁盘中的文件进行增删改查时需要先将文件加载到内存中,在内存中修改后再刷新回磁盘。
2、文件在内存中用文件描述符来唯一标识文件,文件在磁盘中用inode来唯一标识文件。
挂载分区
1、一个磁盘,必须分区格式化,才有使用的前提。
2、一个分区,必须挂载到指定的目录才可以被真正的使用。
3、所以,可以根据访问⽬标⽂件的"路径前缀"准确判断我在哪⼀个分区。
三、软硬链接
操作
在linux中,还有两种链接文件:软连接和硬链接。在讲解原理之前,我们先认识一下操作,如何用指令创建这两种链接文件:

原理和区别

我们看上图会发现软链接是一个独立的文件,而硬链接不是一个独立的文件。
软链接的文件内容是该链接文件指向文件的路径字符串,例如test-soft的文件内容就是test.c文件的路径字符串。
硬链接本质是在指定目录下,建立新的文件名和inode的映射关系,并没有在系统层面创建新的文件。文件的硬链接数本质是有几个文件名指向特定的inode,相当于是一种引用计数,删除一个文件名时若该文件名映射的inode的引用计数减1,若引用计数为0了才会真的释放掉文件的属性和内容所占用的磁盘空间。
应用场景
软链接:
1、快捷方式。
2、让用户无感知进行软件升级。
硬链接:1、对文件进行备份,并且备份后该文件只会占用一份文件的属性和内容空间,因为创建硬链接本质只是创建了一组文件名和inode的映射关系。
一些问题
1、为什么新建的普通文件硬链接数默认为1? 因为只有一组文件名和inode的映射关系。
2、为什么新建的目录文件(空目录)硬链接数默认为2? 因为目录不仅有目录名和inode的映射关系,目录里还有一个隐藏目录文件 . 表示当前目录,所以还有一组 . 和inode的映射关系。当我们在该目录下再创建一个目录文件时,目录的硬链接数会变为3,因为新建的目录文件里会有一个隐藏目录文件 .. 指向当前文件。

3、一个目录的子目录数是它的硬链接数减2,因为它本身要占两个(目录名和 . )。
4、在用户层面,我们可以给目录、普通文件设置硬链接,但是不允许对目录设置硬链接,因为对目录设置硬链接会发生路径环路问题,如下图。当我们想对该路径进行路径解析时,打开最后一个目录home-hard就相当于打开了home,所以会继续从home开始进行解析,形成环路问题。

那为什么系统却允许的 . 和 .. 的存在呢,它们不就是对目录的硬链接吗?这其实是系统的一种特殊处理,系统通过底层逻辑避免了环路风险。
以上就是小编分享的全部内容了,如果觉得不错还请留下免费的赞和收藏
如果有建议欢迎通过评论区或私信留言,感谢您的大力支持。
一键三连好运连连哦~~
