1. 磁盘结构
磁盘在我们的计算机中有着重要的地位,当文件没有被打开时其数据就存储在磁盘上,要了解磁盘的工作原理先要了解磁盘的结构。
1.1 磁盘的物理结构
以传统的存储设备机械硬盘为例,它通过磁性盘片和磁头来读写数据。磁盘内部有多个旋转的磁盘(盘片),磁头通过移动到不同的位置来读写数据。
机械硬盘的结构主要有:
盘片(Platters):硬盘的存储介质,通常由铝或玻璃制成,表面覆盖有磁性涂层。数据以磁化的形式存储在盘片的表面。每个盘片都有两个面可以用来存储数据。
磁头(Read/Write Heads):磁头用于读取和写入数据。每个盘片面上都有一个磁头,通过磁头的电磁作用来改变磁化状态,进而写入数据;或者通过读取磁化状态来获取数据。
主轴电机(Spindle Motor):驱动盘片旋转的电机,通常有几千转每分钟(RPM)的速度。常见的转速有5400 RPM和7200 RPM,较高的转速意味着数据读取和写入速度更快。
磁头臂(Actuator Arm):连接磁头和驱动电机的部分,负责移动磁头,使其能够定位到盘片的不同区域。
控制器(Controller):硬盘的“脑袋”,负责控制硬盘的读写操作、定位磁头、管理数据的存取等。
机械硬盘的成本相对较低,存储容量大。但是速度较慢,尤其是随机访问速度,因为磁头需要移动到正确的位置读取数据。
1.2 磁盘的存储结构
机械硬盘(HDD)采用的是一种基于磁性存储的结构,数据被存储在盘片上,并按照一定的方式进行组织,以便磁头能够快速且高效地读取或写入数据。
磁道(Tracks):磁道是硬盘上盘片表面上一个圆形的存储区域。每个盘片上都有多个磁道,磁道的排列是同心圆形状,磁道的数量通常取决于盘片的密度和大小。硬盘上的每个磁道可以看作是一个线性存储区域,数据按照顺序存储在这些磁道上。
扇区(Sectors):扇区是硬盘上磁道的最小存储单元,硬盘通过扇区来管理数据的存储和读取。每个扇区通常存储512字节的数据。磁盘将数据按照扇区来组织和存储,因此每个扇区都有一个编号。当硬盘控制器发出读写请求时,它会指定具体的扇区编号,以确定需要访问的物理位置。当磁头在磁道上移动时,它会按照一定的顺序访问磁道上的每个扇区。读取或写入操作的最小单位通常是扇区。扇区的编号是从1开始的。
磁道相对于磁盘面是同心圆环的分布方式,扇区则是同心圆环上的一段扇环。对于老式磁盘而言,采取的是非分区记录方式,即不同磁道的扇区数目相同。此时所有的扇环圆心角大小相等,又因为扇区存储的都是512字节的数据,所以由内向外存储密度逐渐减小。新式的磁盘采取的则是分区记录方式,不同磁道扇区数不相同。
柱面(Cylinders):柱面是硬盘上由多个盘片的相同磁道所组成的一个三维概念。硬盘有多个盘片,每个盘片上都有若干个磁道。在硬盘工作时,磁头会在同一时间访问多个盘片上的相同磁道,这些磁道在物理上是垂直对齐的。
1.2.1 扇区的定位——CHS寻址
磁盘读写就是依靠这些结构完成的。
磁盘的盘片是始终处于高速旋转(固定速率)中的,磁盘有多个盘片,而盘片的两个面都是可以读写的。读写工作由磁头来完成,每一个磁盘面都有一个自己的磁头,所有的磁头被机械臂连接在一起,他们是同时做同样的运动的。
当需要对某一个位置(扇区)进行读写操作时,硬盘控制器会控制磁头移动到对应的磁道,然后等待盘片旋转到指定扇区位置后,对应盘面的磁头就开始读写数据。
定位扇区实质就是定位圆柱体内一个点,类比柱坐标系需要三维数据:半径、高、角度,通过上面的磁盘结构与读写过程,定位一个扇区就需要柱面号(半径)、磁头号(高)、扇区号(角度)。
于是我们可以计算出磁盘容量=磁头数×柱面数×每道扇区数×扇区字节大小。
1.3 磁盘的逻辑结构
1.3.1 逻辑地址块(LBA)
对于一个磁盘,我们使用的CHS寻址方法实际上是一种三维寻址方法,三个维度分别是面号、磁头号、扇区号,于是这实际上就是一个三维数组。而我们知道在C语言中,几维数组都好,它们实质上在物理空间中是连续存储的,相当于是一个一维数组一样。
对于磁盘而言也是一样,扇区是基本的单位,多个扇区在一起就组合成了磁道,所有盘面的同位置磁道共同组合成为一个柱面,多个柱面就构成了整个磁盘。
于是对于这样的一个逻辑结构的磁盘,我们抽象成为了一个数组结构,就可以通过线性地址的方法来定位任何一个扇区了。
1.3.2 CHS与LBA的转换
需要注意的是,在CHS下柱面号、磁头号是从0开始的,扇区号从1开始。
CHS→LBA:
LBA=柱面号×单个柱面扇区数+磁头号×单个磁道扇区数+扇区号-1
LBA→CHS:
柱面号=LBA/单个柱面扇区数
磁头号=(LBA%单个柱面扇区数)/单个磁道扇区数
扇区号=LBA%单个磁道扇区数+1
2. 文件系统
2.1 磁盘分区
2.1.1 物理块
磁盘以扇区作为基本的存储单位,当操作系统和磁盘IO交互时,由于磁盘扇区大小为512字节,单次交互数据量少,所以会一次性交互1KB、2KB、4KB、8KB等大小的数据,而其中以4KB最为普遍,因此抽象出物理块的概念,将4KB大小作为一个物理块的大小。于是一个块中就包含了8个扇区,也就是8个LBA地址。在与操作系统交互时,基本存储单位就是块,此时磁盘也可以看作是以块为单位的一维数组。
因为我们知道一个块对应着8个LBA地址,于是就二者之间的转换就非常容易了。
2.1.2 分区
磁盘分区在我们使用电脑的过程中会有很多使用经验。磁盘分区是对硬盘物理存储空间进行逻辑划分的过程,通过分区,硬盘可以被划分为多个独立的区域,每个区域可以被操作系统视为一个独立的存储设备,即不同分区允许使用不同的文件系统。
可以看到我的机器具有三个分区。
对于一个磁盘而言,我们在将其看作一个以块为单位的一维数组后,对其分区相当于就是对这个一维数组进行分割,划分成为几个区间。于是要指明分区的具体大小与位置,只需要给出各个分区的起始和终止块号即可。
2.2 Ext2文件系统
2.2.1 分组
硬盘(Disk)被分为了多个分区(Partition),对于任意一个磁盘分区,它的开头会存在一个启动块(Boot Sector),这个启动块的⼤⼩是确定的1KB,⽤来存储磁盘分区信息和启动信息,任何⽂件系统都不能修改启动块。在其之后才是真正的分区的文件系统。
为了更好地管理分区的内容,Ext2文件系统对一个分区的空间继续进行划分,这样就划分出了很多的块组(Block Group)。
得到的分组都具有同样的结构,包括 Super Block、Group Descriptor Table、Block Bitmap、inode Bitmap、inode Table、Data Blocks。
2.2.1.1 inode
文件是由属性和内容两部分组成的,因此属性也需要存储在磁盘中。为了将文件属性组织起来,Ext2文件系统使用inode的结构体对其属性进行描述,记录了文件的权限、属主、时间、大小等信息以及文件的i结点编号。通过ls的-i选项我们就可以看到文件的inode值,他们的值是不重复的。
每一个文件都有着自己的inode,inode结构体的大小是固定的,一般而言大小为128字节或256字节。
2.2.1.1 超级块(Super Block)
超级块中存放的是文件系统的结构信息,描述了所在分区的文件系统结构,包括分区的inode和block的总量,inode和block的剩余数量,分区最近读写时间等等。
作为存储分区信息的块而言,本来应该是属于分区结构一层的,为什么会将其下放到块组结构中呢。这是因为超级块因为记录着分区信息,所以十分重要,一旦出现错误就会使得整个分区文件系统崩溃。将其放在组的层级之中,就可以创建多个备份来保证文件系统的健壮性。
每一个分区都有多个组,其中第一个组一定会有超级块的备份,其余的个别组会具有超级块的备份,以此来保证当Super Block的信息被破坏后还有备用的信息。
2.2.1.2 块组描述符表(GDT/Group Descriptor Table)
GDT中存放的是块组的属性信息,在每一个块组中都有一个,描述了当前组的结构信息。包括inode Table开始的块号、Data Blocks开始的块号、inode和Data Block剩余的数量等。
在此做一下小结和区别,我们当前的层次一共有三层。
①第一层是磁盘被分为多个分区。每个分区从何处开始,到何处结束的信息由分区表记录,而这个分区表位于磁盘的主引导记录(MBR,Master Boot Record),通常在磁盘第一个扇区。——磁盘内的分区信息→主引导记录
②第二层是分区被分为多个块组。每个块组的大小、起始结束块号等块组信息由超级块(Super Block)记录。超级表位于分区下第一个组块中和其他某几个组块中。——分区内的块组信息→超级块
③第三层是组块被划分为不同的区域。由块组描述符表(GDT)来记录块组内Block Bitmap、inode Bitmap、inode Table、Data Blocks的起始结束块号与块总量和剩余量等信息。——块组内的块结构信息→GDT
2.2.1.3 块位图(Block Bitmap)
块位图以位图的形式管理数据块的占用情况,每个块对应一个bit位。
2.2.1.4 inode位图(inode Bitmap)
inode位图以位图的形式管理i节点表的占用情况,每个inode值对应一个bit位。
2.2.1.5 i节点表(inode Table)
i节点表中存储的就是当前组内所有的文件属性信息,值得注意的是文件名并是inode的字段。inode的结点编号分区之间相互隔离,即同一分区内inode编号是唯一的,不同分区下inode编号可以相同。
在inode结构体内存在着一个数组,用于指向文件对应的文件内容存储的块。出于inode结构体大小的128字节限制,这个数组一般有15个元素。
前12个属于直接块指针,即直接指向文件对应的数据块。这种方式很快,但是因为一个元素只能指向数据块,所以12个指针一共可以容纳12*4KB=48KB大小的文件。
第13个指针是一级间接块索引表指针,即先指向一个块,这个块中存储着文件的数据块的直接块指针。于是可容纳文件的大小就增加了4KB(索引表大小--一个块)/4B(一个块号占的字节数)*4KB(一个数据块的大小)=4MB。
第14个指针是二级间接块索引表指针,即指向的一个块是索引,这个块再指向的4KB/4B=1K个块也是索引,所以最后可容纳文件的大小增加了1K*4MB=4GB。
第15个指针是三级间接块索引表指针,即指向的一个块是索引,这个块再指向的4KB/4B=1K个块也是索引,这个二级索引指向的块也是索引,所以最后可容纳文件的大小增加了1K*1K*4MB=4TB。
通过这样间接块索引的方法即可让仅仅只有128KB的文件能够管理GB级别的文件。当文件较小时,只需要使用前12个指针,速度最快,随着文件的大小增加,再启用后续的索引块。
3.3.1.6 数据块(Data Block)
在数据块中存放的就是各个文件的文件内容了,可以通过文件i结点表映射找到数据块中需要的内容。实际在分组中占据空间最大空间的是Data Blocks。
2.2.2 格式化
文件系统格式化是在磁盘分区之后对分区进行初始化的一系列操作,目的是在一个已分区的存储介质上建立一个结构化的方式来存储和管理数据,方便操作系统进行读写和管理,通俗来说格式化相当于将搭建整个文件系统的框架好。
格式化一个分区,对于每一个块组,首先会创建超级块,记录了分区内的块组信息;然后创建GDT,管理块组结构;然后将两个位图初始化为全0;然后创建inode表与数据块区域。于是格式化后,分区有几个块组、每个块组大小、分配的inode数量等结构信息都已经被决定。
由于一个块组内inode的数量和数据块的数量已经在格式化时被决定,所以会出现inode和数据块数量不匹配的情况。当文件系统中有大量小文件,每个文件都需要一个 inode,但它们使用的数据块很少时,inode 耗尽而数据块未耗尽;当文件系统中有少量大文件,每个文件占用大量数据块,但只需要一个 inode时,就会出现inode 未耗尽而数据块耗尽。
2.2.3 文件操作(增删查改)
基于这样的这样的文件系统,我们通过文件操作来系统地理解其工作流程。
已知创建了一个log.txt文件,其i结点值为114514。在创建时,首先操作系统会在确定新创建的文件属于哪一个组,然后查询inode位图和块位图找到一个空闲inode与数据块分配给文件,然后写入属性和内容。最后这个申请到的inode就是i结点位图空闲的下标+分组起始inode值。
对于同一个log.txt文件进行查找,实际上是通过i结点值来查询的。通过inode对比组inode区间找到对应的组,然后减去start inode即可找到文件的inode结点,通过结构体即可访问到内容。
删除则是在查找的基础上将位图对应的位置置为空闲。修改则是在查找的基础上对文件的i结点内容或数据块进行读入内存,然后修改再写回磁盘。
2.2.4 目录文件
对于目录文件,在文件系统看来也和普通文件一样,有着自己的inode结点,也有着自己的数据块。只是目录文件的数据块(内容)是目录内的文件名和inode值的映射关系。
于是我们理解为什么说访问文件使用的是inode值而非文件名,因为在使用文件名访问文件时,首先打开文件所在目录的,从其中获取文件名对应的inode值,从而才能查找到文件并打开。于是目录的r权限会限制能否获取目录内容——目录下文件的文件名与inode值映射,从而限制能否访问目录下的文件;w权限会限制能否修改目录内容,从而限制能否创建、修改、删除目录下的文件。
2.2.4.1 路径解析
目录也是文件,访问当前目录也需要他的inode值,因此需要打开它所在的目录内容来进行映射,而访问上级目录又需要上上级目录的帮助。于是不论打开什么文件,最后一定会递归到要打开根目录来进行文件名与inode映射,于是操作系统将根目录的固定加载到了内存中,以便从根目录开始解析。
于是我们知道,打开路径为 "/home/xlz/tmp/log.txt" 这样的一个文件,首先会从根目录开始,打开根目录,将根目录内容中的home文件名映射得到一个inode,找到这个inode的inode结点从而找到数据块,取得其内容后就可继续拿出其中xlz文件名映射的inode。同样打开了xlz的内容后,映射得到tmp的inode,再由tmp的内容映射得到log.txt的inode,从而找到了这个文件。
因此打开文件需要拿到其路径,而进程打开文件,绝对路径不必多说。相对路径由于进程CWD的存在,取得文件的路径也很容易。接着层层解析自然就可以打开对应的文件。
2.2.4.2 路径缓存
如果对于所有文件的访问都要从根目录解析一遍,那效率未免也太低了,于是解决这种问题最经典的方案就是缓存技术。而路径文件这种结构是一种天生的树形结构,所以缓存结构采用树状结构自然是最优选择。
在Linux中,存在名为dentry的结构体,它就是文件作为树状缓存结构的结点,其中包含了文件的inode、父子结点、LRU队列等信息。
所有的文件都会存在自己的dentry结构体,树的结点从根目录开始,当需要打开某个文件时,就会根据路径提供的顺序查找这棵树,查找到了就可以返回文件inode从而得到文件内容。如果没有找到,则会逐步进行路径解析,并在树上增加dentry结点,缓存新的路径。
需要注意的是这棵缓存树是内核数据结构,仅仅存在于内存当中,来方便内存进行文件访问。整棵树形节点也被LRU结构管理,淘汰last recently used的结点。
2.3 分区挂载
①制作磁盘块
dd if=/dev/zero of=./disk.img bs=1M count=5
dd——dd是一个 Unix/Linux 系统上的工具,用于低级数据拷贝和转换,能够在设备之间或文件之间以块为单位复制数据,并支持数据的格式化、转换等操作。它常用于创建磁盘镜像、写入磁盘、备份等任务。
if=/dev/zero——if 表示输入文件(Input File)。/dev/zero 是一个特殊的设备文件,表示无限的空数据流(每个字节为 0)。这里使用 /dev/zero 作为数据源,也就是生成连续的空字节。
of=./disk.img——of 表示输出文件(Output File)。./disk.img 是输出文件的路径,即在当前目录下创建一个名为 disk.img 的文件。
bs=1M——bs 表示块大小(Block Size)。1M 表示每个块的大小为 1 MB(1,048,576 字节)。这是拷贝的基本单位。
count=5——count 指定拷贝多少个块。5 表示拷贝 5 个块。
dd是在按块进行数据拷贝。在这条指令后dd 会从 /dev/zero 中读取 5 个块的数据(每块大小为 1 MB),然后将这些数据写入 disk.img 文件,最终在当前目录下生成一个大小为 5 MB 的文件 disk.img。
②格式化文件系统
mkfs.ext4 disk.img
mkfs.ext4——mkfs 是 "make filesystem" 的缩写,用来创建文件系统。ext4 是常用的 Linux 文件系统类型。
disk.img——disk.img 是创建 ext4 文件系统的目标文件。它可以是一个文件,表示一个虚拟磁盘映像。
mkfs将文件格式化为文件系统。在这步之后,该文件将会被格式化为 ext4 文件系统的结构,并可以像一个实际的磁盘分区一样使用。
③查看分区
df -h
df—— "disk free"(磁盘空间)的缩写。它用于显示文件系统的磁盘空间使用情况。
-h选项——表示 human-readable(易读的格式)。它会以适合人类阅读的格式显示磁盘空间,通常以 KB、MB、GB 等单位而不是字节(B)显示。
④分区挂载
sudo mount -t ext4 ./disk.img /mnt/test/
将磁盘映像文件 disk.img 挂载到 Linux 系统中的一个目录 /mnt/test/,并指定该磁盘映像使用 ext4 文件系统。
在类 Unix 操作系统(如 Linux)中,挂载(mount)是将一个存储设备或文件系统(如磁盘、分区、CD-ROM、USB 设备等)与系统的目录结构连接起来的过程。挂载之后,操作系统就能把存储设备上的文件和目录呈现给用户,并允许用户像操作本地文件一样操作它们。挂载的目的是让操作系统能够访问存储设备上的数据,把它“挂”到文件系统的树状结构中,使用户可以通过目录路径访问这些数据。
对于真正的存储设备而言,挂载就是将其接入系统,使得操作系统可以访问存储设备的数据从而让用户也可以进行访问。
而对于我们这里的磁盘映像文件,也就是虚拟磁盘,有一些容易混淆的地方。可以类比,磁盘映像文件和u盘一样,一旦生成大小就是固定的,其中的空间只有使用和未使用两种情况。通过dd if=/dev/zero of=./disk.img bs=1M count=5 生成的磁盘映像文件实际上是将实际的磁盘中拿出5M的空间分给了磁盘映像文件,而磁盘映像文件作为一个磁盘其大小就是5M,这时把它挂载后进行文件创建等操作,在真实磁盘视角是对这个5M映像文件的内容做修改,而在虚拟磁盘的视角是在自己5M大小的磁盘空间内创建文件。简言之就是拿出了5M的空间给虚拟磁盘,而这个虚拟磁盘在实际磁盘中的表现就是映像文件。
挂载点则是一个特定的目录,操作系统通过该目录将文件系统中的文件和目录呈现给用户和应用程序。例如,/(根目录)是文件系统的起始挂载点,其他磁盘分区、文件系统或设备可以挂载到 /data、/mnt、/home 等目录上。
其中/dev/loop0 是 Linux 系统中的一个环回设备(loop device)。环回设备允许你将一个普通的文件(如磁盘映像文件)映射到一个虚拟的块设备,从而使操作系统能够像处理物理磁盘一样处理这个文件。
因为磁盘映像文件被看作了环回设备loop0,所以一个文件构成的虚拟磁盘也可以对文件进行挂载、读取、写入、分区等操作,就好像它是一个物理设备。通常情况下,环回设备用于挂载磁盘映像文件,或者以类似磁盘的方式访问存储在普通文件中的数据。
⑤分区卸载
sudo umount /mnt/test
用于卸载已挂载的文件系统,将之前挂载到 /mnt/test 目录的文件系统卸载掉。
3. 软硬链接
在Linux中,**软链接**(Symbolic Link)和**硬链接**(Hard Link)都是用来为文件创建别名的方式,但它们在实现和使用上有所不同。
3.1 软链接(Symbolic Link 或 Symlink)
软链接,也叫符号链接,是一个指向目标文件路径的特殊文件,可以类比为Windows中的快捷方式。软链接文件中保存的是目标文件的路径,而不是目标文件的内容,因此它是一个独立的文件,拥有自己独立的inode。
创建方法【后者链接前者】
ln -s <原文件或目录> <软链接>
注意点
①通过软链接查找来打开目标文件的过程包括:通过软链接文件的inode找到其物理块,其中存储着目标文件的路径信息;内存得到这个路径信息后,进行路径解析,一步步找到目标文件并打开。因此软链接不局限于当前文件系统,而可以跨文件系统。
②软链接不仅可以指向文件,还可以指向目录。
③软连接文件文件在Linux中是独立的,有一个自己唯一的inode,包含文件的元数据(如文件大小、权限等)。
④删除软链接不会影响原文件。如果目标文件被删除,软链接就会变成悬挂链接(即无法访问的链接)。
3.2 硬链接(Hard Link)
硬链接是文件系统中对文件的直接引用。硬链接创建后,多个文件名指向相同的磁盘块(即相同的数据)。每个硬链接都有相同的inode编号,它们指向相同的数据块,因此删除其中一个硬链接并不会影响其他硬链接,只有当所有硬链接都被删除时,磁盘上的数据才会被回收。
创建方法
ln <原文件> <硬链接>
因为通过文件名来打开文件,实际上是读取了所在目录的文件内容,将文件名映射为了inode值,所以硬链接的本质就是创建了一个映射到同一个inode的新文件名,此时一个inode有多个文件名映射了。
通过观察发现软链接的文件间inode不同,而硬链接的文件具有相同的inode。
另外可以发现在我们熟知的文件信息中有一个数字(红色标出),这个实际上是硬链接数(inode的引用计数),表示当前文件有多少个硬链接(inode有多少个映射关系)。如我们刚才创建的text.txt的硬链接,二者的引用计数都是2。
又可以发现dir1的引用计数也是2,这是因为目录文件中默认包含.和..两个目录,这个其中.就是指向dir1自身的一个硬链接。
我们发现当在dir1中再创建一个目录dir2,会发现引用计数又变为了3,这是因为dir2中的..是dir1目录的硬链接。所以目录文件固有的.和..实际上就是硬链接。
可以看到根目录有17个硬链接,所以我们知道根目录下有16个文件(17-1个根目录下的.目录文件),其余的目录都会贡献一个..硬链接。值得一提的是根目录的..也是根目录自身,但是操作系统为了使所有文件保持一致就没有将..加入引用计数。
注意点
①通过硬链接查找来打开目标文件和正常文件一致,只需要根据自己的文件名映射到inode访问数据块即可。所以硬链接不能跨文件系统,因为硬链接文件inode一致,不同文件系统下的inode各自为一套。
②无法创建目录的硬链接,这是出于避免循环结构的考虑。但是.和..就是目录的硬链接,这是操作系统做的例外操作,用户是不可以创建目录的硬链接的。
③删除一个硬链接不会影响文件内容,只有所有链接删除时,文件才会被删除。于是我们可以在剪切大型文件的时候,直接使用硬链接的方式,避免拷贝开销。同时也可以采取硬链接的方式来备份文件。