尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

【Linux系统】理解文件系统以及软硬连接

【Linux系统】理解文件系统以及软硬连接 目录一、认识硬件 - 磁盘1. 磁盘的物理构成2. 磁盘的存储构成3. 磁盘的逻辑结构二、文件系统1. Linux ext2文件系统2. 磁盘格式化介绍3. 理解文件操作三、理解软硬链接1. 软链接2. 硬链接3. 总结四、文件系统的底层逻辑补充磁盘和内存交互的基本单位操作系统如何管理内存内核级缓冲区文件页缓冲区一、认识硬件 - 磁盘磁盘是一种永久性存储介质它是我们电脑中唯一一个机械设备也是一个外设(输出/输入)。同时在我们电脑中还有一个叫内存的东西它则是掉电易失性存储介质。1. 磁盘的物理构成磁盘张什么样如图所示为磁盘的正面和背面样子其中在正面我们对其中结构的称呼如下需要注意磁头和盘面是不接触的盘面有多个以主轴为中心重叠放置一个盘面上只有一个磁头盘片是会以主轴为中心高速旋转的。2. 磁盘的存储构成磁道与扇区在上述图中每一个盘片表面都被划分为许多同心圆轨迹每一个同心圆叫做磁道。每一个磁道又被划分为若干个弧形区域这样的区域叫做扇区。扇区是磁盘上进行数据读写的最基本单元传统标准为512字节现代高级格式化硬盘通常为4KB。所以我们可以将磁盘看做是有无数个扇区构成的存储介质。要将对磁盘进行数据读写首先需确定数据的位置可以分为以下三个步骤确定是哪一个盘面定位使用的是哪一个磁头然后确定是哪一个磁道最后确定是哪一个扇区这种寻址的方法叫做CHS寻址方式。然后向该位置进行读写读写的操作就是操作系统向磁盘读写数据时实际上是控制磁头在高速旋转的盘片上方通过电路控制改变或感应盘片表面的磁极性来实现二进制数据的写入与读取并且所有的读写操作都是严格按照扇区为单位进行的。3. 磁盘的逻辑结构我们可以将磁盘想象成一个线性的存储结构就像磁带一样当卷起来的时候就是圆的但是当将磁带拉直就是线性的。而磁盘是有一个个盘面组成而每一个盘面有是有很多磁道组成二磁道又是由许多扇区组成。所以磁盘就是有很多个扇区组成。所以对于一整个磁盘我们可以将其想象成一条直线整个有由多个盘面组成每个盘面又由很多磁道组成每个磁道由由很多扇区组成。如图所示如果我们将这个线性结构看做一个数组则每一个扇区都有一个下标而这种下标我们逻辑扇区地址也叫LBA地址。通过 LBA我们可以计算出数据在磁盘物理结构上的具体位置即物理地址由磁头号/盘面号、柱面号/磁道号、扇区号组成简称 CHS。。示例假如每个盘面有2000个扇区每个磁道有50个扇区则下标为2888的扇区就位于第2个磁头(盘面)因为2888 / 2000 1, 2888 % 2000 888第18个磁道因为888 / 50 17, 888 % 50 38第38号扇区。由此我们就完成了从逻辑地址LBA到物理地址CHS的转化。二、文件系统1. Linux ext2文件系统在Linux ext2文件系统中为了更好的管理磁盘会先对磁盘进行分区管理每个分区就可以管理好整个磁盘比如我们电脑上的C盘和D盘其实就是对磁盘的分区。在此基础上通过格式化操作ext2文件系统会进一步对分区进行分组每个分区划分为若干个“块组Block Group”。具体的如图所示其中的 Boot Block 是启动块位于磁盘的最开始通常是前 1024 字节。它不属于 Ext2 文件系统本身而是用于存放操作系统的引导加载程序。当电脑开机时就会先读取这里来启动系统。随后的Block Group 0 ... Block Group n块组 0 到 n表示的就是一个个的分组即分区分成的很多个大小相等的块组。而对于每一个组块又有以下细分如图所示我们知道文件文件内容文件属性但其实在磁盘中内容和属性是分开存储的。为什么怎么说呢在理解了这个分组中的各个区域代表的意思就知道了。Data Blocks 数据块存文件内容的区域以块的形式呈现。每个块的常见大小为 4KB 相当于8个扇区如果每个扇区是512字节的话并且这个块表示的也是文件系统的块。Inode Table (Inode 表) 这是存放文件属性的地方但不包含文件名。一个 inode 表由许多固定大小的inode 结构体组成每个文件都对应表中的一个 inode每一个inode都有唯一的一个编号inode编号。所以在Linux系统中标识一个文件使用的就是inode编号。通过 ls 的 -i 选项就可以查看到文件对应的 inode 编号所以说内容和属性是分开存储的。那 inode 和 文件内容 又是怎么关联的呢Data Blocks数据块是由许许多多个块例如 4KB 大小组成的线性空间每个块都有自己的编号你可以把它想象成数组的下标。而 Inode Table 中的每个 inode 结构体里不仅存储的一个inode的编号还存储了以下关键信息文件大小。文件所有者.文件权限rwx。时间戳创建、修改、访问时间。数据块索引数组。......数据块索引数组这个数组中存储的是在 Data Blocks 中存储文件内容的块编号。通常这个数组中里有 12-15 个数据个数这其中也分为了直接和间接的索引。如果文件很小则前 12 个数组内容存储的编号直接指向 Data Block 中存放内容块的编号。系统直接去读这些块即可如果文件较大超出了直接指针的范围系统会使用第 13 个索引。这个指针指向的一个数据块不再存储文件内容而是存储了一个“块编号列表”。通过这个列表再去找存内容的数据块。如果文件更大则还会使用到二级第14个下标或三级索引第15个下标它们执行的数据块中存储的还是块编号列表具体内核的实现如下图所示这样我们就可以通过inode结构体中的这个数据块索引数组找到Data Blocks中存储内容的块从而实现一个inode对应一个文件内容的关联。注意这里的inode是文件系统中的inode不是Linux操作系统源代码VFS虚拟文件系统层中的inode 。Inode Bitmap (Inode 位图)表示当前块组中 Inode 表的占用情况。位图中的每一位对应一个 inode 编号如果该位为 1表示对应的 inode 已经被使用如果为 0表示该 inode 空闲可供分配。Block Bitmap (数据块位图)表示当前块组中数据块的占用情况。位图中的每一位对应一个数据块编号如果该位为 1表示对应的数据块已经被使用如果为 0表示该数据块空闲可以用于存储新数据。Group Descriptor Table (块组描述符表)表示当前这个分组的使用情况。它是一个由多个“块组描述符”组成的数组每个描述符对应一个块组记录了每个块组内部的使用情况信息如inode位图的使用情况等。Super Block (超级块)表示文件系统整个分区的基本信息例如总块数、总 inode 数、块组总数、每个块组的大小、每个块组的 inode 数和块数、文件系统状态等。它是文件系统挂载时首先读取的关键结构注意Super Block (超级块) 并不是每个分组都有的。虽然标准的 Super Block 只存在于第 0 号块组开头但为了防止损坏文件系统通常会在后续的特定块组如第 1, 3, 5, 7... 号块组中保留超级块的备份。当主超级块损坏时可以使用这些备份进行修复。识别文件类型就需要用到一个魔术数的概念它是超级块中的一个固定字段文件系统工具通过读取超级块的魔术数识别文件系统类型inode 总数是整个文件系统分区的属性属于分区级别的所有块组的 inode 数之和等于超级块中的总inode数分配是是以分区为单位的。2. 磁盘格式化介绍以上是我们ext文件系统的整个结构在进行分区之后但是这些复杂的结构并不会自动出现在新硬盘上每一个分区在被使用之前都必须提前将部分文件系统的属性信息设置进对应的分区中——这个过程就是我们常说的‘格式化’。具体来说格式化就是要将超级块、块组描述符表、Inode位图、数据块位图按照特定的规则提前“写入”到磁盘分区的物理空间中。即超级块要提前写入因为它记录了文件系统的类型如ext2ext4、总块数、空闲块数、Inode总数等最核心的全局信息。没有它系统甚至无法识别这是一个什么类型的文件系统。块组描述符表需要提前写入因为它可以告诉系统每个块组的Inode表在哪里、数据块在哪里、还有多少剩余空间等。初始化位图创建或重置 blocks bitmap 和inode bitmap也就是将它们全部初始化为“未使用”状态而Inode表和数据块虽然不需要像超级块那样写入具体的配置参数但必须在磁盘上预留出连续的空间位置。对于Inode表格式化程序会根据分区大小计算出需要多少个Inode并在磁盘上划出一块区域专门存放它们。对于数据块这是真正用来存储文件内容的地方。格式化会将剩余的绝大部分空间切割成一个个固定大小如4KB的块。虽然此刻里面没有数据但必须通过前面的“位图”将它们标记为全0即全部空闲以便后续随时可以被分配使用。总之格式化就是对分区写入管理信息然后每个分区从一个“存储容器”变成了一个可被管理的“文件系统”。3. 理解文件操作那么基于上述逻辑我们可以深入理解 Linux 下文件操作的本质。当我们执行新建、删除、查找或修改文件时系统底层究竟在做什么Linux系统中一个文件一个inode。每一个inode都有自己的inode编号(inode的设置是以分区为单位的不能跨分区)inode 中包含了文件的所有属性既然 Inode 里不存文件名那系统怎么知道一个文件名对应哪个 Inode 呢答案就在目录里。为什么需要“目录”首先来看看目录目录页是文件所以目录页有属性和内容也有自己的inode也有存放数据的 Data Block。那么目录里面存的是什么呢?普通文件的 Data Block 存的是文本或二进制数据而目录的 Data Block 存的是该目录下所有文件的 文件名 与其对应的 Inode 编号 的映射关系。结论目录的作用就是维护一份“文件名 - Inode”的映射关系列表。拓展目录的inode又是怎么知道的呢目录是文件所以目录的inode是在它的上级目录的 Data Block 中存着那么上级目录又是怎么找到的呢这有要看上上级目录了如此重复直到根目录而根目录没有父目录它依赖于文件系统的预定义规则所以根目录的 Inode 编号是固定不变的一般inode是2。所以任意找一个文件都必须带路径通过路径因为Linux系统是一颗树所以就要从根目录/出发向下找到当前目录的inode从而找到目录内容从而找到要找的文件的 inode再重复这个过程直到找目标文件的inode。这就是Linux系统查找文件的过程是一个单向层层遍历的过程。然而如果每次访问文件都要进行如此繁琐的磁盘 I/O 操作系统性能将极其低下。为了解决这个问题Linux操作系统会将最近访问过的目录项即“文件名”到“Inode”的映射关系保存在内存中这种机制叫做Dentry 缓存。新建一个文件系统要做什么系统先检查当前目录的 Data Block确认没有重名文件因为同一目录下不能有重复的文件名映射。然后通过目录定位到当前文件所属的分区并准备在该分区内分配 inode。。再在这个分区中查Group Descriptor Table找到有空闲的分组。最后在Inode Bitmap中找空闲的inode进行分配并写入文件属性。如果要对文件进行写入则就是在找到inode的基础上通过数据库数组关联到内容然后对Data Blocks进行写入。删除一个文件系统要做什么系统先通过路径找到目标文件的 Inode。将Inode Bitmap中对应的inode编号置0将inode结构体中数据块数组中的编号在Block Bitmap中置0。因此删除文件时只是将Inode Bitmap和Block Bitmap中的数据置为无效而已。但是像拷贝文件这类写入操作不仅要修改Inode Bitmap和Block Bitmap还要修改Data Blocks区域。这也是为什么删除文件一般都比写入文件如拷贝等操作快的原因。与之对应恢复文件也就是将这些位图Bitmap中的数据重新置为有效置1这也是为什么我们在误删文件后第一原则是立即停止对该磁盘的任何写入操作防止原始数据被新数据覆盖导致永久无法恢复。查找一个文件系统要做什么通过路径找到目标文件的 Inode。判断Inode Bitmap中对应的inode编号置0是否有效。有效则查找成功否则则失败。成功之后通过inode中的数据块数组找到对应的内容块再将它们加载到内存就拿到了文件内容了。修改一个文件系统要做什么先找到inode就拿到了对应文件的所有属性以及数据块数组关联了文件内容修改内容则就是在数据库数组中进行增、删操作...修改属性就是对inode属性进行写入删除操作...三、理解软硬链接1. 软链接建立软链接对一个文件建立软链接的指令ln -s 原文件名 软链接的文件名如果要对文件 file.txt 建立软链接则具体过程如图所示并且从图中我们可以看出软链接文件和原文件的inode是不一样的软链接文件的大小也比原文件要小。以上便是软链接的方法和所看到的现象。理解软链接软链接也被称为符号链接。软链接是一个完全独立的文件。它拥有自己独立的 Inode 编号它拥有自己独立的数据块Data Block它的数据块里只存了一行文本字符串也就是指向目标文件的路径例如 /home/user/original.txt 可以把软链接理解为Windows 系统中的“快捷方式”。当你访问软链接时操作系统读取它的内容然后跳转到那个路径去找真正的文件。如图所示但是如果源文件被删除了软链接就变成了“死链接”因为它指向的路径不再有效。如图要删除软链接有两种方法rm 软链接文件名unlink 软链接文件名注意不要使用 rm -rf 软链接文件名如果软链接指向目录-r 参数会递归删除目录内容导致误删源文件应用场景正是因为软链接可以当做快捷方式的作用而在系统 /lib64/ 目录下存在大量软链接这主要是为了实现共享库的版本管理与向后兼容。 它允许程序通过一个通用的名称如 libssl.so来调用库而实际上指向的是具体的版本文件如 libssl.so.1.1。这样既保证了旧程序能找到依赖又方便系统在升级库文件时只需更新链接而无需重新编译所有软件。如下就是Linux中/lib64/目录下的文件其中比如 ld-linux-x86-64.so.2 就是一个软链接指向的是 ld-2.17.so 。2. 硬链接建立硬链接对一个文件建立硬链接的指令ln 原文件名 硬链接文件名这里和软链接不一样因为硬链接不用加 -s 选项。使用示例对文件 test2.txt 建立一个硬链接过程如图所示从图中我们可以得到以下现象软链接文件和原文件的inode是一样的软链接文件和原文件的硬链接数从1变成了2。文件属性中的硬链接数在一个存在文件的目录中通过ls -l 可以看到以下信息如图所示其中蓝色的框框中的数字就是硬链接数它本质是一个计数器是在inode结构体中的一个属性表示有多少个文件名指向同一个inode编号。因为在当前目录中保存了文件名和文件的inode编号的映射关系虽然 inode 编号唯一但是也会存在多个不同的文件名对应同一个inode的编号的情况也就是多个文件名映射一个inode编号所以这些文件本质上都是同一个文件只是文件名不同而已。所以当我们执行rm 文件名删除一个文件时实际上只是将其 inode 中对应的硬链接数减一直到这个数减到 0 时才会正真执行删除操作其实也就是对inode位图和block位图置零。结论硬链接不是一个独立的文件因为它没有独立的 Inode。所谓的“建立硬链接”本质上并不是创建了一个新文件而是在特定目录的数据块中新增了一条“文件名 : Inode 编号”的映射关系。那么当删除一个文件时硬链接的文件都不会受影响只是这里的映射关系少了一条而已。新创建的普通文件硬链接数为什么是1新创建的目录为什么硬链接数为2新创建一个普通文件因为此时该文件只有当前这一个文件名所以引用计数就是1所以硬链接数就是 1。但是对于新创建的目录我们会看到它的硬链接数为2如图所示但是我们如果通过ls -lai 查看这个目录的inode编号以及这个目录中的一个隐藏文件 . 的编号就会发现新创建的 dir 目录和它里面的一个隐藏文件.的inode是一样的。而IG木箱下面的这个两个隐藏文件./..也是硬链接也就是说对应图中1188347这个inode编号是有两个文件名指向它的所以这里目录的硬链接数为2。补充如果我们要求一个目录下相邻子目录的个数则可以使用当前目录的硬链接数减2,得出结果。因为每个子目录中有一个..的隐藏文件对应的就是当前目录。但是Linux系统不允许手动对目录建立硬链接.和..只是系统在设定时建立的属于特例在Linux中不允许手动对目录建立硬链接如图所示因为如果建立了的话会引发系统的一个环路问题在 Linux 中文件系统是一棵以根目录 / 为起点的倒置树。当我们访问一个文件例如 /home/user/test.txt时内核的查找逻辑是单向且分层的首先会从根目录出发找到根目录 / 的 inode通常是 2 号然后遍历根目录文件的内容找到第二个目录 home再在这个目录文件的内容中遍历去找 user 这个目录最后再在 user 这个目录中去找这个文件。所以如果我们在某一个目录下建立了一个硬链接比如说是指向 根目录 的硬链接那么当遍历当这个硬链接所在的目录时找到这个硬链接时则就又会回到根目录由此出现不停的寻找的情况也就是死循环即出现环路问题。虽然用户不能创建但系统内核在创建目录时会自动生成两个特殊的硬链接文件.(当前目录)指向自己。..(父目录)指向上一级目录。这两个文件由内核严格控制维护目的是为了确保目录树结构的完整性而不会引发上述的死循环问题。3. 总结软链接也被称为符号链接。软链接是一个完全独立的文件。它拥有自己独立的 Inode 编号它拥有自己独立的数据块Data Block它的数据块里只存了一行文本字符串也就是指向目标文件的路径例如 /home/user/original.txt 。相当于快捷方式。硬链接不是一个独立的文件因为它没有独立的 Inode。所谓的“建立硬链接”本质上并不是创建了一个新文件而是在特定目录的数据块中新增了一条“文件名 : Inode 编号”的映射关系。四、文件系统的底层逻辑补充磁盘和内存交互的基本单位在计算机操作系统中内存和磁盘是两种速度差异巨大的存储介质。为了协调这两者之间的工作操作系统引入了一种核心的数据交换机制。这种交互并非以单个字节或任意大小的数据块进行而是基于一个固定的基本单位大小为一般为4KB。物理内存会被操作系统划分成一个个大小相等、连续的块每一个块的大小为4KB。根据上面关于文件系统的认识我们也知道磁盘上的文件的内容是按照一个个数据块存储的大小也是为4KB。当磁盘和内存进行文件级交互时是以4KB为单位进行交互的物理内存中的4KB叫做页框而磁盘上用于与内存进行交换的数据单位叫做页帧大小也为4KB。如图所示注意因为是以4KB的块位单位来交互数据的所以即使要访问的是1KB也需要将这4Kb全部加载又比如如果修改了磁盘中一个文件的某1KB的数据则它所在的整个4KB的空间都要重新写入。如果不想要以4KB的大小来交互数据则就需要去修改操作系统源代码并重新编译。但是最常用的还是4KB。为什么一定是4KB整块交互呢这其实是在计算机发展史中经过验证的。对于太小的情况原因有两点在硬件IO上如果每次只读取几个字节频繁启动磁头寻道或发送指令效率极低。通过一次性读取或写入一个较大的固定块4KB可以显著减少访问外设磁盘的次数基于计算机中的局部性原理即程序运行时往往倾向于访问最近访问过的数据或附近地址的数据所以操作系统在读取数据时不会只读取请求的那一点点而是会将包含该数据的整个 4KB 页框都加载到内存中这就也是程序的预加载机制。对于太大时比如 1MB哪怕程序只用了 1KB 数据也要占用整个块造成严重的内部碎片。具体如何交互是有操作系统管理的我们不关心。操作系统如何管理内存之前虚拟地址空间时我们知道它是操作系统提供的我们在应用层看到的是虚拟地址就是因为操作系统在虚拟地址空间做了很多事所以滁州学院一定是要看到物理地址的。现在我们已经知道物理内存就是一个个连续的4KB大小的块组成的但是这样的块有很多那么操作系统是如何知道哪些被使用了哪些没有使用哪些内存被使用了多少哪些内存是被锁定的.......这样的一系列问题所以操作系统是怎么管理内存的答案就是先描述再组织。所以操作系统首先需要定义一个数据结构来描述每一个4KB页框的属性。在 Linux 内核等系统中这个结构体通常被称为struct page即struct page { // page页必要的属性信息 // 例如该页是否被占用属于哪个进程是否在磁盘上有备份引用计数是多少 };在源码中如图其中flags表示了当前页框当前的使用状态有没有被使用等而_count则是一个引用计数表示当前有多少个内核组件正在使用它这也关于发生写时拷贝原理中的关键点。有了描述单个页框的结构体后操作系统采用了一种非常直接且高效的方式来组织所有物理页框数组。如果假设系统有 4GB 的物理内存每页大小为 4KB字节。 总页数 页。那么操作系统会定义一个巨大的全局数组struct page mem_array[1,048,576]; // 假如数组名为 mem_array而数组的下标就对应了物理内存的第几个4KB的页框所以每一个页框就有了对应页号的概念这里的下标就是对应页框的页号。因为每一个页狂的大小是固定的4KB即所以物理地址的低 12 位代表页内偏移而高位部分则直接代表页号Page Number也就是数组的下标。所以我们如果访问一个物理地址如 0x11 22 33 44只需要将它通过位运算比如右移 12 位或者是 0xFF FF F0 00就可以找到对应的页框了。所以对内存的管理就变成了对数组的管理。因此当我们需要操作某一块物理内存时只需在数组中找到对应的 page 结构体就能定位到它在物理内存中的具体位置物理页框进而读写其中的数据。所以所有针对物理内存的生命周期管理操作如申请和释放本质上都是在操作这个内存 page 数组。内核级缓冲区文件页缓冲区当我们计算机开机时操作系统会预先把文件系统相关的信息比如Super BlockGroup Descriptor Table Block BitmapInodeBitmap等加载到内存了。当我们通过一个进程打开一个文件对其进行读写时。首先打开了这个文件在操作系统中就有了一个 struct file 结构体进程task_struct可以通过文件描述符fd找到它。此时文件的属性和内容呢怎么找到呢其实在struct file中只保存了少量的属性的数据比如文件描述符这个的属性都没有在这个结构体中正真的文件属性是在操作系统创建的一个struct inode 结构体的。所以当我们打开一个文件时操作系统会根据这个文件名在目录中找到这个文件的inode编号然后确认磁盘中的inode bitmap 中中文件是否存在如果存在则就将磁盘中indoe table中这个文件 inode 对应的属性加载到操作系统内核中struct inode 结构体中。最后通过struct file 我们就能够找到这个 struct inode从而找到文件属性。比如在Linux2.6的一个源代码不同版本的代码可能不一样中的定义如图所示那么文件内容呢在Linux操作系统的源代码中的 struct file 中有一个struct address_space *f_mapping;这个结构体中有一个struct radix_tree_root page_tree;这个page_tree本质上是一个基树/字典树这棵树的叶子节点存着的是一个个struct page 数组。如图所示当通过一个进程对一个文件进行读写时在内核中保存文件内容的就是保存在这一个个的struct page对象中的。这就是内核中的文件的页缓冲区也就是操作系统内核级缓冲区即文件缓冲区。最后这些page对象中数据都会写入到内存中再与磁盘进行交互最后这一步是操作系统自己完成的。总结一下文件访问的全过程当打开一个文件时会预先将这个文件在磁盘中的inode属性加载到内存然后当在写数据到文件中时首先会将数据写入磁盘的文件中时首先会将内容加载到用户级缓冲区(C语言自己提供的FILE结构体中维护的缓冲区)让后再通过系统调用比如write就会将数据从用户级缓冲区写入到内核级缓冲区也就是这里的页缓冲区即这里叶子节点中的一个个struct page对象中最后才会将数据通过4KB的数据交互从物理内存写入到磁盘中。总结一下就是例如图这样的结构小知识当数据写入磁盘会经历3次拷贝用户缓冲区拷贝到内核缓冲区内核缓冲区拷贝到物理内存物理内存拷贝到磁盘。感谢各位观看希望能多多支持
返回列表