
前言文件分为内存文件和磁盘文件内存文件相关知识前面已经介绍过了接下来谈谈磁盘文件这是一个特殊的存在因为它不属于冯诺依曼体系而是位于专门的存储设备中因此磁盘文件存在的意义是将文件更好的存储起来以便后续对文件进行访问。在高效存储 磁盘文件 这件事上前辈们研究出了十分巧妙的管理手段及操作方法而这些手段和方法共同构成了我们今天所谈的文件系统在这里插入图片描述注 本文主要介绍对象为机械硬盘存储模式及EXT 文件系统️正文1、磁盘文件在计算机中没有被打开的文件都是静静的躺在外存磁盘中当需要对文件进行操作时会通过inode对文件进行访问通过以下指令查看当前目录中文件的详细信息及inode值代码语言javascriptAI代码解释ll -i在这里插入图片描述这个就是inode的值如同pid与进程的唯一对应性一样inode与文件也是唯一对应的未被硬链接的情况下可以通过inode访问文件在磁盘中的详细信息磁盘文件是如何进行管理的磁盘文件的管理类似于菜鸟驿站其中的包裹就像待访问的文件而inode就是取件码得益于这种规范化的存储模式我们可以做到对文件的快速定位、快速读取和快速写入2、磁盘概念现在市面上的磁盘主要分为机械硬盘和固态硬盘前者读取速度慢但便宜、稳定后者读取速度快但价格高昂且数据易损两者各有其应用场景本文主要介绍的是机械硬盘2.1、基本结构机械硬盘是我们电脑中的唯一一个机械设备并且它还是一个外设根据冯诺依曼体系结构机械硬盘在速度上远远慢于CPU和 内存举例机械硬盘有多慢假设 CPU 运行速度是纳秒级那么内存就是微秒级而机械硬盘只不是是毫秒级为何 机械硬盘 如此慢这与它的结构有很大关系机械硬盘的结构主要包括以下几种盘片一片两面每一面都可以存储数据有一摞盘片磁头一面配备一个磁头专门用于读取盘面中的数据主轴用于控制整块盘的转动音圈马达控制磁头的进退磁头臂链接磁头与音圈马达伺服电路板控制读取数据的流向及各种结构的运行 ……注意 多个盘片、多个磁头都是共进退的机械设备控制是需要时间的因此导致机械硬盘读写数据速度相对于CPU和内存来说比较慢在这里插入图片描述2.2、数据存储总所周知数据是以 0 和 1 的方式进行存储的常见的存储介质有强信号与弱信号、高电平与低电平、波峰与波谷、南极与北极等而盘面上比较适合的是南极与北极当磁头移动到指定位置时向磁盘写入数据N-S删除磁盘中的数据S-N磁盘中读写的本质更改基本元素的南北极、读取南北极注意 磁头并非与盘面进行直接接触而是以15纳米的超低距离进行磁场更改这个距离相当于一架波音747距离地面1米进行超低空飞行所以如果磁头制作工艺不够精湛可能会导致磁头在写入/读取数据时与盘面发生摩擦高速旋转发热从而导致磁场消失该扇区失效数据丢失机械硬盘不能在其运行时随意移动因为角度的偏转也有可能导致发生摩擦造成数据丢失更不能用力拍打机械硬盘在这里插入图片描述在盘面设计上一个盘面被切割若干个扇区单个扇区大小为512 字节或者 4 kb这些扇区用来存储数据同一半径中的所有扇区组成扇面而半径相同的扇区组成磁道柱面我们可以先根据磁头(head)确定盘面再根据半径定位磁道柱面 cylinder最后根据块号确定扇区(sector)这种寻址方法称为 CHS 定位法是机械设备查找具体扇区时的方法在这里插入图片描述文件数据属性在存储时占用一个或多个扇区进行数据存储在这里插入图片描述虽然CHS定位法很妙但它太依赖于具体硬件信息了假设其中的硬件参数有所不同那么 OS 就得使用另一套 CHS 定位法于是为了做到解耦OS 使用的并非 CHS 定位法进行文件定位而是采用LBA 逻辑地址块进行寻址将盘面分割为多个线性分区通过下标 N 计算出 CHS 地址然后进行文件访问将磁道拉长会得到一串线性空间数组其中的每个单位扇区为 512 字节或者 4 kb在这里插入图片描述硬盘的每个分区是被划分为⼀个个的”块”。⼀个”块”的⼤⼩是由格式化的时候确定的并且不可 以更改最常⻅的是4KB即连续⼋个扇区组成⼀个 ”块”。”块”是⽂件存取的最⼩单位。现在 OS 想访问具体的扇区时只需通过起始扇区的地址 偏移量就可以获取LBA 地址然后通过特定手段转为 CHS 地址交给外设进行访问即可LBA和CHS转换因此对于外设中文件的管理经过先描述再组织后变成了对数组的管理这个数据就是task_struct 中的struct block最后我们就能理解为什么 IO 的基本单位是 4 kb 了因为直接读取一个数据块4 kb这样可以提高 IO 效率内存对齐3、磁盘信息一般电脑中会存在多个分区比如C盘、D盘那么为何需要存在这些分区呢3.1、分区意义磁盘空间是巨大的如果不加以划分会导致OS的管理成本提高对应到现实生活中学校需要将不同专业的学生及老师分为不同学院比如管理学院、信息工程学院等分院后的好处不言而喻最重要的是上层管理者更好的调用管理资源这种思想称为分治思想在文件系统中OS 先将整个大文件系统分为不同的区存入struct disk数组中进行管理代码语言javascriptAI代码解释struct disk { struct part[2]; //…… };可以通过 ll /dev/vda* -i 查看当前系统中的分区数及详细信息在这里插入图片描述系统在分区后需要对区块进行格式化在这里插入图片描述不同的文件系统在格式化时写入的数据是不同的这里讨论的是EXT 文件系统磁盘分区后分组、填写系统属性是OS做的事为了使分区能被正常使用需要对分区进行格式化分区格式化OS向分区写入文件系统的管理属性信息在具体分区内还可以细分为块组由块组构成的线性空间亦可称为组线代表一个分区代码语言javascriptAI代码解释struct part { struct part group[100]; //分为100个块组 int lba_start; //起始与结束位置 int lba_end; //…… }将现有资源再分配后可以最大化利用资源避免造成浪费及拖慢效率块组Block Group是本文的重点内容3.2、块组信息块组是由分区细分出的产物它与分区的关系如图所示在这里插入图片描述其中Boot Block 是启动块大小固定为 1 kb在每一条组线前都有此 块组它用来存储分区信息和系统启动属于被保护的内容不允许用户私自修改至于其他块组它们有着统一的格式具体内容如图所示在这里插入图片描述其中一个文件对应一个inode而inode中存储了该文件的所有属性包括所使用的数组块信息因为文件很多所以需要先描述再组织即通过 inode Table 对 inode 进行管理注意inode 属性中并不包含文件名文件名只是给用户用的目录文件也有 inode目录中的数据块保存的是该目录下的 文件名 和 inode 编号对应的映射关系而且在此目录内文件名和 inode 互为 key 值inode 确定分组inode值只在一个分区内有效不能跨分区4、文件相关操作接下来看看文件是如何创建在磁盘中的4.1、文件创建创建一个文件的步骤如下申请一个空闲的 inode将文件信息记录至 inode 属性中寻找空闲的数据块Data block将数据块信息填入 inode 中的磁盘分布区添加文件名至当前目录文件的Data block中同时将文件名和inode之中的属性链接起来注意 每使用一个inode和一个Data block需要把它们对应位图中的信息改为已占用在这里插入图片描述创建文件后生成inode系统标识。4.2、文件访问文件创建后如何根据inode访问文件呢找到文件的inode编号在目录分组中查找通过inode和Data block的映射关系找到文件的数据块并加载至内存中这也就解释了为什么在file对象中会存在inode信息因为它与fd一样重要4.3、对文件进行增删查改文件创建后如何删除删除并不是真删除而是将inode Bitmap和Block Bitmap中位图信息进行修改即可只要访问不到就是删除根据文件名找到 inode 编号再根据inode属性中的映射关系设置Block Bitmap对应的比特位设置为 0 删内容最后根据 inode 编号设置inode Bitmap中对应的比特位为 0 删属性将位图信息置为 0 后创建新文件时系统可以直接使用至于文件的查找与修改通过 inode 修改其内部属性即可注意inode和Data blcok可能存在失衡的情况一直创建空文件导致inode满载而Data block空余很多不断往同一个文件中写入数据导致Data block被占用后续创建文件时inode无法再分配到Data block4.4 dentry树结构上面分析可知linux中并没有文件名的概念而是通过属性加内容形成映射进行查找。也就是说访问文件必须要知道当前文件目录根据文件名获得对应的inode然后进行文件访问。那么就会产生如下问题当前文件目录同样也是文件如果想访问当前文件目录就需要知道存储当前文件目录的上级目录由此产生递归问题递归出口为根目录。因此每一次文件访问都要从根⽬录开始依次打开每⼀个⽬录根据⽬录名依次访问每个⽬录下指定的⽬录直到访问到test.c。这个过程叫做Linux路径解析。路径缓存由于每次打开都要层层访问较为缓慢低效因此OS会进行历史路径缓存。在内核中维护树状路径结构的内核结构体叫做struct dentry代码语言javascriptAI代码解释struct dentry { atomic_t d_count; unsigned int d_flags; /* protected by d_lock */ spinlock_t d_lock; /* per dentry lock */ struct inode *d_inode; /* Where the name belongs to - NULL is * negative */ /* * The next three fields are touched by __d_lookup. Place them here * so they all fit in a cache line. */ struct hlist_node d_hash; /* lookup hash list */ struct dentry *d_parent; /* parent directory */ struct qstr d_name; struct list_head d_lru; /* LRU list */ /* * d_child and d_rcu can share memory */ union { struct list_head d_child; /* child of parent list */ struct rcu_head d_rcu; } d_u; struct list_head d_subdirs; /* our children */ struct list_head d_alias; /* inode alias list */ unsigned long d_time; /* used by d_revalidate */ struct dentry_operations *d_op; struct super_block *d_sb; /* The root of the dentry tree */ void *d_fsdata; /* fs-specific data */ #ifdef CONFIG_PROFILING struct dcookie_struct *d_cookie; /* cookie, if any */ #endif int d_mounted; unsigned char d_iname[DNAME_INLINE_LEN_MIN]; /* small names */ };注意 • 每个⽂件其实都要有对应的dentry结构包括普通⽂件。这样所有被打开的⽂件就可以在内存中 形成整个树形结构 • 整个树形节点也同时会⾪属于LRU(Least Recently Used最近最少使⽤)结构中进⾏节点淘汰 • 整个树形节点也同时会⾪属于Hash⽅便快速查找 • 更重要的是这个树形结构整体构成了Linux的路径缓存结构打开访问任何⽂件都在先在这棵树下根据路径进⾏查找找到就返回属性inode和内容没找到就从磁盘加载路径添加dentry结构缓存新路径4.5 挂载分区上文我们提到inode存在分区属性不可以跨区使用那么问题是我们如何知道自己是在哪一个分区呢分区写⼊⽂件系统⽆法直接使⽤需要和指定的⽬录关联进⾏挂载才能使⽤。所以可以根据访问⽬标⽂件的路径前缀准确判断我在哪⼀个分区4.6 大文件存储单个数据块大小有限(4 kb)如何做到一个数据块存储大量数据答案是套娃Data block中存储其他Data block信息此时称为多级索引可以做到一个数据块中存储大量数据在这里插入图片描述多级索引的基本原理 Linux文件系统中的inode包含15个指针12个直接指针(0-11)直接指向数据块1个一级间接指针(12)指向一个索引块该索引块包含数据块指针1个二级间接指针(13)指向一个索引块该索引块包含一级索引块的指针1个三级间接指针(14)指向一个索引块该索引块包含二级索引块的指针多级索引的容量计算 假设每个指针占用4字节块大小为4KB(4096字节)直接寻址: 12个直接指针 × 4KB 48KB一级间接: 1个索引块可以存储1024个指针(4096/4) → 1024个数据块 × 4KB 4MB二级间接: 1024个一级索引块 × 1024个指针 × 4KB 4GB三级间接: 1024个二级索引块 × 1024个一级索引块 × 1024个指针 × 4KB 4TB这种套娃结构使Linux文件系统能够高效地支持从几KB到数TB的文件同时保持较小文件的访问效率。多级索引的优缺点优点:支持非常大的文件小文件访问效率高(只需要直接指针)灵活适应不同大小的文件缺点:大文件随机访问较慢(可能需要多次磁盘IO来解析索引)索引块占用额外空间