文章目录
持久性
一、介绍
1.架构
- 原则:访问速度越快则越贵,所以应尽可能短,所以尽可能离CPU近一点
- 具体实现:CPU 内存总线,高性能I/O,普通I/O
2.标准
- 标准设备:
- 内部:微处理器CPU,内存,其他硬件特定的芯片
- 接口:寄存器存储状态,命令,数据
- 标准协议:
- 轮询:操作系统反复读取状态寄存器,直到设备进入可以接受命令的就绪状态
- 操作系统下发数据到数据寄存器
- 操作系统将命令写入命令寄存器
- 操作系统轮询设备,判断设备是否执行完成命令
3.中断
- 对于I/O:通过设备的并发提高了利用率
- 对高性能设备:计算非常快的设备,中断会造成额外的开销;可能等待一段时间是好的决定
- 对网络:可能会不断处理中断而无法处理请求
- 中断合并:抛出中断之前会等待一段时间,多个中断合并为一次中断
4.DMA
- 系统的特殊设备,能够协调内存和设备之间的数据传递,不需要CPU介入
- 操作系统通过编程告知DMA引擎数据在内存的位置,要拷贝的大小以及拷贝位置,之后又DMA来解决,解决完了会抛出一个终端
5.设备交互
- 明确I/O指令:将数据写到对应的寄存器
- 内存映射:硬件将设备寄存器作为内存地址提供
6.设备驱动程序
- 应用程序->文件系统->通用块->设备驱动程序
- 上层的部分完全不知道具体的工作方式,他们只会向下层发送读写请求
- 设备驱动程序:作为软件清除的知道设备如何工作(在内核中占据了大部分)
- IDE磁盘驱动程序
- 等待驱动就绪:轮询状态寄存器直到Ready
- 向命令寄存器写入参数:写入扇区数,欲访问扇区上逻辑块地址LBA,将驱动号告知命令寄存器
- 开启I/O:发送读写命令到命令寄存器
- 数据传送:等待驱动状态为READY和DRQ,向数据端口写入数据
- 中断处理:简单情况下一个扇区一次中断,复杂情况下全部完成才有一个中断
- 错误处理:每次操作之后读取寄存器,如果ERROR被置位,那么就可以读取错误寄存器来获取详细信息
二、磁盘驱动器
1.硬件
磁盘:一组扇区
磁盘驱动器:大量扇区(n个),0到n-1是驱动器的地址空间,一次读写512B的一个扇区是原子的
磁道:扇区的同心圆
磁头:连接于磁盘臂,磁盘臂在表面上运动,将磁头锁定到对应的磁道上
2.性能评价指标
- 旋转延迟:在同一磁道上,为了等待对应的磁盘所花费的时间
- 寻道时间:在多个磁道上,移动到目标磁道所需要的时间
3.细节
- 磁道偏移:为了保证顺序读取的高效性,每一个磁道上磁盘的起始点是不一样的。
- 外圈磁道具有更多的扇区
- 磁道缓冲区:磁道读取时,可能会将该磁道上所有的磁盘数据都读到缓冲中
- 后写:写入内存后即回报写入正确 直写:写入磁盘后就回报写入正确
4.磁盘调度
相较于进程的调度,磁盘调度的最大不同:所有磁盘的位置等情况都是已知的
- 最短寻道时间优先SSTF
- 电梯调度:只会沿着一个方向先完成任务,再接收另一个方向的任务(向内,向外)
- 最短定位时间优先SPTF:视情况而定,两个阶段结合那个快就做那个
三、廉价冗余磁盘阵列(RAID)
1.目标
构建一个大型、快速、可靠地存储系统
2.组成
对外表现为一个磁盘:能够读写数据
对内表现为计算机系统:多个磁盘,内存,CPU处理器
3.RAID
- 0级(条带化)
- 数据以块存储在同一个磁盘上,将磁盘组的第一行称为条带
- 块小则文件的并行性增强,但是定位的时间(由最长定位时间决定)会增长
- 容量超大N,可靠性不足0,性能比较好
- 1级(镜像)
- 在不同磁盘上存储相同的镜像,并维持条带化,读随意但是写必须都写
- 容量缩水了N/2,可靠性增强1,性能降低了
- 4级(奇偶校验)
- 通过在一个额外的磁盘上增加一个奇偶校验位,利用公式计算此条带的奇偶校验位
- 小写入问题:写入或者修改时,需要修改奇偶校验位,如果要在两个磁盘上修改,却会收到奇偶校验磁盘的限制,不能实现并行
- 容量略微缩水N-1,可靠性还行,性能不太行
- 5级(旋转奇偶校验)
- 不设定固定的奇偶校验磁盘,将奇偶校验位分布在不同的磁盘,进而能够比较有效地克服小写入问题
- 和4差不多,但是性能稍微好一点儿
四、文件和目录
1.目标
构建持久存储,作为虚拟化的一部分
2.文件
- 表现为线性字节数组,具有低级名称inode号
- 文件的类型名只是文件的惯例,不会强制文件内容中的数据必须是文件的类型名
- 函数
- fd文件描述符=open(文件名,其他标志用于说明文件的操作);打开标准输入,标准输出,标准错误
- read(文件描述符,存放读取结果的缓冲区,缓冲区大小)
- write(标准输入1,内容,大小)
- close(文件描述符)
- fsync(文件描述符),将所有脏数据全部都写入磁盘
- lseek(文件描述符,偏移量,搜索的执行方式) 将指针的位置进行偏移
- rename(旧名,新名)
- stat():获得文件的元数据(文件的大量信息)
- unlink(名称):删除文件
3.目录
- 包含(用户可读名字,低级名字)对的列表,具有低级名字inode
- 函数…
4.文件系统树的连接
- 硬连接:正常的创建了另一种引用同一个文件的方法,创建了一个名字和一个链接(文件名->inode),指向该inode,所以unlink只是解除一个连接,只有一个inode的所有连接都被解除完,文件才会被清除
- 符号链接:创建了一个不同类型的文件,该文件的内容是链接目标文件的路径名,如果目标文件被删,那么符号链接的文件也不再存在路径名
5.创建并挂载文件系统
- 创建:mkfs(地址,文件系统类型),则会在该磁盘分区中创建一个该类的文件系统
- 挂载:通过mount函数,将该文件系统挂载到统一的文件系统树中进行后续的访问
五、文件系统的实现VSFS
1.如何构建
- 数据结构:在磁盘上使用哪类结构来组织数据和元数据
- 访问方法:如何将进程的调用,如open,read,write,映射到结构上
2.文件整体组织
超级块:该特定文件系统的信息,有多少个inode和数据块,inode表的开始位置等
数据位图d:表示后面数据块是空闲还是正在使用
inode位图i:表示inode表是空闲还是正在使用
inode表:存储inode,利用inode号进行指引,其中包含数据的元数据(包括指向磁盘数据块的指针),1个node通常是128B或者256B;考虑到直接指针可能会出现数量不足,所以可以采用多级指针
数据:
3.目录组织
(条目名称,inode号) “.”表示自身目录 “…”表示父目录
文件系统将目录视为特殊的文件
4.获得文件
- 输入文件名
- 层次遍历目录,先找到根目录"/"的inode号
- 读入该块,找到对应的数据指针,指向另一个目录,再次读取下一级的inode号
- 最终找到目标文件的inode号
- 找到对应的inode并读入内存,然后文件系统进行最后的权限检查,在每个进程的打开文件表中,为此进程分配一个文件描述符,返回给用户
- 如果之后输入read指令,那么就会从该数据块的第一位开始读取
5.缓存
- 没有缓存的情况下,读取一次就要查询一次inode号并获得一次数据块,而查询一次路径,可能需要很多级的查询,所以需要利用缓存;
- 动态划分:不确定缓存的大小,如果需要就多分配一点,不需要则少一点
六、局部性和快速文件系统FFS
1.目标
- 解决原系统inode和数据块相距较远,寻道时间较长的问题
- 解决块太小造成的定位时间过长的问题
2.组织结构:柱面组
- 将磁盘划分为一些分组,称为“柱面组”
- 组内结构:超级块副本,数据位图,inode位图,inode表,数据块
- 同一组内的元素访问较快
3.策略:局部性分配文件和目录
-
目录的放置:找到分配数量少的柱面组和大量自由的inode,目录放于该组
-
文件的放置:
-
inode和数据块尽量同组
-
同一目录的所有文件尽可能放在该目录对应的组下
-
-
大文件例外
大文件的存在可能会让一个组直接被填满,那么后续与其相关的文件就放不进去,因此对于大文件,将其拆分成小份放入各个组中
4.其他方法
- 子块:将4K的块分成8个512B的子块,数据先写入子块缓冲,积累后写入4K的块
- 优化磁盘布局:磁盘设计存在一定的间隔(比如跳过一个快),避免出现当前磁盘刚读完,下一个磁盘却划走了的情况;现代通过缓存的设计,能够直接读取对应的数据
七、崩溃一致性:FSCK和日志
1.目标
- 系统在任意两次写入之间崩溃,仅完成部分的更新,崩溃后如何确保文件系统能够再次正确的在系统上挂载。
2.崩溃场景
数据位图,inode表,数据块三者出现了不一致性
数据位图:说明某数据块是否已经被使用,若没被写入,那么就会出现不一致性,出错
inode表:用于指示数据块,若没被写入,那么就找不到数据块
数据块:数据真实被写入,若没被写入,那么就是旧数据
3.文件系统检查程序FSCK
早期的程序,主要是用于确保文件系统元数据一致性,有点慢
- 超级块:健全性检查,是否合理(比如大小是不是超过分配的大小)
- 空闲块:inode,间接块…;如果位图和inode不一致,选择相信inode
- inode状态:inode是否存在损坏问题;如果损坏则清除
- inode链接:扫描整个目录树,验证inode中的连接数是否正确,会修正
- 重复:重复检查指针,如果两个inode指向一个块,则会清除
- 坏块:检查指向超过有限范围的指针,清除指针
- 目录检查:检查目录中每个文件都有inode,整个层次结构中没有目录引用超过一次
4.日志记录
在更新磁盘,覆写结构之前,先写下一点小注记
- 数据日志(中间的三个块称为物理日志):事务开始块(更新的相关信息,以及事务标识符TID),inode表,inode位图,数据块,事务结束块 (结束的标志,包含TID)
- 加检查点:事务安全地位于磁盘上,就可以覆写文件系统中的旧结构
- 整体过程
- 数据写入:将数据写入最终位置,等待完成(能够避免数据两次写入浪费的时间)
- 日志元数据写入:将开始块和元数据写入日志,等待写入完成
- 日志提交:将事务提交块写入日志(包括TxE),等待写完成(前两步顺序不重要,但必须先完成,否则可能会出现TxE已经写入,该日志被认为是正确,然而数据还没写进去)
- 加检查点元数据:将元数据更新的内容写入文件系统的最终位置(表示已经做完了,日志存下来)
- 释放:在日志超级块中将事务标记为空闲
- 恢复
- 崩溃发生于事务被安全写入日志之前,那么就不会再做
- 崩溃发生于事务已提交到日志,但是在加检查点之前,那么就会重做
- 特殊情况
- 日志中的一个数据块被删除,尚未加检查点,然后有被重新写入,此时发生了崩溃,恢复时,把被删除的数据复原了,这不是正确的操作-----------可以永远不复用块;也可以不会重放这类撤销行为
5.其他方法
- 软更新:固定写入的顺序,先写入指向的数据块,再写入指向的inode,即可保证永远不会指向垃圾
- 写时复制:永远不会覆写文件或目录,对以前未使用的数据进行更新,再修改指针
八、日志结构文件系统LFS
1.目标
所有更新缓冲在内存段;段满之后,进行一次顺序写入(核心思想);传输到磁盘的未使用部分;永远不会覆写
- 内存不断增大,写入内存决定了系统性能
- 寻道和旋转成本下降慢,随机读取性能过差
- 现有文件系统在许多工作负载上表现不佳
- 文件系统不支持RAID:小写入问题可能带来的最坏结果使得系统拒绝该操作
2.顺序高效写入
一次写入一个来达到连续写入是不成立的,磁道的转动会造成旋转延迟很长,需要一次写入很多才能达到比较高的效率,所以引入”写入缓冲“来缓冲“一段”
3.查找inode
- imap:将inode号作为输入,返回最新版本的inode磁盘地址,将其放于最新导入段的旁边(不会覆写,只会占用新位置)
- 检查点区域CR:是一个固定位置,包含指向最新inode映射片段的指针
4.垃圾回收
- 版本控制文件系统
- 定期读入许多旧的段
- 确定哪些块在这些段中存在
- 写出一组新的段,只包含还活着的段
- 段摘要块:位于段的最前面,包含inode号(属于哪个文件)和偏移量(是该文件的哪一块)
- 确定块的死活:找到某个块,查看段摘要块找到inode,看inode中的数据指针是否指向该块
- 冷热段清理:尽快清理冷段(内容相对昏定),延迟清理热段(经常被覆盖的段)
5.崩溃恢复和日志
- 将段写入时发生崩溃:不能仅依靠CR(每30s才能写入一次,所以最后的太旧了),使用前滚技术(从最后一个检查点往前重复操作)
- 修改CR时发生崩溃:LFS保留了两个CR,位于磁盘两端,交替写入;先写入前面的,如果写入过程发生崩溃,那么前后不一致,则会修改
九、数据完整性和保护
1.潜在扇区错误(LSE)
- 读取时磁盘返回错误,可能是磁盘被刮擦
- 磁盘内纠错码(ECC)、奇偶校验码、镜像副本
2.块讹误
- 磁盘本身无法检测到,有缺陷的磁盘固件会将块写入错误的位置,数据内容出错
- 检验和:
- 让系统将校验和与数据一起存储,然后访问时将正确数据的检验和与存储的检验和比较
- 布局:
- 将检验和放到每一块中
- 为多块保存一个检验和
3.错误写入
写到错误的位置,在检验和中使用物理标识符(PID)
4.写入丢失
- 已经回报写入完成,但是在从内存到磁盘的写入发生了错误
- 在写入后再发送即可
5.擦净
系统会定期通过检验和来检查每个块是否有效,可以减少某个数据被破坏的可能性